Search code examples
iphonecocoacocoa-touchmotion

motionBegan: Not Working


I am running into a bit of a problem when I attempt to use (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event in order to capture a shake event. The problem is that the function isn't even running, even when I override canBecomeFirstResponder and set it to return YES. I have seen some other people's posts with this problem, but I have not found an answer.

Thanks for any help!

First Example .h (class inherited from UIView - Is "called" from the app delegate class) {

@class TestApplicationView;

@interface TestApplicationView : UIView {

    IBOutlet UIView *view;
}

}

First Example .m {

- (id)initWithCoder:(NSCoder *)coder
{
    [self setUpView];
    return self;
}

- (id)initWithFrame:(CGRect)frame
{
    [self setUpView];
    return self;
}

- (void)setUpView
{
    [self becomeFirstResponder];
    NSLog(@"First Responder - %d", [self isFirstResponder]);
}

}

Second Example .h (class inherited from UIApplicationDelegate and UIScrollViewDelegate) {

#import <UIKit/UIKit.h>

@class TestApplicationViewController;

@interface TestApplicationAppDelegate : NSObject <UIApplicationDelegate, UIScrollViewDelegate> {

IBOutlet UIWindow *window;
IBOutlet UIScrollView *scrollView;
}

}

Second Example .m {

- (void)applicationDidFinishLaunching:(UIApplication *)application
{
    [self becomeFirstResponder];
}

}

-- The second example returns the following warning: 'TestApplicationAppDelegate' may not respond to '-becomeFirstResponder'


Solution

  • I assume you want to implement this in a subclass of UIViewController. Make the UIViewController capable of becoming first responder:

    - (BOOL)canBecomeFirstResponder {
         return YES;
    }
    

    Make the UIViewController become first responder in viewDidAppear:.

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

    If the view contains any element, such as a UITextField, that might become first responder itself, ensure that element resigns first responder at some point. With a UITextField, for example, the UIViewController would need to implement the UITextFieldDelegate protocol, and actually be the delegate. Then, in textFieldShouldReturn:

    - (BOOL)textFieldShouldReturn:(UITextField *)theTextField {
        // Hides the keyboard
        [theTextField resignFirstResponder];
        // Returns first responder status to self so that shake events register here
        [self becomeFirstResponder];
        return YES;
    }
    

    Implement motionEnded:withEvent:

    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
        if ( event.subtype == UIEventSubtypeMotionShake ) {
        // Do something
        }
    
        if ([super respondsToSelector:@selector(motionEnded:withEvent:)]) {
            [super motionEnded:motion withEvent:event];
        }
    }
    

    There is a good post by Matt Drance of Apple in the iPhone Developer Forums (requires registration as a developer).

    Update: implementing in a subclass of UIView

    As discussed in the comments, this also works, not too surprisingly, in a subclass of UIView. Here's how to construct a minimal example.

    Create a new View-based Application project, call it ShakeTest. Create a new subclass of UIView, call it ShakeView. Make ShakeView.h look like this:

    #import <UIKit/UIKit.h>
    @interface ShakeView : UIView {
    }
    @end
    

    Make ShakeView.m look like this:

    #import "ShakeView.h"
    @implementation ShakeView
    - (BOOL)canBecomeFirstResponder {
        return YES;
    }
    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
        if ( event.subtype == UIEventSubtypeMotionShake ) {
            NSLog(@"Shake!");
        }
    
        if ([super respondsToSelector:@selector(motionEnded:withEvent:)]) {
            [super motionEnded:motion withEvent:event];
        }
    }
    @end
    

    Make ShakeTestViewController.h look like this:

    #import <UIKit/UIKit.h>
    #include "ShakeView.h"
    @interface ShakeTestViewController : UIViewController {
        ShakeView *s;
    }
    @end
    

    Make ShakeTestViewController.m look like this:

    #import "ShakeTestViewController.h"
    @implementation ShakeTestViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        s = [[ShakeView alloc] init];
        [[self view] addSubview:s];
        [s becomeFirstResponder];
    }
    - (void)dealloc {
        [s release];
        [super dealloc];
    }
    @end
    

    Build and run. Hit Cmd-Ctrl-Z to shake the iPhone simulator. Marvel at the log message.