Implementing Motion Events
There are a few different things that we’ll need to take care of to see our application work:
1. | Initialize and configure the Core Motion motion manager (CMMotionManager).
|
2. | Manage events to toggle the accelerometer/gyroscope on and off (toggleHardware), registering a handler block when the hardware is turned on.
|
3. | React to the accelerometer/gyroscope readings, updating the background color and alpha transparency values appropriately.
|
Let’s work our way through these different pieces of code now.
Initializing the Core Motion Motion Manager
When the ColorTilt application launches, we need to allocate and initialize the Core Motion motion manager (CMMotionManager) instance, motionManager. Next, we’ll set two properties of the motion manager, accelerometerUpdateInterval and gyroUpdateInterval
to match the frequency (in seconds) with which we want to get updates
from the hardware. We’ll update at 100 times a second, or an update
value of .01.
Uncomment and update the viewDidLoad method to match these requirements:
- (void)viewDidLoad {
motionManager = [[CMMotionManager alloc] init];
motionManager.accelerometerUpdateInterval = .01;
motionManager.gyroUpdateInterval = .01;
[super viewDidLoad];
}
The next step is to implement our action, toggleHardware, so that when one of the UISwitch instances is turned on or off, it tells the motion manager to begin or end readings from the accelerometer/gyroscope.
Managing Accelerometer and Gyroscope Updates
The logic behind the toggleHardware method is simple. If the accelerometer switch is toggled on, the CMMotionManager instance, motionManager,
is asked to start monitoring the accelerometer. Each update is
processed by a handler block that, to make life simple, will call a new
method doAcceleration. If the switch is toggled off, monitoring of the accelerometer stops.
The same pattern will be implemented for the gyroscope, but our gyroscope handler block will be invoking a new method called doGyroscope whenever there is an update.
Implement the controlHardware method, as shown in Listing 2.
Listing 2.
1: - (IBAction)controlHardware:(id)sender {
2:
3: if ([toggleAccelerometer isOn]) {
4: [motionManager
5: startAccelerometerUpdatesToQueue:[NSOperationQueue currentQueue]
6: withHandler:^(CMAccelerometerData *accelData, NSError *error) {
7: [self doAcceleration:accelData.acceleration];
8: }];
9: } else {
10: [motionManager stopAccelerometerUpdates];
11: }
12:
13: if ([toggleGyroscope isOn] && motionManager.gyroAvailable) {
14: [motionManager
15: startGyroUpdatesToQueue:[NSOperationQueue currentQueue]
16: withHandler:^(CMGyroData *gyroData, NSError *error) {
17: [self doRotation:gyroData.rotationRate];
18: }];
19: } else {
20: [toggleGyroscope setOn:NO animated:YES];
21: [motionManager stopGyroUpdates];
22: }
23: }
|
Let’s step through this method to make sure we’re all still on the same page.
Line 3 checks to see whether the toggle switch (UISwitch)
is set to On for the accelerometer. If it is, lines 4–8 tell the motion
manager to start sending updates for the accelerometer and define a
code block to handle each update. The code block consists of a single
line (line 7) that will call the doAcceleration method and send it the acceleration property of the CMAccelerometerData
object received by the handler. This is a simple structure with x, y, z
components, each containing a reading of the acceleration on that axis.
Lines 9–11 handle the case of the accelerometer being toggled Off, using the motion manager’s stopAccelerometerUpdates method to end accelerometer readings.
In line 13, this exact same
process begins for the gyroscope, with minor exceptions. In the
conditional statement in line 13, we also check the motionManager (CMMotionManager) gyroAvailable
property. This is a Boolean to indicate whether the device has a
gyroscope at all. If it doesn’t, we shouldn’t try to read it—so we
toggle the switch back off in line 20.
Another difference
from the accelerometer code is that when handling updates from the
gyroscope (line 17) we will call a method, doRotation, with the gyroscope’s rotation data; available in a simple structure, rotationRate with x, y, z components that indicate the rotation rate, in radians, along each axis.
Finally, when the gyroscope is toggle off, the motion manager’s stopGyroUpdates method is called (line 21).
We now have the code in place to prepare our motion manager, start and stop updates, and invoke the doAccelerometer and doGyroscope methods with the appropriate data from our iPhone hardware. The last step? Actually implementing doAccelerometer and doGyroscope.
Reacting to Accelerometer Updates
We’ll start with doAccelerometer because it is the more complicated of the two. This method should do two things. First, it should change the color of colorView
if the user moves the phone suddenly. Next, if the user gently tilts
the phone along the x-axis, it should progressively make the current
color more solid.
To change colors, we need to
sense motion. One way to do this is to look for g-forces greater than 1g
along each of our x, y, and z axes. This is good for detecting quick,
strong movements. A more subtle approach is to implement a filter to
calculate the difference between gravity and the force the accelerometer
is measuring. We’ll go with the former for our implementation.
To
set the alpha value as the phone tilts, we only pay attention to the
x-axis. The closer the x-axis is to being on edge (a reading of 1.0 or
–1.0), the more solid (1.0 alpha) we’ll make the color. The closer the
x-axis reading is to 0 (the phone standing upright), the more
transparent (0.0 alpha) the color. We’ll use the C function fabs()
to get the absolute value of the reading because for this example we
don’t care whether the device is tilting left edge or right.
Implement doAccelerometer as shown in Listing 3.
Listing 3.
1: - (void)doAcceleration:(CMAcceleration)acceleration {
2:
3: if (acceleration.x > 1.3) {
4: colorView.backgroundColor = [UIColor greenColor];
5: } else if (acceleration.x < -1.3) {
6: colorView.backgroundColor = [UIColor orangeColor];
7: } else if (acceleration.y > 1.3) {
8: colorView.backgroundColor = [UIColor redColor];
9: } else if (acceleration.y < -1.3) {
10: colorView.backgroundColor = [UIColor blueColor];
11: } else if (acceleration.z > 1.3) {
12: colorView.backgroundColor = [UIColor yellowColor];
13: } else if (acceleration.z < -1.3) {
14: colorView.backgroundColor = [UIColor purpleColor];
15: }
16:
17: double value = fabs(acceleration.x);
18: if (value > 1.0) { value = 1.0;}
19: colorView.alpha = value;
20: }
|
The logic is surprisingly
simple. Lines 3–15 check the acceleration along each of the three axes
to see if it is greater (or less than) 1.3—that is, greater than the
force of gravity on the device. If it is, the colorView UIView’s backgroundColor
attribute is set to one of six different predefined colors. In other
words, if you jerk the phone in any direction, the color will be
different.
Did you Know?
A little experimentation shows
that +/–1.3g is a good measure of an abrupt movement. Try it out
yourself with a few different values, and you may decide another value
is better.
Line 17 declares a double-precision floating-point variable value that stores the absolute value of the acceleration along the x-axis (acceleration.x). This is the measurement used to lighten or darken the color when the phone tilts gently.
If value is greater than 1.0, it is reset to 1.0 in line 18.
Line 19 sets the alpha property of colorView to value to finish the implementation.
Not that bad, right? The gyroscope implementation is even easier.
Reacting to Gyroscope Updates
The gyroscope handling is even easier than the accelerometer because we don’t need to change colors—only alter the alpha property of colorView
as the user spins the phone. Instead of forcing the user to rotate the
phone in one direction to get the alpha channel to change, we’ll combine
the rotation rates along all three axes.
Implement doRotation as shown in Listing 4.
Listing 4.
1: - (void)doRotation:(CMRotationRate)rotation {
2:
3: double value = (fabs(rotation.x)+fabs(rotation.y)+fabs(rotation.z))/8.0;
4: if (value > 1.0) { value = 1.0;}
5: colorView.alpha = value;
6:
7: }
|
Line 3 declares value as a double-precision floating-point number and sets it to the sum of the absolute values of the three axis rotation rates (rotation.x, rotation.y, and rotation.z), divided by eight.
By the Way
Why are we dividing by 8.0?
Because an alpha value of 1.0 is a solid color, and a rotation rate of 1
(1 radian) means that the device is rotating at one-half of a rotation a
second. In practice, this just seems too slow a rotation rate to get a
good effect; barely turning the phone at all gives us a solid color.
By dividing by 8.0, the rotation rate would have to be four revolutions a second (8 radians) for value to reach 1, meaning it takes much more effort to make the view’s background color solid.
In line 4, if value is greater than 1.0, it is set back to 1.0 because that is the maximum value the alpha property can accept.
Finally, in line 5, the alpha property of the colorView UIView is set to the calculated value.
You’ve just finished the
application. Plug your iPhone in (this won’t work in the simulator!) and
choose Build and Run in Xcode. Experiment with the sudden motion, tilt,
and rotation, as shown in Figure 4. Be sure to try activating both the accelerometer and gyroscope at once, then one at a time.
Did you Know?
If
you know you’re going to use both the accelerometer and gyroscope in
your application, you can request updates from both simultaneously using
the Core Motion motion manager (CMMotionManager) startDeviceMotionUpdatesToQueue:withHandler method. This incorporates both sensors into a single method with a single handler block.
It’s been a
bit of a journey, but you now have the ability to tie directly into one
of the core features of Apple’s iOS device family: motion. Even better,
you’re doing so with Apple’s latest and greatest framework: Core
Motion.