To access orientation and
motion information, we use two different approaches. First, to determine
and react to distinct changes in orientation, we can request that our
iOS device send notifications to our code as the orientation changes.
We’ll be able to compare the messages we receive to constants
representing all possible device orientations—including face up and face
down—and determine what the user has done. Second, we’ll take advantage
of a framework called Core Motion to directly access the accelerometer
and gyroscope data on scheduled intervals. Let’s take a closer look
before starting this hour’s projects.
Requesting Orientation Notifications Through UIDevice
Although it is
possible to read the accelerometer and use the values it returns to
determine a device’s orientation, Apple has made the process much
simpler for developers. The singleton instance UIDevice (representing our device) includes a method beginGeneratingDeviceOrientationNotifications that will tell the iOS to begin sending orientation notifications to the notification center (NSNotificationCenter). Once the notifications start, we can register with an NSNotificationCenter instance to have a method of our choosing automatically be invoked with the device’s orientation changes.
Besides just knowing that an orientation event occurred, we need some reading of what the orientation is. We get this via the UIDevice orientation property. This property, of type UIDeviceOrientation, can be one of six predefined values:
UIDeviceOrientationFaceUp: The device is laying on its back, facing up.
UIDeviceOrientationFaceDown: The device is laying on its front, with the back facing up.
UIDeviceOrientationPortrait: The device is in the “normal” orientation, with the home button at the bottom.
UIDeviceOrientationPortraitUpsideDown: The device is in portrait orientation with the home button at the top.
UIDeviceOrientationLandscapeLeft: The device is lying on its left side.
UIDeviceOrientationLandscapeRight: The device is lying on its right side.
By comparing the property to each of these values, we can determine the orientation and react accordingly.
How Is This Different from Adapting to Interface Rotation Events?
We first tell the device what orientations our
interface supports, and then we can programmatically tell it what to do
when the interface needs to change. The method we are using now is for
getting instantaneous orientation changes regardless of what the
interface supports. The constants UIDeviceOrientationFaceUp and UIDeviceOrientationFaceDown are also meaningless with regard to the interface-managing methods we covered.
Reading the Accelerometer and Gyroscope with Core Motion
You
work with the accelerometer and gyroscope a bit differently. First,
you’ll need to add the Core Motion framework to your project.
Within your code, you’ll create an instance of the Core Motion motion manager: CMMotionManager.
The motion manager should be treated as a singleton—one instance can
provide accelerometer and gyroscope motion services for your entire
application.
By the Way
Recall that a singleton is
a class that is instantiated once in the lifetime of your application.
The services of the iPhone’s hardware provided to your application are
often provided as singletons. Because there is only one accelerometer
and gyroscope in the device, it makes sense that they are accessed via a
singleton. Multiple instances of the CMMotionManager
objects existing in your application wouldn’t add any extra value and
would have the added complexity of managing their memory and lifetime,
both of which are avoided with a singleton.
Unlike orientation
notifications, the Core Motion motion manager enables you to determine
how frequently you receive updates (in seconds) from the accelerometer
and gyroscope and allows you to directly define a handler block that executes each time an update is ready.
Did you Know?
You need to decide how
often your application can benefit from receiving motion updates. You
should decide this by experimenting with different update values until
you come up with an optimal frequency. Receiving more updates than your
application can benefit from can have some negative consequences. Your
application will use more system resources, which might negatively
impact the performance of the other parts of your application and can
certainly affect the battery life of the device. Because you’ll probably
want fairly frequent updates so that your application responds
smoothly, you should take some time to optimize the performance of your CMMotionManager-related code.
Setting up your application to use CMMotionManager
is a simple three-step process of initializing and allocating the
motion manager, setting an updating interval, and then requesting that
updates begin and be sent to a handler block via startAccelerometerUpdatesToQueue:withHandler.
Consider the following code snippet:
1: motionManager = [[CMMotionManager alloc] init];
2: motionManager.accelerometerUpdateInterval = .01;
3: [motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue currentQueue]
4: withHandler:^(CMAccelerometerData *accelData, NSError *error) {
5: //Do something with the acceleration data here!
6: }];
In line 1, the motion manager is allocated and initialized—you’ve seen code like this dozens of times.
Line 2 requests that the accelerometer send updates every .01 seconds—or 100 times per second.
Lines 3–6 start the accelerometer updates and define a handler block that is called for each update.
The handler block can be confusing looking, so I urge you to look at the CMMotionManager documentation to better understand the format. In essence, it’s like a new method being defined within the startAccelerometerUpdatesToQueue:withHandler invocation.
The accelerometer handler is passed two parameters: accelData, an object of type CMAccelerometerData; and error, of type NSError. The accelData object includes an acceleration property of the type CMAcceleration.
This will be the information we are interested in reading and includes
acceleration values along x, y, and z axes. It is up to the code defined
within the handler (currently just a comment in this snippet) to do
something with this incoming data.
Gyroscope updates work almost exactly the same way but require you to define a gyroUpdateInterval for the Core Motion motion manager and begin receiving updates with startGyroUpdatesToQueue:withHandler. The gyroscope’s handler receives an object, gyroData of type CMGyroData and, like the accelerometer handler, an NSError object. We’re interested in the rotation property of gyroData—data of the type CMRotationRate. The rotation property provides rotation rates along the x, y, and z axes.
Did you Know?
Because only the iPhone 4 (and latest iPods) currently support the gyroscope, you can check for its presence using the CMMotionManager Boolean property gyroAvailable. If YES, it exists and can be used.
When we are done processing accelerometer and gyroscope updates, we can stop receiving them with the CMMotionManager methods stopAccelerometerUpdates and stopGyroUpdates, respectively.
Feeling confused? Not to worry—it makes much more sense seeing the pieces come together in code.
By the Way
We’ve skipped over an explanation of the chunk of code that refers to the NSOperationQueue.
An operations queue maintains list of operations that need to be dealt
with (such as accelerometer and gyroscope readings). The queue you need
to use already exists, and we can access it with the code fragment [NSOperationQueue currentQueue]. As long as you follow along, there’s no need to worry about managing operation queues manually.