Bonus Material
The following material is from, From Idea to App: Creating iOS UI, Animations and Gestures. Long story short, after writing the book my editor told me it was too long and that I had to cut pages before it could be published. I cut this section on using the gyroscope and accelerometer, and decided to provide it free online.
Enjoy!
Using Accelerometer and Gyroscope Data
You can design applications to use motions other than a simple shake gesture. For example, if your app calls for continuous shaking (instead of a deliberate gesture), or has a view that always orients itself up despite the tilt angle of the device, you can do this by accessing the accelerometer and gyroscope data directly.
This section will show you how to create a UIView of a triangle that will always orient itself pointing up despite the tilt angle of the device. To accomplish this, we will first create a custom UIView and implement the accelerometer control methods. Finally, we’ll transform the view by rotating it to point up relative to the position of the device.
Tip
Accelerometer Data
Every application has access to a single shared accelerometer. By accessing this shared accelerometer, you can pull the x, y, and z components of a device’s current orientation. Long story short, iOS automatically updates these values in the shared accelerometer and you, as a designer or developer, can design applications that take advantage of them.
You can access the values of a device’s shared accelerometer at any time using:
1 | UIAccelerometer *a = [UIAccelerometer sharedAccelerometer]; |
In our case, however, we don’t want to access and store the acceleration data of our accelerometer 60 times a second to update our view in real time. Instead, we’ll set up our view as a delegate to the shared accelerometer. This means we won’t be looking to the accelerometer for changes. Instead, the accelerometer will notify us automatically with new values at a specified time interval.
We’ll start by adding the accelerometer delegate to the custom view’s .h file:
1 2 3 4 5 | @interface PointUp : UIView <UIAccelerometerDelegate> { } @end |
Next, in the .m file of the custom UIView, we’ll assign self as the shared UIAccelerometer delegate. We’ll do this in the initialization of our UIView, so it is done immediately after creation.
1 2 3 4 5 6 7 8 | - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.opaque = NO; [UIAccelerometer sharedAccelerometer].delegate = self; } return self; } |
In line 4 we set our custom UIView’s opaque property to NO, which gives the UIView a transparent background. In line 5 we assign the shared UIAccelerometer delegate to self.
Now we are free to implement the UIAccelerometer protocol methods in our view. There is only one UIAccelerometer delegate protocol method we need to worry about, however, and that method is called regularly based on the accelerometer’s updateInterval property.
Tip
Finally, the shared accelerometer calls accelerometer:didAccelerate in the delegate at the specified interval. Because we’ve set the delegate to self, we can implement this function in the UIView.
1 2 3 4 5 6 7 8 9 10 | - (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration{ UIAccelerationValue x, y, z; x = acceleration.x; y = acceleration.y; z = acceleration.z; self.transform = CGAffineTransformMakeRotation(-x); } |
This code block implements the accelerometer:didAccelerate method in our custom UIView. In lines 4 to 7 we pull the x, y, and z component values from our acceleration data. These properties indicate rotation values in each direction of our device; conveniently for the next step, these values are in radians. In line 9, we create a rotation transformation and apply it to self (our custom UIView).
Tip
Developer Note
Notice how we created our transformation based on the negative value of our x-axis rotation. That’s because if our device is tilted left by 45 degrees, we need to rotate our view right by 45 degrees to keep it oriented up.
Get the Code
MotionDemoPart1-Accelerometer.zip
Alternatively, you can download this and other projects at the downloads page.
Gyroscope Data and Core Motion
The gyroscope data can be used to detect the pitch and roll of a device. This information is often used to control the augmented reality apps you see on the market. Essentially, using the gyroscope data enables you to track a device in 3D space.
Core Motion, like Core Graphics and Core Animations, is a system-level framework that gives you low-level access and control over various pieces of information. You can use Core Motion to track the motion events we’ve discussed up to this point: shake events and the accelerometer. However, for the most part, the techniques we’ve described for shake events and accelerometer data can handle the majority of features in your apps and using Core Motion would likely be overkill. As of iOS 4.2, however, the only way to access gyroscope data is through Core Motion.
To use Core Motion, you must first create a motion manager. Because of the impact a motion manager has on system resources, you should create only one motion manager per app. Having more than one motion manager drastically decreases the rate at which information is relayed from the device.
To access the gyroscope, first create a motion manager in memory, and then set the gyroscope update interval:
1 2 | motionManager = [[CMMotionManager alloc] init]; motionManager.gyroUpdateInterval = 1.0/60.0; |
Next, because not every iOS device supports the gyroscope (currently only the fourth-generation iPod touch, iPhone 4, and iPad support it), add an if-else statement block to ensure device compatibility:
1 2 3 4 5 6 | if (motionManager.gyroAvailable) { //Handle our gyroscope handler } else { NSLog(@"No gyroscope on device."); [motionManager release]; } |
In this code block, we check to see if our motion manager has a gyroscope available in line 1. If a gyroscope is present, we can set up gyroscope handlers, however, if there is no gyroscope we should release the motion manager we just created.
Handling the gyroscope is a little more complicated than what we’ve done thus far. Because of the interaction with Core Motion, we must set up an operation queue to handle gyroscope updates, and then define a handler method to pass through that operation queue when updates are needed. The following code block sets up the option queue and creates the gyroscope handler. Inside that handler, we take the gyroscope rotation data (y-axis) and apply it as a vertical scale transformation on the triangle UIView. As a result, when the device is rotated along its y-axis (as if spinning on top the dock connector) the triangle scales in and out.
1 2 3 4 5 | opQ = [[NSOperationQueue currentQueue] retain]; gyroHandler = ^ (CMGyroData *gyroData, NSError *error) { CMRotationRate rotate = gyroData.rotationRate; up.transform = CGAffineTransformMakeScale(rotate.y, 1); }; |
In line 1 we create the operation queue. This is used to handle the series of operations every time the gyroscope is updated. In line 2 to 5 we create our gyroscope handler. This looks complicated, but essentially just means that the gyroscope handler executes lines 3 and 4 every time this handler is run. In line 3, we pull the gyroscope’s rotation data, and in line 4, we apply that data as a transformation along the width of the UIView.
Finally, to begin updating our gyroscope, we need to tell the motion manager to start gyroscope updates.
1 | [motionManager startGyroUpdatesToQueue:opQ withHandler:gyroHandler]; |
Here we tell the motion manager to start generating gyroscope updates in the operation queue, opQ, which we created in a previous code block. Next, we instruct the motion manager to execute the code contained in the gyroscope handler, gyroHandler, again created in a previous code block. The final code should look something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | motionManager = [[CMMotionManager alloc] init]; motionManager.gyroUpdateInterval = 1.0/60.0; if (motionManager.gyroAvailable) { opQ = [[NSOperationQueue currentQueue] retain]; gyroHandler = ^ (CMGyroData *gyroData, NSError *error) { CMRotationRate rotate = gyroData.rotationRate; up.transform = CGAffineTransformMakeScale(rotate.y, 1); }; } else{ NSLog(@"No gyroscope on device."); [motionManager release]; } [motionManager startGyroUpdatesToQueue:opQ withHandler:gyroHandler]; |
Get the Code
Alternatively, you can download this project and others at the downloads page
