How do I detect when someone shakes an iPhone?

0 votes
asked Sep 29, 2008 by josh-gagnon

I want to react when somebody shakes the iPhone. I don't particularly care how they shake it, just that it was waved vigorously about for a split second. Does anyone know how to detect this?

14 Answers

0 votes
answered Jan 29, 2008 by camflan
0 votes
answered Jan 29, 2008 by dave-verwer

You need to check the accelerometer via accelerometer:didAccelerate: method which is part of the UIAccelerometerDelegate protocol and check whether the values go over a threshold for the amount of movement needed for a shake.

There is decent sample code in the accelerometer:didAccelerate: method right at the bottom of AppController.m in the GLPaint example which is available on the iPhone developer site.

0 votes
answered Sep 1, 2008 by millenomi

From my Diceshaker application:

// Ensures the shake is strong enough on at least two axes before declaring it a shake.
// "Strong enough" means "greater than a client-supplied threshold" in G's.
static BOOL L0AccelerationIsShaking(UIAcceleration* last, UIAcceleration* current, double threshold) {
    double
        deltaX = fabs(last.x - current.x),
        deltaY = fabs(last.y - current.y),
        deltaZ = fabs(last.z - current.z);

    return
        (deltaX > threshold && deltaY > threshold) ||
        (deltaX > threshold && deltaZ > threshold) ||
        (deltaY > threshold && deltaZ > threshold);
}

@interface L0AppDelegate : NSObject <UIApplicationDelegate> {
    BOOL histeresisExcited;
    UIAcceleration* lastAcceleration;
}

@property(retain) UIAcceleration* lastAcceleration;

@end

@implementation L0AppDelegate

- (void)applicationDidFinishLaunching:(UIApplication *)application {
    [UIAccelerometer sharedAccelerometer].delegate = self;
}

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

    if (self.lastAcceleration) {
        if (!histeresisExcited && L0AccelerationIsShaking(self.lastAcceleration, acceleration, 0.7)) {
            histeresisExcited = YES;

            /* SHAKE DETECTED. DO HERE WHAT YOU WANT. */

        } else if (histeresisExcited && !L0AccelerationIsShaking(self.lastAcceleration, acceleration, 0.2)) {
            histeresisExcited = NO;
        }
    }

    self.lastAcceleration = acceleration;
}

// and proper @synthesize and -dealloc boilerplate code

@end

The histeresis prevents the shake event from triggering multiple times until the user stops the shake.

0 votes
answered Jan 18, 2009 by newtz

Sorry to post this as an answer rather than a comment but as you can see I'm new to Stack Overflow and so I'm not yet reputable enough to post comments!

Anyway I second @cire about making sure to set the first responder status once the view is part of the view hierarchy. So setting first responder status in your view controllers viewDidLoad method won't work for example. And if you're unsure as to whether it is working [view becomeFirstResponder] returns you a boolean that you can test.

Another point: you can use a view controller to capture the shake event if you don't want to create a UIView subclass unnecessarily. I know it's not that much hassle but still the option is there. Just move the code snippets that Kendall put into the UIView subclass into your controller and send the becomeFirstResponder and resignFirstResponder messages to self instead of the UIView subclass.

0 votes
answered Jan 22, 2009 by benjamin-ortuzar

This is the basic delegate code you need:

#define kAccelerationThreshold      2.2

#pragma mark -
#pragma mark UIAccelerometerDelegate Methods
    - (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration 
    {   
        if (fabsf(acceleration.x) > kAccelerationThreshold || fabsf(acceleration.y) > kAccelerationThreshold || fabsf(acceleration.z) > kAccelerationThreshold) 
            [self myShakeMethodGoesHere];   
    }

Also set the in the appropriate code in the Interface. i.e:

@interface MyViewController : UIViewController <UIPickerViewDelegate, UIPickerViewDataSource, UIAccelerometerDelegate>

0 votes
answered Jan 29, 2009 by joe-dandrea

First, Kendall's July 10th answer is spot-on.

Now ... I wanted to do something similar (in iPhone OS 3.0+), only in my case I wanted it app-wide so I could alert various parts of the app when a shake occurred. Here's what I ended up doing.

First, I subclassed UIWindow. This is easy peasy. Create a new class file with an interface such as MotionWindow : UIWindow (feel free to pick your own, natch). Add a method like so:

- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
    if (event.type == UIEventTypeMotion && event.subtype == UIEventSubtypeMotionShake) {
        [[NSNotificationCenter defaultCenter] postNotificationName:@"DeviceShaken" object:self];
    }
}

Change @"DeviceShaken" to the notification name of your choice. Save the file.

Now, if you use a MainWindow.xib (stock Xcode template stuff), go in there and change the class of your Window object from UIWindow to MotionWindow or whatever you called it. Save the xib. If you set up UIWindow programmatically, use your new Window class there instead.

Now your app is using the specialized UIWindow class. Wherever you want to be told about a shake, sign up for them notifications! Like this:

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deviceShaken) name:@"DeviceShaken" object:nil];

To remove yourself as an observer:

[[NSNotificationCenter defaultCenter] removeObserver:self];

I put mine in viewWillAppear: and viewWillDisappear: where View Controllers are concerned. Be sure your response to the shake event knows if it is "already in progress" or not. Otherwise, if the device is shaken twice in succession, you'll have a li'l traffic jam. This way you can ignore other notifications until you're truly done responding to the original notification.

Also: You may choose to cue off of motionBegan vs. motionEnded. It's up to you. In my case, the effect always needs to take place after the device is at rest (vs. when it starts shaking), so I use motionEnded. Try both and see which one makes more sense ... or detect/notify for both!

One more (curious?) observation here: Notice there's no sign of first responder management in this code. I've only tried this with Table View Controllers so far and everything seems to work quite nicely together! I can't vouch for other scenarios though.

Kendall, et. al - can anyone speak to why this might be so for UIWindow subclasses? Is it because the window is at the top of the food chain?

0 votes
answered Sep 10, 2009 by kendall-helmstetter-

In 3.0, there's now an easier way - hook into the new motion events.

The main trick is that you need to have some UIView (not UIViewController) that you want as firstResponder to receive the shake event messages. Here's the code that you can use in any UIView to get shake events:

@implementation ShakingView

- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
    if ( event.subtype == UIEventSubtypeMotionShake )
    {
        // Put in code here to handle shake
    }

    if ( [super respondsToSelector:@selector(motionEnded:withEvent:)] )
        [super motionEnded:motion withEvent:event];
}

- (BOOL)canBecomeFirstResponder
{ return YES; }

@end

You can easily transform any UIView (even system views) into a view that can get the shake event simply by subclassing the view with only these methods (and then selecting this new type instead of the base type in IB, or using it when allocating a view).

In the view controller, you want to set this view to become first responder:

- (void) viewWillAppear:(BOOL)animated
{
    [shakeView becomeFirstResponder];
    [super viewWillAppear:animated];
}
- (void) viewWillDisappear:(BOOL)animated
{
    [shakeView resignFirstResponder];
    [super viewWillDisappear:animated];
}

Don't forget that if you have other views that become first responder from user actions (like a search bar or text entry field) you'll also need to restore the shaking view first responder status when the other view resigns!

This method works even if you set applicationSupportsShakeToEdit to NO.

0 votes
answered Jan 8, 2010 by eran-talmor

I finally made it work using code examples from this Undo/Redo Manager Tutorial.
This is exactly what you need to do:

Set the applicationSupportsShakeToEdit property in the App's Delegate:

    - (void)applicationDidFinishLaunching:(UIApplication *)application {

        application.applicationSupportsShakeToEdit = YES;

        [window addSubview:viewController.view];
        [window makeKeyAndVisible];
}

Add/Override canBecomeFirstResponder, viewDidAppear: and viewWillDisappear: methods in your View Controller:

-(BOOL)canBecomeFirstResponder {
    return YES;
}

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

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

Add the motionEnded method to your View Controller:

- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
    if (motion == UIEventSubtypeMotionShake)
    {
        // your code
    }
}
0 votes
answered Jan 15, 2012 by himanshu-mahajan

Add Following methods in ViewController.m file, its working properly

    -(BOOL) canBecomeFirstResponder
    {
         /* Here, We want our view (not viewcontroller) as first responder 
         to receive shake event message  */

         return YES;
    }

    -(void) motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
    {
            if(event.subtype==UIEventSubtypeMotionShake)
            {
                    // Code at shake event

                    UIAlertView *alert=[[UIAlertView alloc] initWithTitle:@"Motion" message:@"Phone Vibrate"delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil];
                    [alert show];
                    [alert release];

                    [self.view setBackgroundColor:[UIColor redColor]];
             }
    }
    - (void)viewDidAppear:(BOOL)animated
    {
             [super viewDidAppear:animated];
             [self becomeFirstResponder];  // View as first responder 
     }
0 votes
answered Jan 21, 2013 by mashhadi

Just use these three methods to do it

- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event{
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event{

for details you may check a complete example code over there

Welcome to Q&A, where you can ask questions and receive answers from other members of the community.
Website Online Counter

...