This article is based on iPhone in Action, to be published January 2009. It is being reproduced here by permission from Manning Publications. Manning early access books and ebooks are sold exclusively through Manning. Visit the book's page for more information.
The iPhone's accelerometers can provide access to a variety of information about where an iPhone exists in space. By measuring gravity, you can easily discover an iPhone's precise orientation. By measuring movement, you can see how an iPhone is being guided through space. Further, you can build more complex movements into three-dimensional gestures, such as the shake.
Although we usually think about the iPhone's touch screen in reference to input, the accelerometers provide another method that could allow users to make simple adjustments to a program. We can imagine game controls and painting programs both built entirely around the accelerometers.
The Accelerometer and Orientation
The easiest use of the accelerometers is to determine the iPhone's current orientation. The view controller's interfaceOrientation property can be used to determine orientation; however, you can also access that information through the UIDevice object. It can provide more information, as well as real-time access, that isn't available through the view controller.
There are two ways to access the UIDevice information: through properties and through a notification.
The Orientation Property
The easier way to access the UIDevice is to look at its orientation property. You must first access the UIDevice, which you can do by calling a special UIDevice class method, pretty much the same way you access the UIApplication object:
UIDevice *thisDevice = [UIDevice currentDevice];
Once you've done this, look at the orientation property. It will return a constant drawn from UIDeviceOrientation. This looks exactly like the results you can get from a view controller's orientation property except there are two additional values, as shown in Table 1.
|
Constant |
Summary |
|
UIDeviceOrientationPortrait |
iPhone is vertical, right side up |
|
UIDeviceOrientationPortraitUpsideDown |
iPhone is vertical, upside down |
|
UIDeviceOrientationLandscapeLeft |
iPhone is horizontal, tilted left |
|
UIDeviceOrientationLandscapeRight |
iPhone is horizontal, tilted right |
|
UIDeviceOrientationFaceUp |
iPhone is lying on its back |
|
UIDeviceOrientationFaceDown |
iPhone is lying on its screen |
Table 1: UIDeviceOrientation lists six values for a device's orientation
These additional values show off the first reason that you might want to access the UIDevice object rather than examining orientation using a view controller.
The Orientation Notification
The UIDevice class can also give instant access to an orientation change when it occurs. This is done through a notification and is detailed in Listing 1.
As shown, this is a two-step process. First alert the iPhone that you're ready to start listening for notification that the iPhone has changed its orientation (#1). This is one of a pair of UIDevice methods, the other of which is endGeneratingDeviceOrientationNotifactions. Generally you should only leave notifications on when you actually need them, as they take up CPU cycles and thus increase your power consumption.
Second, register to receive the UIDeviceOrientationDidChangeNotification messages (#2), our first real-live example of the notification methods. Thereafter, whenever an orientation change notification occurs, your deviceDidRotate: method will be called. Note that you won't actually receive notification of what the new orientation is, simply that the change happened. For more, you'll have to go out and query the orientation property, as normal.
We've now seen the two ways in which an iPhone's orientation can be tracked with the UIDevice object, providing more information and more rapid notification than we received when using the view controller. However, that only touches the surface of what you can do with the iPhone's accelerometers. It's probably the raw data about changes in three-dimensional space that you really want to access.
The Accelerometer and Movement
When you're using the iPhone's orientation notification, the frameworks are doing your work for you: they're taking low-level acceleration reports and turning them into more meaningful events. It's similar to the concept of iPhone actions, which turn low-level touch events into high-level control events.
Warning
Accelerometer programs cannot be demoed on the iPhone Simulator. Instead you'll need to have a fully provisioned iPhone in order to test out your code on a live device.
However, that's not going to be sufficient for many of you, who would prefer to program an entire interface that effectively uses the iPhone's movement in three-dimensional space as a new user input device. For that, you'll need to access two iPhone classes: UIAccelerometer and the UIAcceleration.
Accessing the UIAccelerometer
The UIAccelerometer is a shared device (like the UIApplication and UIDevice) that you can use to register to receive acceleration-related data. The simple process is shown in Listing 2.
The first step is to access the accelerometer (#1), which is achieved by yet another call to a shared-object method. Having this step as a standalone is probably strictly unnecessary, since you could do the other two steps as nested calls, but we find it a lot more readable.
Next, select your update interval, which is how often you'll receive information on acceleration. This is hardware limited, with a current default of 100 updates per second. That's probably just right if you're creating a game using the accelerometer, but excessive for other purposes. We've opted for 10 updates per second, and thus an updateInterval of .1. You should always go for the lowest acceptable input because doing so preserves power on the iPhone.
Finally, you must set a delegate for the accelerometer. The delegate will need to respond to only one method: accelerometer:didAccelerate:, which sends a message containing a UIAcceleration object whenever acceleration occurs (to the limit of the updateInterval).
Parsing the UIAcceleration
The UIAcceleration information can be used to accurately and easily measure two things: the device's relationship to gravity and the device's movement through three-dimensional space. These are both done through a set of three properties: x, y, and z, which refer to a three-space axis, as shown in Figure 1.
The x-axis measures along the short side of the iPhone, the y-axis measures along the long side of the iPhone, and the z-axis measures through the iPhone. All values are measured in units of "g"s, which is to say g-force. A value of 1g represents the force of gravity on earth.
The thing to watch for when accessing your accelerometer is that it measures two types of force applied to the device: both "g"s of movement in any direction and the actual force of gravity. That means that an iPhone at rest will always show an acceleration of 1g toward the earth's core, something that may require filtering if you're doing more sophisticated iPhone work.
Filtering and the Accelerometer
Though it might seem like the iPhone acceleration data is all mushed together, it's actually easy to isolate exactly the data that you need using basic electronics techniques.
A low-pass filter passes low-frequency signals and attenuates high-frequency signals. That's what you'll use to reduce the effects of sudden changes in your data, such as an abrupt motion.
A high-pass filter passes high-frequency signals and attenuates low-frequency signals. That's what you'll use to reduce the effects of ongoing effects, such as gravity.
We'll see examples of the two main filtering methods in the upcoming sections.
Checking for Gravity
When the accelerometers are at rest, they naturally detect gravity. This may be used to detect the precise orientation that an iPhone is currently held in, going far beyond the four or six states supported by the orientation variables. Listing 3 shows how this could be used.
Any accelerometer program begins with the accelerometer:didAccelerate: method (#1) that's accessed by setting the current program as a delegate of the Accelerometer shared action.
This particular example takes advantage of a red ball (#2), a UIImageView object that you can create in Interface Builder. You're going to move that red ball based on the accelerometer.
In order to access the accelerometer, look at the x and y coordinates of the UIAcceleration object (#3). There's also a z property for the third axis and a timestamp property showing when the UIAcceleration object was created, none of which you'll be using in this example.
Note that we're not yet talking about filtering; we'll hit that topic next after finishing this example. For the moment movement is going to have a pretty limited effect on our example anyway, since an abrupt movement won't really change the ball's slow roll.
After acquiring your gravitic information, make sure that the 50x50 red ball stays within the bounds of the iPhone screen (#4). If you wanted to be really fancy, you could introduce vectors and bounce the ball when it hits the edge, but that's beyond the scope of this example. After that check, you can actually move your ball (#5).
With a very minimal amount of work you've created a program that's truly acted upon by gravity. This program could easily be modified to act as a leveler tool for pictures (by having it only move in one of the three axes) or into a game where a player tries to move a ball from one side of the screen to the other, avoiding pits on the way.
Now, what would it take to do this example totally right by filtering out all movement? The answer, it turns out, is quite easy.
Filtering Out Movement
To create a low-pass filter that will let through gravitic force but not movement, average out the acceleration information that you're receiving so that at any time the vast majority of your input is coming from the steady force of gravity. This is shown in Listing 4, which modifies our previous example.
This example depends on three pre-defined elements. First, there's kFilteringFactor, a constant that's set to .1, which means that only 10% of the active moment will be used at any time. Second, there are two instance variables: gravX and gravY. These each maintain an average for their axis of movement as the program runs. After that, the filter is easy to use.
You filter things, as noted, by averaging 10% of the active movement together with 90% of the average (#1). This smooths out any bumps, which practically means that sudden acceleration will be largely ignored. You did this for the x and y axes because that's all we were using in the example. Clearly, if you cared about the z-axis, you'd need to filter that too.
Afterward use the averaged acceleration instead of the raw acceleration when changing the position of ball (#2). From what looked like an imposing mass of data, you managed to extract the gravity information with just a couple of lines of code.
As we'll see, looking at only the movement is just as easy.
Checking for Movement
In our previous example, we isolated the gravitic portion of the accelerometer's data by creating a simple low-pass filter. With that data in hand, it's trivial to create a high-pass filter. All you need to do is subtract out the low-pass filtered data from the acceleration value; the result is the pure movement data. This process is shown in Listing 5.
We should first offer the warning that this filter does not entirely stop gravitic movement. That's because it takes several iterations for the program to cut out gravity completely. In the meantime, your program will be influenced by gravity for a few fractions of a second at startup and afterward. If that's a problem, you could tell your program to ignore acceleration input for a second after loadup and after an orientation change. We'll show the first solution in our next example.
Absent that caveat, as soon as you start using these new variables, you'll be looking at the filtered movement information rather than the filtered gravity information. However, when you start looking at movement information, you'll see it's a bit trickier to use than gravity. There are two reasons for this.
First, movement information is a lot more ephemeral. It will appear for a second and then be gone again. If you're doing some type of continuous movement, as with the red ball example, you'd need to make your program much more sensitive to detect the movements. You'd have to multiply your moveX and moveY by about 25x to see movement forces applied therein.
Second, it's a lot noisier. As we'll see when we look at some real movement data, it occurs in a multitude of directions at the same time, forcing you to parse out the exact information that you want.
Ultimately, to interpret movement you have to be more sophisticated, recognizing what are effectively gestures in 3D space.
Recognizing Simple Accelerometer Movement
If you want to write programs using acceleration gestures, download the Accelerometer Graph program available from Apple's developer site. This is a nice, simple example of accelerometer use, but more important it also provides you with a clear graph of what the accelerometers report as you make different gestures. Make sure you enable the high-pass filter to get the clearest results.
Figure 2 shows what the Accelerometer Graph looks like in use (but without movement occurring). As you move around an iPhone, you'll quickly come to see how the accelerometers respond.
Here are some important facts about how the accelerometers report information when you look at the Accelerometer Graph:
All of this suggests a simple methodology to detect basic accelerometer movement: you monitor the accelerometer over the course of movement, saving the largest acceleration in each direction. When the movement has ended, you can report out the largest acceleration as the direction of movement.
Listing 6 puts these lessons together by creating a program that could easily be used to report the direction of the iPhone's movement (which you can then use to take an arbitrary action).
As usual, start by creating a low-pass filter (#1), then taking the converse of it (#2) in order to get relatively clean movement data. Because the data can be a little dirty at start, don't accept any data until a second has passed after the first acceleration data was sent (#3). You could cut this down to a mere fraction of a second.
Start looking for movement whenever one of the accelerometers goes above .3g (#4). When that occurs, save the direction of highest movement (#5) and keep measuring it until all of the .3g+ movement has occurred. Afterward make sure that at least a tenth of a second has passed (#6), so that you know you're not just in a lull during a movement.
Finally, do whatever you want to do with your movement data. Here, just report out the information in a label (#7), but you'd doubtless do something much more intricate in a live program. Cleanup is required (#8) to get the next iteration of movement reporting going.
Generally this sample program does the right thing except if movement is very subtle. In those cases it occasionally goes the opposite direction due to the force that's reported when the device stops its motion. If this type of subtlety is a problem for your application, more work would be required. To resolve this, you'd need to make a better comparison of the start and stop forces for movements; if they're similar in magnitude you'll usually want to grab the first force measured, not necessarily the biggest one. However, for the majority of cases, what you've programmed is sufficient, and you now have an iPhone application that can accurately report (and take action based on) direction of movement.
Together gravity and force measurement represent the most obvious things that you can do with the accelerometers, but by no means are they the only things. We suspect that using the accelerometers to measure three-dimensional gestures will be one of their best (and most frequent) uses as the iPhone platform matures.
The Accelerometer and Gestures
Three-dimensional gestures are one of the coolest results of having accelerometers inside your iPhone. Using them, your users can manipulate your iPhone programs without ever having to touch (or even look at) the screen.
To recognize a gesture you must do two things. First, you must accurately track the movements that make up a gesture. Second, make sure that in doing so you won't recognize a random movement that wasn't intended to be a gesture at all.
Technically, recognizing a gesture requires only the coding foundation that we've discussed thus far. However, we're going to show one example that puts that foundation into real-world use by create a method that recognizes a shake gesture.
The Shake
We've defining a shake as a rapid shaking back and forth of your iPhone, like you might shake dice in your hand before you threw them. As usual, Apple's Accelerometer Graph is a great tool to use to figure out what's going on. It shows a shake as primarily having these characteristics, presuming a program that's running in portrait mode:
We can use the above requirements to define the minimum requirements for a shake. If we wanted to tighten them up, we'd probably require four or more peaks of movement, but for now, this will do for us. On the flipside, we might want to decrease the .g requirements so that users don't have to shake their iPhone quite as much (and, in fact, we will). We've detailed the code that will watch for a shake in Listing 7.
We've generally followed the logic of what we saw when viewing the accelerometer graph, though with increased sensitivity, as we noted. Our didShake: method registers a shake if it sees three or more movements of at least .75g, at least one of which is 1.25g, with movements going in opposite directions of each other.
Start off by removing gravity from the accelerometer data (#1), as you did in previous examples. This time don't worry about the quirk at the beginning of data collection, because it won't register as a shake.
The main work of the function is found in its latter half (#4), which is called whenever movement continues to occur. First check if the strongest movement is along the x-axis (#5). If so, register the movement if it's at least .75g and if it's in the opposite direction as the last x-axis move (#6). Do the latter check by seeing if the product of the last two moves on that axis is negative; if so, one must have been positive and the other negative.
If the strongest move was actually on the y-axis (#7), check for a sufficiently strong y-axis move that's in the opposite direction as the last y-axis move (#8). We could have written a more restrictive shake checker that only looked for x-axis movement, or we could have written a less restrictive checker that also looked for z-axis movement, but we opted for this middle ground.
As long as movement continues without a break of more than a quarter of a second, the shakecount continues to accrue, but when movement stops (#2), the program is now ready to see whether a shake actually occurred. Do this by seeing if your shake count equals or exceeds 3 and if the largest movement exceeded 1.25g. Afterward all of the variables are reset to check for the next shake.
By building this shake checker as a separate method, you made it so that it could easily be integrated into a list of checks made in the accelerometer:didAccelerate: method. Listing 8 shows off a simple usage of this that changes the color of the iPhone's screen every time a shake occurs (using a nextColor method which could do whatever you want).
We expect that the shake is going to be the most common 3D gesture programmed into the iPhone. With our code, you've already got it ready to go, though you may choose to change its sensitivity or to make it work in either one or three dimensions.
Other gestures such as a tilt, a fan, a circle, or a swat may be of use, depending on the specifics of your program, but we leave that up to your own ingenuity. For now, we've covered all of the main points of the accelerometers, from orientation to gravity to movement to gestures.