The CoreMotion framework provides access to several device motion-related sensors. In order to access these sensors, you make use of CMMotionManager. This class enables you to read data from the following sensors:
- Gyroscope
- Accelerometer
- Magnetometer
The gyroscope sensor provides information about the device's current rotation rate in 3D space. The rotation of the device is measured in relation to a variable frame. This means that you can't make any assumptions about the orientation the device is actually in. If you lie the device flat on a table and raise it, the gyroscope will show an increasing value on the X-axis if we pick the phone up in portrait mode. Once we stop rotating the device, the value will be close to zero again.
The accelerometer also measures rotation in 3D space, just like the gyroscope. This makes it very easy to get confused about the differences between the accelerometer and the gyroscope. Both sensors respond to movement, but while the gyroscope measures rotation, the accelerometer measures acceleration. So if we pick up our phone from a table and the gyroscope measures an X-axis rotation, the accelerometer will measure more movement on the Y-axis. This is because the phone is accelerating upward, along the Y-axis. This distinction is very important. The gyroscope measures rotation around an axis and the accelerometer measures movement along an axis.
The last sensor we can individually target is the magnetometer. This sensor uses the earth's magnetic field to determine a device's orientation in the physical world. The CoreLocation framework uses this sensor to determine which direction a user is headed. The ratings you get from this sensor are fairly abstract, and if you're looking to determine the exact device orientation, it's a lot easier to use the CMAttitude class. We'll cover this class soon.
All three of the preceding sensors can be monitored individually with CMMotionManager. The following example shows how you can read the data you receive from the gyroscope; the APIs for reading the accelerometer or the magnetometer follow the same pattern, so for brevity, there is just a single version shown:
let motionManager = CMMotionManager()
func startTracking() {
motionManager.gyroUpdateInterval = 1
motionManager.startGyroUpdates(to: OperationQueue.main, withHandler: { data, error in
guard let gyroData = data
else { return }
print("gyro data: \(gyroData)")
})
}
Note how the motionManager is defined outside of the startTracking method. This is important because if we declare the manager inside of the function, the manager will be de-allocated after the function is executed. We need to make sure that there is something referencing, or holding on to, the manager for as long as we use it. Defining it inside of a function means that by the time the function is executed, nothing holds on to the manager, so the system will clean it up by removing it. If our class is holding onto it, the manager will exist until the instance of the class stops existing.
Also note that we specify an operation queue that we want to use for our motion updates. Depending on your use case, your app might do some in-depth analysis on the received data, or maybe you're storing it somewhere in a database instead of directly displaying it. If this is the case, you might want to handle the incoming motion data on a background queue to prevent the interface from locking up. In the preceding snippet, the main operation queue is used, so the data readings can be passed to the user interface directly without dispatching the UI changes to the main queue explicitly.
If your app needs a more refined output than what the raw sensor data gives you, the CMDeviceMotion class is probably what you're looking for. This class contains processed and ready-to-use information about a device's orientation, rotation, magnetic field, and acceleration. A great use case for this class is to figure out whether a device is currently laid out flat on a table or mounted in a dock. To do this, you use the attitude property of CMDeviceMotion. The attitude provides information about a device's yaw, pitch, and roll. The yaw is the rotation along the Y-axis, the pitch is the rotation along the X-axis, and roll is the rotation on the Z-axis. The following example shows how you can read the device rotation through CMDeviceMotion:
motionManager.startDeviceMotionUpdates(to: OperationQueue.main, withHandler: {
data, _ in
self.xLabel.text = "pitch: \(data?.attitude.pitch ?? 0.0)"
self.yLabel.text = "roll: \(data?.attitude.roll ?? 0.0)"
self.zLabel.text = "yaw: \(data?.attitude.yaw ?? 0.0)"
})
The API for device motion is similar to the raw sensor APIs. We tell the motion manager to start updating, provide an operation queue, and process the incoming data as it's pushed to the app.