Wednesday, February 24, 2010

iPhone SDK: Shake, Rattle & Roll - Accelerometer and Vibrate

I’d like to share with you a bit of functionality that we use in most of our applications and that I am asked about frequently: how to implement the accelerometer and the vibrate functions of the iPhone SDK.

There are three parts to the accelerometer equation:
UIAccelerometer Class: lets you register to receive acceleration related data from the hardware, this class is not directly created but rather accessed using a shared UIAccelerometer object.

UIAcceleration Class: this contains x, y, and z axis acceleration data (measured in G-force) as well as a relative timestamp.

UIAccelerometerDelegate Protocol: receives the acceleration related data from the system.

...and only one part to the vibrate equation... which we'll cover below.

For more information on the classes and protocols visit the iPhone Dev Center.

Skake: Using the Accelerometer
Implementing the use of the accelerometer is quite simple and only requires a few steps to start sensing the motion of your iPhone/iPod touch.  In this example we’re going to use a fictitious class MainViewController.

First thing we do is add the UIAccelerometerDelegate protocol to MainViewController.h allowing the class to respond to acceleration related data sent from the device.  Also, note that UIAccelerometer is part of the UIKit Framework.

#import <UIKit/UIKit.h>

@interface MainViewController : UIViewController <UIAccelerometerDelegate> {

}

@end

The UIAccelerometerDelegate protocol only has only has one instance method which delivers acceleration data to the delegate (which we’ll add to the implementation file):

- (void)accelerometer:(UIAccelerometer *)accelerometer
        didAccelerate:(UIAcceleration *)acceleration;

As the accelerometer is a shared object and is constantly updated I like to add on/off methods to my class so that I can control when the updates are handled.  For example you might want the acceleration to only happen when the view controller is visible.

Also, as the information sent from the UIAcceleration delegate is a stream of data updated at a defined interval I want to be able to create a threshold (minimum) before acting upon the data.  I add this in the method itself, but you could create a #define for each of the three axis.  Also, I’d like to point out that this is not complete code as you will still need to implement your view, init, etc, and this covers adding functionality.

Let’s add the methods and definition to our implementation file (MainViewController.m):

#import "MainViewController.h"

@interface MainViewController ()

- (void)startAccelerometer;
- (void)stopAccelerometer;

@end


@implementation MainViewController

- (void)accelerometer:(UIAccelerometer *)accelerometer
        didAccelerate:(UIAcceleration *)acceleration
{
  double const kThreshold = 2.0;
  if (   fabsf(acceleration.x) > kThreshold
      || fabsf(acceleration.y) > kThreshold
      || fabsf(acceleration.z) > kThreshold) {
    NSLog(@"Hey, stop shaking me!");
  }
}

- (void)startAccelerometer {
  UIAccelerometer *accelerometer = [UIAccelerometer sharedAccelerometer];
  accelerometer.delegate = self;
  accelerometer.updateInterval = 0.25;
}

- (void)stopAccelerometer {
  UIAccelerometer *accelerometer = [UIAccelerometer sharedAccelerometer];
  accelerometer.delegate = nil;
}

- (void)viewDidAppear:(BOOL)animated {
  [self startAccelerometer];
}

- (void)viewWillDisappear:(BOOL)animated {
  [self stopAccelerometer];
}

@end

That’s it.  Simple.  When viewDidAppear is called it calls startAccelerometer and we can start listening for acceleration related data.  If the data is above 2g then we log a message.  When viewWillDisappear is called it calls stopAccelerometer and we nil out our delegate and we stop listening for the accelerometer data.

Rattle: Vibrate
To make the device vibrate we need to use the AudioToolbox Framework.  You may be asking why audio, and that is a good question but don’t have a solid answer.  I only know how to make it work and that’s the key.

Import the AudioToolbox into the MainViewController.m file and add a declaration and method:

#import "MainViewController.h"
#import <AudioToolbox/AudioToolbox.h>

@interface MainViewController ()

- (void)vibrate;

@end

@implementation MainViewController

- (void)vibrate {
  AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
}

@end

That’s it.  And it’s simpler than dealing with the shaking of the accelerometer.  Just one line of code (more or less) and you have vibrating in your app.

Roll: Combining Accelerometer and Vibrate
What I like to do is add the vibrate method to the accelerometer:didAccelerate: method so that when user shakes their device it fires off the vibration to give haptic feedback of the action.

All you need to do is modify the accelerometer:didAccelerate: method:

- (void)accelerometer:(UIAccelerometer *)accelerometer
        didAccelerate:(UIAcceleration *)acceleration
{
  double const kThreshold = 2.0;
  if (   fabsf(acceleration.x) > kThreshold
      || fabsf(acceleration.y) > kThreshold
      || fabsf(acceleration.z) > kThreshold) {
  [self vibrate];
  }
}

Now when you shake the device, it vibrates.  Simple to implement and good user feedback.

We first implement this in our kids' application Fridgemags and I have to say that kids like shaking the device and feeling it vibrate.  We also move colored alphabet letters around  the screen and make a whooshing sound of sorts to engage the kids and keeps them busy for a good amount of time.

4 comments:

maniacdev said...

Thanks for these very useful snippet of code.

I'll have to let my nephew try out your app.

Kevin Bomberry said...

You're welcome, I'm glad you find it useful. As for Fridgemags, cool, thanks. I suggest that if you let any children play with your iPhone/iPod touch that you have a shock resistant case for it... just in case. (^_^)

Also, I think we'll have an update for it in a few weeks. If you have any questions, comments or suggestions you can use the integrated feedback form in the app to send them to us. (All of our apps have an integrated feedback system - in-app settings.)

shawnbrinkman said...

Can you explain how to move a UIImage with the accelerometer?

Thank you
Shawn Brinkman

Kevin Bomberry said...

@shawnb457: Your request sounds like a nice follow-up blog entry. Keep your eyes on the postings and either Don or myself will try to get to this shortly for you. Cheers!