views:

2841

answers:

2

Hi Everyone:

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'

A: 

Do you have the old method implemented? If you have the old application wide method implemented, you need to remove it before the new one will be called.

Jab
That's the odd part - motionBegan: is not anywhere else in my app...
PF1
I was referring to the old way of detecting shakes. The accelerometer:didAccelerate method. Do you have that implemented somewhere?
Jab
Oh, no. I just created a new project a few days ago, and am only using the motionBegan: method.
PF1
+8  A: 

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.

Paul A. Hoadley
Hi Paul: Actually, I'm using UIView for the class - Would this be why it isn't working?
PF1
That shouldn't prevent it from working, no. You just need to go through the same steps: ensure the object can become first responder, and make sure it _is_ the first responder when the shake event comes in. For example, if a child UITextField is first responder when the shake occurs, _it_ will receive the event, and since it implements its own behaviour (shake to undo) for a shake, the event won't bubble up to where you're implementing motionEnded:withEvent:.
Paul A. Hoadley
For some reason, it still isn't working... I'm using the latest SDK, and call [self becomeFirstResponder]; inside a function that I know runs every time. However, every time I try to get the function to run, it doesn't. Is it possible to check which UIView/UITextField is the first responder at a specific point in the app? I think this may help me track down why it isn't running (if some other element is being set to the first responder later on).
PF1
It does say, though that UIView may not respond to viewDidAppear:(BOOL)animated... Could this be why it isn't working?
PF1
Sorry, I meant go through the same _general_ steps. `viewDidAppear:` is a convenient method to use to take first responder status _if you're a `UIViewController`_. If you must use a `UIView` for this, you're going to need to call `becomeFirstResponder` somewhere else. You can see if a _particular UI element_ is first responder at any time with `isFirstResponder`.
Paul A. Hoadley
I've just built a minimal application that demonstrates this works as described with `motionEnded:withEvent:` in a `UIView` subclass. So there's something else going wrong in your code. As a guess, I'd say that (as I think you suspect), some other element is first responder at the time you're expecting to receive the event.
Paul A. Hoadley
Have you got it working yet? It definitely works. We should be able to get you there.
Paul A. Hoadley
Hi Paul: No, I'm still not getting it to work. Even if I run both [self becomeFirstResponder] and [self isFirstResponder] in the same function, it still says self is not the first responder. Also, in another class that inherits from UIApplicationDelegate and UIScrollViewDelegate it says that it may not respond to becomeFirstResponder...
PF1
This additional information makes it even _less_ clear what's going on. :-) You might need to add some of your source code to your original post.
Paul A. Hoadley
I just added some sample source code to the main body... And sorry that previous post proceeded to confuse things. ;)
PF1
There are some problems with the code excerpts you have posted. Firstly, the `TestApplicationView` class:* I don't know what you mean when you say this class is '"called" from the app delegate class'.* You're _probably_ calling `becomeFirstResponder` too early. See how I've called it in `viewDidAppear` above?Secondly, the warning you get when compiling `TestApplicationAppDelegate` is spot on: it doesn't respond to `becomeFirstResponder`. Why are you trying to make the UIApplicationDelegate become first responder?Seriously, go through the code in my answer step by step. It works.
Paul A. Hoadley
Hi Paul: My problem is more, that in the "Example 1" it tells me that UIView will probably not respond to viewDidAppear. And for the second example, which function could I place it in so it isn't called too early. Or, do you think you could just post a sample project online somewhere if that would be easier?
PF1
Again, the compiler is spot on (of course). A `UIView` won't respond to `viewDidAppear:`. That's a method for your `UIViewController` (it's called after the root view is loaded) in which you send `becomeFirstResponder` to the `UIView` of interest. I will post some minimal code in an additional answer.
Paul A. Hoadley
Minimal example code added to my original answer above. Please go through this step by step in a fresh project. When you get it working, would you mind accepting this answer?
Paul A. Hoadley
Thanks Paul: Accepted. I will ask, however, if there would be a function where when called in the "Second Example" it wouldn't run too early?
PF1
Your "Second Example" class seems to be an app delegate. There are at least two problems with how you've called `becomeFirstResponder` in `applicationDidFinishLaunching:` up there. Firstly, you're sending the message to `self`, _which is the app delegate_, not the `UIView` I assume you mean. Secondly, it's not clear you've set up your view hierarchy by that stage anyway, in which case there's nothing to ask to become first responder. Try my code above, observe how it's really just minimally modified from the view-based template, and work from there.
Paul A. Hoadley