motionBegan: Not Working
Asked Answered
A

2

14

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'

Aldercy answered 27/8, 2009 at 17:46 Comment(0)
L
31

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.

Lisp answered 28/8, 2009 at 1:49 Comment(17)
Hi Paul: Actually, I'm using UIView for the class - Would this be why it isn't working?Aldercy
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:.Lisp
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).Aldercy
It does say, though that UIView may not respond to viewDidAppear:(BOOL)animated... Could this be why it isn't working?Aldercy
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.Lisp
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.Lisp
Have you got it working yet? It definitely works. We should be able to get you there.Lisp
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...Aldercy
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.Lisp
I just added some sample source code to the main body... And sorry that previous post proceeded to confuse things. ;)Aldercy
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.Lisp
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?Aldercy
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.Lisp
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?Lisp
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?Aldercy
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.Lisp
followed the UIViewController method, not working; but UIView method is fine.Recti
B
0

Do you have the old method implemented

'acelerometer:didAccelerate'

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

Bodgie answered 27/8, 2009 at 19:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.