Skip to content

Commit ee46a30

Browse files
committedDec 27, 2017
MotionGraphs: Version 2.0, 2017-02-02
Updated to Swift This sample demonstrates how to receive updates from sensors using Core Motion. It displays graphs of accelerometer, gyroscope and device motion data. Signed-off-by: Liu Lantao <liulantao@gmail.com>
1 parent 9c23c37 commit ee46a30

34 files changed

+2142
-0
lines changed
 

‎MotionGraphs/LICENSE.txt

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
Sample code project: MotionGraphs
2+
Version: 2.0
3+
4+
IMPORTANT: This Apple software is supplied to you by Apple
5+
Inc. ("Apple") in consideration of your agreement to the following
6+
terms, and your use, installation, modification or redistribution of
7+
this Apple software constitutes acceptance of these terms. If you do
8+
not agree with these terms, please do not use, install, modify or
9+
redistribute this Apple software.
10+
11+
In consideration of your agreement to abide by the following terms, and
12+
subject to these terms, Apple grants you a personal, non-exclusive
13+
license, under Apple's copyrights in this original Apple software (the
14+
"Apple Software"), to use, reproduce, modify and redistribute the Apple
15+
Software, with or without modifications, in source and/or binary forms;
16+
provided that if you redistribute the Apple Software in its entirety and
17+
without modifications, you must retain this notice and the following
18+
text and disclaimers in all such redistributions of the Apple Software.
19+
Neither the name, trademarks, service marks or logos of Apple Inc. may
20+
be used to endorse or promote products derived from the Apple Software
21+
without specific prior written permission from Apple. Except as
22+
expressly stated in this notice, no other rights or licenses, express or
23+
implied, are granted by Apple herein, including but not limited to any
24+
patent rights that may be infringed by your derivative works or by other
25+
works in which the Apple Software may be incorporated.
26+
27+
The Apple Software is provided by Apple on an "AS IS" basis. APPLE
28+
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
29+
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
30+
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
31+
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
32+
33+
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
34+
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
35+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
36+
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
37+
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
38+
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
39+
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
40+
POSSIBILITY OF SUCH DAMAGE.
41+
42+
Copyright (C) 2016 Apple Inc. All Rights Reserved.

‎MotionGraphs/MotionGraphs.xcodeproj/project.pbxproj

+345
Large diffs are not rendered by default.

‎MotionGraphs/MotionGraphs.xcodeproj/project.xcworkspace/contents.xcworkspacedata

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Bucket
3+
type = "1"
4+
version = "2.0">
5+
</Bucket>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "0820"
4+
version = "1.3">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES">
8+
<BuildActionEntries>
9+
<BuildActionEntry
10+
buildForTesting = "YES"
11+
buildForRunning = "YES"
12+
buildForProfiling = "YES"
13+
buildForArchiving = "YES"
14+
buildForAnalyzing = "YES">
15+
<BuildableReference
16+
BuildableIdentifier = "primary"
17+
BlueprintIdentifier = "B579FE3E1DDD1D2700A8C0C7"
18+
BuildableName = "MotionGraphs.app"
19+
BlueprintName = "MotionGraphs"
20+
ReferencedContainer = "container:MotionGraphs.xcodeproj">
21+
</BuildableReference>
22+
</BuildActionEntry>
23+
</BuildActionEntries>
24+
</BuildAction>
25+
<TestAction
26+
buildConfiguration = "Debug"
27+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
28+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
29+
shouldUseLaunchSchemeArgsEnv = "YES">
30+
<Testables>
31+
</Testables>
32+
<MacroExpansion>
33+
<BuildableReference
34+
BuildableIdentifier = "primary"
35+
BlueprintIdentifier = "B579FE3E1DDD1D2700A8C0C7"
36+
BuildableName = "MotionGraphs.app"
37+
BlueprintName = "MotionGraphs"
38+
ReferencedContainer = "container:MotionGraphs.xcodeproj">
39+
</BuildableReference>
40+
</MacroExpansion>
41+
<AdditionalOptions>
42+
</AdditionalOptions>
43+
</TestAction>
44+
<LaunchAction
45+
buildConfiguration = "Debug"
46+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
47+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
48+
launchStyle = "0"
49+
useCustomWorkingDirectory = "NO"
50+
ignoresPersistentStateOnLaunch = "NO"
51+
debugDocumentVersioning = "YES"
52+
debugServiceExtension = "internal"
53+
allowLocationSimulation = "YES">
54+
<BuildableProductRunnable
55+
runnableDebuggingMode = "0">
56+
<BuildableReference
57+
BuildableIdentifier = "primary"
58+
BlueprintIdentifier = "B579FE3E1DDD1D2700A8C0C7"
59+
BuildableName = "MotionGraphs.app"
60+
BlueprintName = "MotionGraphs"
61+
ReferencedContainer = "container:MotionGraphs.xcodeproj">
62+
</BuildableReference>
63+
</BuildableProductRunnable>
64+
<AdditionalOptions>
65+
</AdditionalOptions>
66+
</LaunchAction>
67+
<ProfileAction
68+
buildConfiguration = "Release"
69+
shouldUseLaunchSchemeArgsEnv = "YES"
70+
savedToolIdentifier = ""
71+
useCustomWorkingDirectory = "NO"
72+
debugDocumentVersioning = "YES">
73+
<BuildableProductRunnable
74+
runnableDebuggingMode = "0">
75+
<BuildableReference
76+
BuildableIdentifier = "primary"
77+
BlueprintIdentifier = "B579FE3E1DDD1D2700A8C0C7"
78+
BuildableName = "MotionGraphs.app"
79+
BlueprintName = "MotionGraphs"
80+
ReferencedContainer = "container:MotionGraphs.xcodeproj">
81+
</BuildableReference>
82+
</BuildableProductRunnable>
83+
</ProfileAction>
84+
<AnalyzeAction
85+
buildConfiguration = "Debug">
86+
</AnalyzeAction>
87+
<ArchiveAction
88+
buildConfiguration = "Release"
89+
revealArchiveInOrganizer = "YES">
90+
</ArchiveAction>
91+
</Scheme>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>SchemeUserState</key>
6+
<dict>
7+
<key>MotionGraphs.xcscheme</key>
8+
<dict>
9+
<key>orderHint</key>
10+
<integer>0</integer>
11+
</dict>
12+
</dict>
13+
<key>SuppressBuildableAutocreation</key>
14+
<dict>
15+
<key>B579FE3E1DDD1D2700A8C0C7</key>
16+
<dict>
17+
<key>primary</key>
18+
<true/>
19+
</dict>
20+
</dict>
21+
</dict>
22+
</plist>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
Copyright (C) 2016 Apple Inc. All Rights Reserved.
3+
See LICENSE.txt for this sample’s licensing information
4+
5+
Abstract:
6+
A view controller to display output from the accelerometer.
7+
*/
8+
9+
import UIKit
10+
import CoreMotion
11+
import simd
12+
13+
class AccelerometerViewController: UIViewController, MotionGraphContainer {
14+
15+
// MARK: Properties
16+
17+
@IBOutlet weak var graphView: GraphView!
18+
19+
// MARK: MotionGraphContainer properties
20+
21+
var motionManager: CMMotionManager?
22+
23+
@IBOutlet weak var updateIntervalLabel: UILabel!
24+
25+
@IBOutlet weak var updateIntervalSlider: UISlider!
26+
27+
let updateIntervalFormatter = MeasurementFormatter()
28+
29+
@IBOutlet var valueLabels: [UILabel]!
30+
31+
// MARK: UIViewController overrides
32+
33+
override func viewWillAppear(_ animated: Bool) {
34+
super.viewWillAppear(animated)
35+
36+
startUpdates()
37+
}
38+
39+
override func viewDidDisappear(_ animated: Bool) {
40+
super.viewDidDisappear(animated)
41+
42+
stopUpdates()
43+
}
44+
45+
// MARK: Interface Builder actions
46+
47+
@IBAction func intervalSliderChanged(_ sender: UISlider) {
48+
startUpdates()
49+
}
50+
51+
// MARK: MotionGraphContainer implementation
52+
53+
func startUpdates() {
54+
guard let motionManager = motionManager, motionManager.isAccelerometerAvailable else { return }
55+
56+
updateIntervalLabel.text = formattedUpdateInterval
57+
58+
motionManager.accelerometerUpdateInterval = TimeInterval(updateIntervalSlider.value)
59+
motionManager.showsDeviceMovementDisplay = true
60+
61+
motionManager.startAccelerometerUpdates(to: .main) { accelerometerData, error in
62+
guard let accelerometerData = accelerometerData else { return }
63+
64+
let acceleration: double3 = [accelerometerData.acceleration.x, accelerometerData.acceleration.y, accelerometerData.acceleration.z]
65+
self.graphView.add(acceleration)
66+
self.setValueLabels(xyz: acceleration)
67+
}
68+
}
69+
70+
func stopUpdates() {
71+
guard let motionManager = motionManager, motionManager.isAccelerometerAvailable else { return }
72+
73+
motionManager.stopAccelerometerUpdates()
74+
}
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
Copyright (C) 2016 Apple Inc. All Rights Reserved.
3+
See LICENSE.txt for this sample’s licensing information
4+
5+
Abstract:
6+
The application delegate.
7+
*/
8+
9+
import UIKit
10+
import CoreMotion
11+
12+
@UIApplicationMain
13+
class AppDelegate: UIResponder, UIApplicationDelegate {
14+
15+
var window: UIWindow?
16+
17+
let motionManager = CMMotionManager()
18+
19+
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
20+
// Enumerate through the view controller hierarchy, setting the `motionManager`
21+
// property on those that conform to the `MotionGraphContainer` protocol.
22+
window?.rootViewController?.enumerateHierarchy { viewController in
23+
guard var container = viewController as? MotionGraphContainer else { return }
24+
container.motionManager = motionManager
25+
}
26+
27+
return true
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"images" : [
3+
{
4+
"idiom" : "iphone",
5+
"size" : "20x20",
6+
"scale" : "2x"
7+
},
8+
{
9+
"idiom" : "iphone",
10+
"size" : "20x20",
11+
"scale" : "3x"
12+
},
13+
{
14+
"idiom" : "iphone",
15+
"size" : "29x29",
16+
"scale" : "2x"
17+
},
18+
{
19+
"idiom" : "iphone",
20+
"size" : "29x29",
21+
"scale" : "3x"
22+
},
23+
{
24+
"idiom" : "iphone",
25+
"size" : "40x40",
26+
"scale" : "2x"
27+
},
28+
{
29+
"idiom" : "iphone",
30+
"size" : "40x40",
31+
"scale" : "3x"
32+
},
33+
{
34+
"idiom" : "iphone",
35+
"size" : "60x60",
36+
"scale" : "2x"
37+
},
38+
{
39+
"idiom" : "iphone",
40+
"size" : "60x60",
41+
"scale" : "3x"
42+
}
43+
],
44+
"info" : {
45+
"version" : 1,
46+
"author" : "xcode"
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"info" : {
3+
"version" : 1,
4+
"author" : "xcode"
5+
}
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"images" : [
3+
{
4+
"idiom" : "universal",
5+
"scale" : "1x"
6+
},
7+
{
8+
"idiom" : "universal",
9+
"filename" : "accelerometer@2x.png",
10+
"scale" : "2x"
11+
},
12+
{
13+
"idiom" : "universal",
14+
"filename" : "accelerometer@3x.png",
15+
"scale" : "3x"
16+
}
17+
],
18+
"info" : {
19+
"version" : 1,
20+
"author" : "xcode"
21+
}
22+
}
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"images" : [
3+
{
4+
"idiom" : "universal",
5+
"scale" : "1x"
6+
},
7+
{
8+
"idiom" : "universal",
9+
"filename" : "device_motion@2x.png",
10+
"scale" : "2x"
11+
},
12+
{
13+
"idiom" : "universal",
14+
"filename" : "device_motion@3x.png",
15+
"scale" : "3x"
16+
}
17+
],
18+
"info" : {
19+
"version" : 1,
20+
"author" : "xcode"
21+
}
22+
}
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"images" : [
3+
{
4+
"idiom" : "universal",
5+
"scale" : "1x"
6+
},
7+
{
8+
"idiom" : "universal",
9+
"filename" : "gyroscope@2x.png",
10+
"scale" : "2x"
11+
},
12+
{
13+
"idiom" : "universal",
14+
"filename" : "gyroscope@3x.png",
15+
"scale" : "3x"
16+
}
17+
],
18+
"info" : {
19+
"version" : 1,
20+
"author" : "xcode"
21+
}
22+
}
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"images" : [
3+
{
4+
"idiom" : "universal",
5+
"scale" : "1x"
6+
},
7+
{
8+
"idiom" : "universal",
9+
"filename" : "magnetometer@2x.png",
10+
"scale" : "2x"
11+
},
12+
{
13+
"idiom" : "universal",
14+
"filename" : "magnetometer@3x.png",
15+
"scale" : "3x"
16+
}
17+
],
18+
"info" : {
19+
"version" : 1,
20+
"author" : "xcode"
21+
}
22+
}
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11134" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
3+
<dependencies>
4+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11106"/>
5+
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
6+
</dependencies>
7+
<scenes>
8+
<!--View Controller-->
9+
<scene sceneID="EHf-IW-A2E">
10+
<objects>
11+
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
12+
<layoutGuides>
13+
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
14+
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
15+
</layoutGuides>
16+
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
17+
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
18+
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
19+
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
20+
</view>
21+
</viewController>
22+
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
23+
</objects>
24+
<point key="canvasLocation" x="53" y="375"/>
25+
</scene>
26+
</scenes>
27+
</document>

‎MotionGraphs/MotionGraphs/Base.lproj/Main.storyboard

+672
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
Copyright (C) 2016 Apple Inc. All Rights Reserved.
3+
See LICENSE.txt for this sample’s licensing information
4+
5+
Abstract:
6+
An extension to `CGContext` to draw the horizontal lines in a `GraphView`.
7+
*/
8+
9+
import UIKit
10+
11+
extension CGContext {
12+
func drawGraphLines(in size: CGSize) {
13+
// Configure context settings.
14+
self.saveGState()
15+
setShouldAntialias(false)
16+
translateBy(x: 0, y: size.height / 2.0)
17+
18+
// Add lines to the context.
19+
let gridLineSpacing = size.height / 8.0
20+
for index in -3...3 {
21+
// Skip the center line.
22+
guard index != 0 else { continue }
23+
24+
let position = floor(gridLineSpacing * CGFloat(index))
25+
move(to: CGPoint(x: 0, y: position))
26+
addLine(to: CGPoint(x: size.width, y: position))
27+
}
28+
29+
// Stroke the lines.
30+
setStrokeColor(UIColor.darkGray.cgColor)
31+
strokePath()
32+
33+
// Add and stroke the center line.
34+
move(to: CGPoint(x: 0, y: 0))
35+
addLine(to: CGPoint(x: size.width, y: 0))
36+
37+
setStrokeColor(UIColor.lightGray.cgColor)
38+
strokePath()
39+
40+
// Restore the context state.
41+
self.restoreGState()
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
Copyright (C) 2016 Apple Inc. All Rights Reserved.
3+
See LICENSE.txt for this sample’s licensing information
4+
5+
Abstract:
6+
A view controller to display output from the motion sensor.
7+
*/
8+
9+
import UIKit
10+
import CoreMotion
11+
import simd
12+
13+
class DeviceMotionViewController: UIViewController, MotionGraphContainer {
14+
15+
// MARK: Properties
16+
17+
@IBOutlet var graphSelector: UISegmentedControl!
18+
19+
@IBOutlet var graphsContainer: UIView!
20+
21+
private var selectedDeviceMotion: DeviceMotion {
22+
return DeviceMotion(rawValue: graphSelector.selectedSegmentIndex)!
23+
}
24+
25+
private var graphViews: [GraphView] = []
26+
27+
// MARK: MotionGraphContainer properties
28+
29+
var motionManager: CMMotionManager?
30+
31+
@IBOutlet weak var updateIntervalLabel: UILabel!
32+
33+
@IBOutlet weak var updateIntervalSlider: UISlider!
34+
35+
let updateIntervalFormatter = MeasurementFormatter()
36+
37+
@IBOutlet var valueLabels: [UILabel]!
38+
39+
// MARK: UIViewController overrides
40+
41+
override func viewDidLoad() {
42+
super.viewDidLoad()
43+
44+
// Create graph views for each graph type.
45+
graphViews = DeviceMotion.allTypes.map { type in
46+
return GraphView(frame: graphsContainer.bounds)
47+
}
48+
49+
// Add the graph views to the container view.
50+
for graphView in graphViews {
51+
graphsContainer.addSubview(graphView)
52+
graphView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
53+
}
54+
}
55+
56+
override func viewWillAppear(_ animated: Bool) {
57+
super.viewWillAppear(animated)
58+
59+
startUpdates()
60+
}
61+
62+
override func viewDidDisappear(_ animated: Bool) {
63+
super.viewDidDisappear(animated)
64+
65+
stopUpdates()
66+
}
67+
68+
// MARK: Interface Builder actions
69+
70+
@IBAction func intervalSliderChanged(_ sender: UISlider) {
71+
startUpdates()
72+
}
73+
74+
@IBAction func graphSelectorChanged(_ sender: UISegmentedControl) {
75+
showGraph(selectedDeviceMotion)
76+
}
77+
78+
// MARK: MotionGraphContainer implementation
79+
80+
func startUpdates() {
81+
guard let motionManager = motionManager, motionManager.isDeviceMotionAvailable else { return }
82+
83+
showGraph(selectedDeviceMotion)
84+
updateIntervalLabel.text = formattedUpdateInterval
85+
86+
motionManager.deviceMotionUpdateInterval = TimeInterval(updateIntervalSlider.value)
87+
motionManager.showsDeviceMovementDisplay = true
88+
89+
motionManager.startDeviceMotionUpdates(using: .xArbitraryZVertical, to: .main) { deviceMotion, error in
90+
guard let deviceMotion = deviceMotion else { return }
91+
92+
let attitude = double3([deviceMotion.attitude.roll, deviceMotion.attitude.pitch, deviceMotion.attitude.yaw])
93+
let rotationRate = double3([deviceMotion.rotationRate.x, deviceMotion.rotationRate.y, deviceMotion.rotationRate.z])
94+
let gravity = double3([deviceMotion.gravity.x, deviceMotion.gravity.y, deviceMotion.gravity.z])
95+
let userAcceleration = double3([deviceMotion.userAcceleration.x, deviceMotion.userAcceleration.y, deviceMotion.userAcceleration.z])
96+
97+
self.graphView(for: .attitude).add(attitude)
98+
self.graphView(for: .rotationRate).add(rotationRate)
99+
self.graphView(for: .gravity).add(gravity)
100+
self.graphView(for: .userAcceleration).add(userAcceleration)
101+
102+
// Update the labels with data for the currently selected device motion.
103+
switch self.selectedDeviceMotion {
104+
case .attitude:
105+
self.setValueLabels(rollPitchYaw: attitude)
106+
107+
case .rotationRate:
108+
self.setValueLabels(xyz: rotationRate)
109+
110+
case .gravity:
111+
self.setValueLabels(xyz: gravity)
112+
113+
case .userAcceleration:
114+
self.setValueLabels(xyz: userAcceleration)
115+
}
116+
}
117+
}
118+
119+
func stopUpdates() {
120+
guard let motionManager = motionManager, motionManager.isDeviceMotionActive else { return }
121+
122+
motionManager.stopDeviceMotionUpdates()
123+
}
124+
125+
// MARK: Convenience
126+
127+
private func graphView(for motionType: DeviceMotion) -> GraphView {
128+
let index = motionType.rawValue
129+
return graphViews[index]
130+
}
131+
132+
private func showGraph(_ motionType: DeviceMotion) {
133+
let selectedGraphIndex = motionType.rawValue
134+
135+
for (index, graph) in graphViews.enumerated() {
136+
graph.isHidden = index != selectedGraphIndex
137+
}
138+
}
139+
}
140+
141+
142+
fileprivate enum DeviceMotion: Int {
143+
case attitude, rotationRate, gravity, userAcceleration
144+
145+
static let allTypes: [DeviceMotion] = [.attitude, .rotationRate, .gravity, .userAcceleration]
146+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
Copyright (C) 2016 Apple Inc. All Rights Reserved.
3+
See LICENSE.txt for this sample’s licensing information
4+
5+
Abstract:
6+
A `UIView` subclass that represents a segment of data in a `GraphView`.
7+
*/
8+
9+
import UIKit
10+
import simd
11+
12+
class GraphSegment: UIView {
13+
// MARK: Properties
14+
15+
static let capacity = 32
16+
17+
private(set) var dataPoints = [double3]()
18+
19+
private let startPoint: double3
20+
21+
private let valueRanges: [ClosedRange<Double>]
22+
23+
static let lineColors: [UIColor] = [.red, .green, .blue]
24+
25+
var gridLinePositions = [CGFloat]()
26+
27+
var isFull: Bool {
28+
return dataPoints.count >= GraphSegment.capacity
29+
}
30+
31+
// MARK: Initialization
32+
33+
init(startPoint: double3, valueRanges: [ClosedRange<Double>]) {
34+
self.startPoint = startPoint
35+
self.valueRanges = valueRanges
36+
37+
super.init(frame: CGRect.zero)
38+
}
39+
40+
required init?(coder aDecoder: NSCoder) {
41+
fatalError("init(coder:) has not been implemented")
42+
}
43+
44+
func add(_ values: double3) {
45+
guard dataPoints.count < GraphSegment.capacity else { return }
46+
47+
dataPoints.append(values)
48+
setNeedsDisplay()
49+
}
50+
51+
// MARK: UIView
52+
53+
override func draw(_ rect: CGRect) {
54+
guard let context = UIGraphicsGetCurrentContext() else { return }
55+
56+
// Fill the background.
57+
if let backgroundColor = backgroundColor?.cgColor {
58+
context.setFillColor(backgroundColor)
59+
context.fill(rect)
60+
}
61+
62+
// Draw static lines.
63+
context.drawGraphLines(in: bounds.size)
64+
65+
// Plot lines for the 3 sets of values.
66+
context.setShouldAntialias(false)
67+
context.translateBy(x: 0, y: bounds.size.height / 2.0)
68+
69+
for lineIndex in 0..<3 {
70+
context.setStrokeColor(GraphSegment.lineColors[lineIndex].cgColor)
71+
72+
// Move to the start point for the current line.
73+
let value = startPoint[lineIndex]
74+
let point = CGPoint(x: bounds.size.width, y: scaledValue(for: lineIndex, value: value))
75+
context.move(to: point)
76+
77+
// Draw lines between the data points.
78+
for (pointIndex, dataPoint) in dataPoints.enumerated() {
79+
let value = dataPoint[lineIndex]
80+
let point = CGPoint(x: bounds.size.width - CGFloat(pointIndex + 1), y: scaledValue(for: lineIndex, value: value))
81+
82+
context.addLine(to: point)
83+
}
84+
85+
context.strokePath()
86+
}
87+
}
88+
89+
private func scaledValue(for lineIndex: Int, value: Double) -> CGFloat {
90+
// For simplicity, this assumes the range is centered on zero.
91+
let valueRange = valueRanges[lineIndex]
92+
let scale = Double(bounds.size.height) / (valueRange.upperBound - valueRange.lowerBound)
93+
return CGFloat(floor(value * -scale))
94+
}
95+
}
+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
Copyright (C) 2016 Apple Inc. All Rights Reserved.
3+
See LICENSE.txt for this sample’s licensing information
4+
5+
Abstract:
6+
A `UIView` subclass used to graph the values retreived from sensors. The graph is made up of segments represeneted by child views to avoid having to redraw the whole graph with every update.
7+
*/
8+
9+
import UIKit
10+
import simd
11+
12+
class GraphView: UIView {
13+
14+
// MARK: Properties
15+
16+
private var segments = [GraphSegment]()
17+
18+
private var currentSegment: GraphSegment? {
19+
return segments.last
20+
}
21+
22+
var valueRanges = [-4.0...4.0, -4.0...4.0, -4.0...4.0]
23+
24+
// MARK: Initialization
25+
26+
override init(frame: CGRect) {
27+
super.init(frame: frame)
28+
29+
commonInit()
30+
}
31+
32+
required init?(coder aDecoder: NSCoder) {
33+
super.init(coder: aDecoder)
34+
35+
commonInit()
36+
}
37+
38+
private func commonInit() {
39+
backgroundColor = .black
40+
}
41+
42+
// MARK: UIView overrides
43+
44+
override func draw(_ rect: CGRect) {
45+
let context = UIGraphicsGetCurrentContext()!
46+
47+
// Fill the background.
48+
if let backgroundColor = backgroundColor {
49+
context.setFillColor(backgroundColor.cgColor)
50+
context.fill(bounds)
51+
}
52+
53+
// Draw the static lines.
54+
context.drawGraphLines(in: bounds.size)
55+
}
56+
57+
// MARK: Update methods
58+
59+
func add(_ values: double3) {
60+
// Move all the segments horizontally.
61+
for segment in segments {
62+
segment.center.x += 1
63+
}
64+
65+
// Add a new segment there are no segments or if the current segment is full.
66+
if segments.isEmpty {
67+
addSegment()
68+
}
69+
else if let segment = currentSegment, segment.isFull {
70+
addSegment()
71+
purgeSegments()
72+
}
73+
74+
// Add the values to the current segment.
75+
currentSegment?.add(values)
76+
}
77+
78+
// MARK: Private convenience methods
79+
80+
private func addSegment() {
81+
let segmentWidth = CGFloat(GraphSegment.capacity)
82+
83+
// Determine the start point for the next segment.
84+
let startPoint: double3
85+
if let currentSegment = currentSegment {
86+
startPoint = currentSegment.dataPoints.last!
87+
}
88+
else {
89+
startPoint = [0, 0, 0]
90+
}
91+
92+
// Create and store a new segment.
93+
let segment = GraphSegment(startPoint: startPoint, valueRanges: valueRanges)
94+
segments.append(segment)
95+
96+
// Add the segment to the view.
97+
segment.backgroundColor = backgroundColor
98+
segment.frame = CGRect(x: -segmentWidth, y: 0, width: segmentWidth, height: bounds.size.height)
99+
addSubview(segment)
100+
}
101+
102+
private func purgeSegments() {
103+
segments = segments.filter { segment in
104+
if segment.frame.origin.x < bounds.size.width {
105+
// Include the segment if it's still visible.
106+
return true
107+
}
108+
else {
109+
// Remove the segment before excluding it from the filtered array.
110+
segment.removeFromSuperview()
111+
return false
112+
}
113+
}
114+
}
115+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
Copyright (C) 2016 Apple Inc. All Rights Reserved.
3+
See LICENSE.txt for this sample’s licensing information
4+
5+
Abstract:
6+
A view controller to display output from the gyroscope.
7+
*/
8+
9+
import UIKit
10+
import CoreMotion
11+
import simd
12+
13+
class GyroscopeViewController: UIViewController, MotionGraphContainer {
14+
// MARK: Properties
15+
16+
@IBOutlet weak var graphView: GraphView!
17+
18+
// MARK: MotionGraphContainer properties
19+
20+
var motionManager: CMMotionManager?
21+
22+
@IBOutlet weak var updateIntervalLabel: UILabel!
23+
24+
@IBOutlet weak var updateIntervalSlider: UISlider!
25+
26+
let updateIntervalFormatter = MeasurementFormatter()
27+
28+
@IBOutlet var valueLabels: [UILabel]!
29+
30+
// MARK: UIViewController overrides
31+
32+
override func viewWillAppear(_ animated: Bool) {
33+
super.viewWillAppear(animated)
34+
35+
startUpdates()
36+
}
37+
38+
override func viewDidDisappear(_ animated: Bool) {
39+
super.viewDidDisappear(animated)
40+
41+
stopUpdates()
42+
}
43+
44+
// MARK: Interface Builder actions
45+
46+
@IBAction func intervalSliderChanged(_ sender: UISlider) {
47+
startUpdates()
48+
}
49+
50+
// MARK: MotionGraphContainer implementation
51+
52+
func startUpdates() {
53+
guard let motionManager = motionManager, motionManager.isGyroAvailable else { return }
54+
55+
updateIntervalLabel.text = formattedUpdateInterval
56+
57+
motionManager.gyroUpdateInterval = TimeInterval(updateIntervalSlider.value)
58+
motionManager.showsDeviceMovementDisplay = true
59+
60+
motionManager.startGyroUpdates(to: .main) { gyroData, error in
61+
guard let gyroData = gyroData else { return }
62+
63+
let rotationRate: double3 = [gyroData.rotationRate.x, gyroData.rotationRate.y, gyroData.rotationRate.z]
64+
self.graphView.add(rotationRate)
65+
self.setValueLabels(xyz: rotationRate)
66+
}
67+
}
68+
69+
func stopUpdates() {
70+
guard let motionManager = motionManager, motionManager.isAccelerometerAvailable else { return }
71+
72+
motionManager.stopGyroUpdates()
73+
}
74+
}

‎MotionGraphs/MotionGraphs/Info.plist

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>CFBundleDevelopmentRegion</key>
6+
<string>en</string>
7+
<key>CFBundleExecutable</key>
8+
<string>$(EXECUTABLE_NAME)</string>
9+
<key>CFBundleIdentifier</key>
10+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
11+
<key>CFBundleInfoDictionaryVersion</key>
12+
<string>6.0</string>
13+
<key>CFBundleName</key>
14+
<string>$(PRODUCT_NAME)</string>
15+
<key>CFBundlePackageType</key>
16+
<string>APPL</string>
17+
<key>CFBundleShortVersionString</key>
18+
<string>1.0</string>
19+
<key>CFBundleVersion</key>
20+
<string>1</string>
21+
<key>LSRequiresIPhoneOS</key>
22+
<true/>
23+
<key>UILaunchStoryboardName</key>
24+
<string>LaunchScreen</string>
25+
<key>UIMainStoryboardFile</key>
26+
<string>Main</string>
27+
<key>UIRequiredDeviceCapabilities</key>
28+
<array>
29+
<string>armv7</string>
30+
</array>
31+
<key>UIRequiresFullScreen</key>
32+
<true/>
33+
<key>UISupportedInterfaceOrientations</key>
34+
<array>
35+
<string>UIInterfaceOrientationPortrait</string>
36+
</array>
37+
</dict>
38+
</plist>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
Copyright (C) 2016 Apple Inc. All Rights Reserved.
3+
See LICENSE.txt for this sample’s licensing information
4+
5+
Abstract:
6+
A view controller to display output from the magnetometer.
7+
*/
8+
9+
import UIKit
10+
import CoreMotion
11+
import simd
12+
13+
class MagnetometerViewController: UIViewController, MotionGraphContainer {
14+
// MARK: Properties
15+
16+
@IBOutlet weak var graphView: GraphView!
17+
18+
// MARK: MotionGraphContainer properties
19+
20+
var motionManager: CMMotionManager?
21+
22+
@IBOutlet weak var updateIntervalLabel: UILabel!
23+
24+
@IBOutlet weak var updateIntervalSlider: UISlider!
25+
26+
let updateIntervalFormatter = MeasurementFormatter()
27+
28+
@IBOutlet var valueLabels: [UILabel]!
29+
30+
// MARK: UIViewController overrides
31+
32+
override func viewDidLoad() {
33+
super.viewDidLoad()
34+
35+
graphView.valueRanges = [-30.0...30.0, -250.0...250.0, -1000.0...1000.0]
36+
}
37+
38+
override func viewWillAppear(_ animated: Bool) {
39+
super.viewWillAppear(animated)
40+
41+
startUpdates()
42+
}
43+
44+
override func viewDidDisappear(_ animated: Bool) {
45+
super.viewDidDisappear(animated)
46+
47+
stopUpdates()
48+
}
49+
50+
// MARK: Interface Builder actions
51+
52+
@IBAction func intervalSliderChanged(_ sender: UISlider) {
53+
startUpdates()
54+
}
55+
56+
// MARK: MotionGraphContainer implementation
57+
58+
func startUpdates() {
59+
guard let motionManager = motionManager, motionManager.isGyroAvailable else { return }
60+
61+
updateIntervalLabel.text = formattedUpdateInterval
62+
63+
motionManager.magnetometerUpdateInterval = TimeInterval(updateIntervalSlider.value)
64+
motionManager.showsDeviceMovementDisplay = true
65+
66+
motionManager.startMagnetometerUpdates(to: .main) { magnetometerData, error in
67+
guard let magnetometerData = magnetometerData else { return }
68+
69+
let magneticField: double3 = [magnetometerData.magneticField.x, magnetometerData.magneticField.y, magnetometerData.magneticField.z]
70+
self.graphView.add(magneticField)
71+
self.setValueLabels(xyz: magneticField)
72+
}
73+
}
74+
75+
func stopUpdates() {
76+
guard let motionManager = motionManager, motionManager.isAccelerometerAvailable else { return }
77+
78+
motionManager.stopMagnetometerUpdates()
79+
}
80+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
Copyright (C) 2016 Apple Inc. All Rights Reserved.
3+
See LICENSE.txt for this sample’s licensing information
4+
5+
Abstract:
6+
Defines a protocol that the view controllers conform to and provides helper methods for updating labels.
7+
*/
8+
9+
import CoreMotion
10+
import UIKit
11+
import simd
12+
13+
protocol MotionGraphContainer {
14+
15+
var motionManager: CMMotionManager? { get set }
16+
17+
var updateIntervalLabel: UILabel! { get }
18+
19+
var updateIntervalSlider: UISlider! { get }
20+
21+
var updateIntervalFormatter: MeasurementFormatter { get }
22+
23+
var valueLabels: [UILabel]! { get }
24+
25+
func startUpdates()
26+
27+
func stopUpdates()
28+
}
29+
30+
extension MotionGraphContainer {
31+
private var sortedLabels: [UILabel] {
32+
return valueLabels.sorted { $0.center.y < $1.center.y }
33+
}
34+
35+
func setValueLabels(rollPitchYaw: double3) {
36+
let sortedLabels = self.sortedLabels
37+
sortedLabels[0].text = String(format: "Roll: %+6.4f", rollPitchYaw[0])
38+
sortedLabels[1].text = String(format: "Pitch: %+6.4f", rollPitchYaw[1])
39+
sortedLabels[2].text = String(format: "Yaw: %+6.4f", rollPitchYaw[2])
40+
}
41+
42+
func setValueLabels(xyz: double3) {
43+
let sortedLabels = self.sortedLabels
44+
sortedLabels[0].text = String(format: "x: %+6.4f", xyz[0])
45+
sortedLabels[1].text = String(format: "y: %+6.4f", xyz[1])
46+
sortedLabels[2].text = String(format: "z: %+6.4f", xyz[2])
47+
}
48+
49+
var formattedUpdateInterval: String {
50+
updateIntervalFormatter.numberFormatter.minimumFractionDigits = 3
51+
updateIntervalFormatter.numberFormatter.maximumFractionDigits = 3
52+
53+
let updateInterval = Measurement(value: Double(updateIntervalSlider.value), unit: UnitDuration.seconds)
54+
return updateIntervalFormatter.string(from: updateInterval)
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
Copyright (C) 2016 Apple Inc. All Rights Reserved.
3+
See LICENSE.txt for this sample’s licensing information
4+
5+
Abstract:
6+
Extends `UIViewController` to add a method to enumerate through a view controller heirarchy.
7+
*/
8+
9+
import UIKit
10+
11+
extension UIViewController {
12+
/// Executes the specified closure for each of the child and descendant view
13+
/// controller, as well as for the view controller itself.
14+
func enumerateHierarchy(_ closure: (UIViewController) -> Void) {
15+
closure(self)
16+
17+
for child in childViewControllers {
18+
child.enumerateHierarchy(closure)
19+
}
20+
21+
}
22+
}

‎MotionGraphs/README.md

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# MotionGraphs
2+
3+
This sample demonstrates how to receive updates from sensors using Core Motion. It displays graphs of accelerometer, gyroscope and device motion data.
4+
5+
## Requirements
6+
7+
### Build
8+
9+
Xcode 8.0 or later; iOS 10.0 SDK or later
10+
11+
### Runtime
12+
13+
iOS 10.0 or later
14+
15+
16+
Copyright (C) 2016 Apple Inc. All rights reserved.

0 commit comments

Comments
 (0)
Please sign in to comment.