I would like to implement the time out functionality like windows session expiration.
My case is If i didn't touch the screes in 30 minutes I need to redirect the Login screen, other wise normal will need to work.
[[NSNotificationCenter defaultCenter]addObserver:self
selector:#selector(applicationDidTimeout:)name:10 object:nil];
then implement the method,
-(void)applicationDidTimeout:(NSNotification*)notif
{
NSLog(#"10 mints looping");
//calling login screen.
}
but if I'm working in the views also the time expires.How to avoid this.Please any one suggest me to achieve this.
Sorry for the poor english and text formation.
Try This.
Implement a subclass of UIApplication to monitor all the touches in
application.
And implement the following code also in that class
- (void)sendEvent:(UIEvent *)event {
if (event) {
[super sendEvent:event];
NSSet *allTouches = [event allTouches];
if ([allTouches count] > 0) {
UITouchPhase phase = ((UITouch *)[allTouches anyObject]).phase;
if (phase == UITouchPhaseBegan){
[self resetIdleTimer];
}
}
}
}
More Details:
- (void)sendEvent:(UIEvent *)event
Parameters
event: A UIEvent object encapsulating the information about an event, including the touches involved.
Discussion
Subclasses may override this method to intercept
incoming events. Any intercepted events should be dispatched by
calling [super sendEvent:event] after examining the intercepted event.
- (void)resetIdleTimer is the method in which you can schedule your timer.
go on .. :)
Related
I'd like to create a custom UIWindow class that will delay all events by 200 ms.
Is it possible? If yes, how?
OK, I created a custom UIApplication class and everything worked fine:
- (void)sendEvent:(UIEvent *)event
{
[super sendEvent:event]; NSLog(#"Viola!");
}
But once I added this extra code to make it delay the events it didn't send any events.
- (void)sendEvent:(UIEvent *)event
{
[self performSelector:#selector(handleEventsAfterDelay:) withObject:event afterDelay:0.500];
}
- (void)handleEventsAfterDelay:(UIEvent *)event
{
[super sendEvent:event]; NSLog(#"Viola!");
}
Also, this doesn't work:
-(void)sendEvent:(UIEvent *)event
{
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.2]];
[super sendEvent:event];
}
Everything that delays the events doesn't fire at the end. I'm standing frustrated staring in a blank black screen as the app won't even start.
EDIT: I found a way around this problem. Yet, to this day, there's no officially plausible and safe way of doing this but I guess it's just not significant at all.
First, I don't think it's a good idea to delay device event. I would try to work around it some how. Also, I'm not sure if Apple will aprove delayed events.
Another reason for me not to try to delay events, is because I'm no guru. I have no idea how the app will start to behave.
That said, as SomeGuy suggested, you could subclass UIApplication and override all event handling methods relevant to your app. Take a look at the UIApplication class's reference.
I've never delayed native events, but I did work with some event manipulation bafore. I remember that back then, overriding sendEvent: was enough for what I was doing.
You could override sendEvent: as follows:
- (void)sendEvent:(UIEvent *)event
{
[NSThread sleepForTimeInterval:.2];
[super sendEvent:event];
}
This will delay all event 200 ms. This will however block your main thread.
I've tried to use a call to dispatch_after(3) but it didn't work.
If you only want to delay certain event types, use the UIEvent's type and subtype properties. Take a look at the UIEvent class's reference.
Again, I never had to delay, nor do I know what you're exactly trying to accomplish, but I would suggest to find an alternative to delaying the device's event.
Also, don't forget you have to tell Cocoa Touch to use your custom UIApplication. Go to the main.m file and add a principal class name to the UIApplicationMain call:
int main(int argc, char *argv[])
{
#autoreleasepool {
return UIApplicationMain(argc, argv, NSStringFromClass([YourApplication class]), NSStringFromClass([YourAppDelegate class]));
}
}
As a reference, this was my overridden method:
- (void)sendEvent:(UIEvent *)event
{
[super sendEvent:event];
// Was it a touch?
if (event.type == UIEventTypeTouches) {
// Get touch phase.
NSSet *allTouches = [event allTouches];
UITouchPhase phase = [((UITouch *)[allTouches anyObject]) phase];
// Check what to do.
switch (phase) {
case UITouchPhaseBegan:
// Reset counter.
[self.counter resetCount];
break;
case UITouchPhaseEnded:
case UITouchPhaseCancelled:
// Start counter.
[self.counter startCount];
break;
default:
break;
}
}
}
This was used to auto-sign out users from the app after some inactive time (it was a kiosk app).
Unfortunately, it's the best I can do. Maybe someone with more experience can give their input.
I hope this help.
I suppose the problem you have is caused by the UIEvent timestamp. I suppose that events will expire if it is not delivered within a short interval.
You could try adding setTimestamp: by a category and modify it accordingly but the change has to be very exact.
I am following through the Stanford iOS class and have a bit of a design question. In the class we are making a card matching game that has some 20 cards on screen or so. I recently made the cards UIViews so I could draw them properly.
I gave them each a method tap that will swap faceUp to YES/NO, thus flipping the cards. I add the gesture recognizer to each in my ViewController and it works. The individual cards know when they're touched and flip.
However, I need to know in my ViewController that a cardView has been touched... and which one.
How/what ways do I have to do this? Can I broadcast something in my View that the ViewController will listen for? Can I have my viewController handle that taps (but is there a way to get the sending view if I do this?) I apologize if this is really base, but I'm new to iOS and would like to not learn by patching and implementing a broken MVC pattern.
EDIT: Just for information, this is my final implementation.
Each CardView has a tap recognizer on it. When a tap is recognized it calls:
- (void)cardTapped:(UIGestureRecognizer *)gesture
{
UIView *view = [gesture view]; // This method is what I was looking for.
if ([view isKindOfClass:[PlayingCardView class]]) {
PlayingCardView *playingCardView = (PlayingCardView *)view;
[playingCardView flip]; // flips card
// Game code
if (!self.game.hasStarted) {
[self startGame];
}
int cardIndex = [self.cardViews indexOfObject:playingCardView];
[self.game chooseCardAtIndex:cardIndex];
[self updateUI];
}
}
The tag property will tell you which view has been tapped. Set the proper tag when you create your view and in your action method that's been triggered on tap you can call a delegate method that will notify your delegate about which view has been tapped. Make your viewcontroller has the delegate and it will received the notification.
// your target method will look like this:
- (void) didTap:(id)sender {
//... your code that handle flipping the card
[self.delegate didTapOnCard:self]; // where delegate is your view controller
}
You can use touchesBegan method to detect which view was tapped.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
NSLog(#"%d", [touch view].tag); // Considering you have set tags for your UIViews..
if([touch view] == cardView1) // Considering you have a view as cardView1
{
NSLog(#"cardView1 is tapped");
}
}
In your UIView card class add
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)e {
UITouch *touch = [touches anyObject];
if ([self pointInside:[touch locationInView:self] withEvent:nil]) {
[self touchesCancelled:touches withEvent:e];
// Add your card flipping code here
}
}
The broadcast approach: in your UIView's tap method, send a notification:
[[NSNotificationCenter defaultCenter] postNotificationName:#"cardTapped" object:self userInfo:#{ #"card": #(self.cardId), #"faceUp": #(self.faceup) }];
and in your ViewController subscribe to that notification:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(cardTapped:) name:#"cardTapped" object:nil];
and implement
-(void)cardTapped:(NSNotification*)notification
{
int card = [[notification.userInfo objectForKey:#"card"] intValue];
BOOL faceUp = [[notification.userInfo objectForKey:#"faceUp"] boolValue];
}
I know that it is possible to detect a touch on iOS using
UITouch *touch = [[event allTouches] anyObject];
However, is it possible to find out when a user does not touch?
EDIT
I want a method to be executed when the user does not touch the screen for 5 seconds. Is this possible?
I do not have any custom methods that react to touches. I only have the existing methods
-touchesBegan
-touchesMoved and
-touchesEnded
To be more specific, The user can keep touching the screen as many times as he wants, for how long ever he wants. But, when the user does not touch the screen for more than 5 seconds, then a method -sampleMethod needs to be fired.
You can start a timer with a 5 second interval and every time you get a touch, restart the timer:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.timer invalidate];
self.timer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(yourMethod) userInfo:nil repeats:NO];
}
- (void)yourMethod {
NSLog(#"not touched for 5 seconds");
}
Depending on your specific needs, you might want to use touchesEnded:withEvent instead.
I'm going to take a whack at an answer here. Because in the comments you clarified what you're trying to do. Something after 5 seconds with no response. What I'm showing here is typically used in opengl apps which all my apps are. But something like it should work for you even if your not in open gl.
You need something that runs continuously...
- (void) startAnimation
{
if (!animating)
{
displayLink = [NSClassFromString(#"CADisplayLink") displayLinkWithTarget:self selector:#selector(drawView)];
[displayLink setFrameInterval:animationFrameInterval];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
animating = TRUE;
}
}
- (void)stopAnimation
{
if (animating)
{
[displayLink invalidate];
displayLink = nil;
animating = FALSE;
}
}
We use this in oepngl apps to run the drawview function every 60th of a second timed with the refresh of the display. I don't see why you couldn't do it. Then in you drawView method check the time at the beginning and take care of any other crap you need to, like advancing pieces in a game or just checking to see how long messages have been up..
- (void)drawView
{
timeThisRound = CFAbsoluteTimeGetCurrent();
And check it against whatever event triggers the start of the 5 seconds. If you're past 5 seconds then do whatever you're going to do instead of waiting any longer for them to tap the button.
I have my own messaging system that does this. I can set for any message that comes up if it should go away on it's own after 5 seconds. Or if they tap it, it goes away faster. I use the timeThisRound method (a global property) everywhere to track when NOW is so that I can have timing based things as well as touch based things.
Sure, the rest of the time. What do you mean? The only way would be to set a boolean flag to false, and in your "touched" method, set it to true. Then anytime it's false, there's been no touch...
Launch a method after a certain delay, starting when user stops touching the view.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self performSelector:#selector(sampleMethod) withObject:nil afterDelay:5.0f];
}
If user touches the view again, you should cancel the pending method call
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(sampleMethod) object:nil];
}
Remember to put a cancel of the pending method call in dealloc too
- (void)dealloc {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(sampleMethod) object:nil];
}
I have a subclass of UIView in which I've overridden hitTest:withEvent: as follows:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
NSLog(#"Event = %#", event);
return self;
}
For each touch in the view, I am seeing three calls to hitTest:withEvent:. These three calls are made before touch up. The output is as follows:
2011-07-01 09:20:58.553 AppName[930:207] Event = <UITouchesEvent: 0x6a08360> timestamp: 4297.16 touches: {(
)}
2011-07-01 09:20:58.553 AppName[930:207] Event = <UITouchesEvent: 0x6a08360> timestamp: 4297.16 touches: {(
)}
2011-07-01 09:20:58.554 AppName[930:207] Event = <UITouchesEvent: 0x6a08360> timestamp: 4304.54 touches: {(
)}
Based on the timestamps and addresses, it appears as if a single UITouchesEvent object is being used and its timestamp isn't properly set until the third call. Can anyone explain why hitTest:withEvent: gets called three times like this? I'm not looking for a workaround. I just want to understand what's going on.
I had the same problem and was able to solve it with this code. Even though pointInside and hitTest get called 3 times, touchesBegan (or touchesEnded) of the UIView that was touched only gets called once.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (event.type == UIEventTypeTouches)
NSLog(#"%#", self);
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if ([self pointInside:point withEvent:event])
return self;
return [super hitTest:point withEvent:event];
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if (CGRectContainsPoint([self bounds], point))
{
if (event.type == UIEventTypeTouches)
{
return YES;
}
}
return NO;
}
Do you have more than one subview?
From the docs:
This method traverses the view hierarchy by sending the pointInside:withEvent: message to each subview to determine which subview should receive a touch event. If pointInside:withEvent: returns YES, then the subview’s hierarchy is traversed; otherwise, its branch of the view hierarchy is ignored. You rarely need to call this method yourself, but you might override it to hide touch events from subviews.
Yes, it’s normal. The system may tweak the point being hit tested between the calls. Since hitTest should be a pure function with no side-effects, this should be fine.
Refer to Apple Mailing List: Re: -hitTest:withEvent: called twice?
You should check to see if the subtype and type property are all the same. those 3 events do make sense since there's event that needs to be triggered in order for the OS to understand the nature of the touch event.
For example, swipe, pinch and tap all start with the same touch event. My guess is that the first two are fired 1 to register the tap event and the second to test for tap event to "move". the second is called not long afterwards probably to either cancel the pinching/zooming/whatever.
Bottom line, the documentations talks about 3 different type of events: touch, motion and remote events. UIEvent Class Reference
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?
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.
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.
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
}
}
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?
I came across this post looking for a "shaking" implementation. millenomi's answer worked well for me, although i was looking for something that required a bit more "shaking action" to trigger. I've replaced to Boolean value with an int shakeCount. I also reimplemented the L0AccelerationIsShaking() method in Objective-C. You can tweak the ammount of shaking required by tweaking the ammount added to shakeCount. I'm not sure i've found the optimal values yet, but it seems to be working well so far. Hope this helps someone:
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
if (self.lastAcceleration) {
if ([self AccelerationIsShakingLast:self.lastAcceleration current:acceleration threshold:0.7] && shakeCount >= 9) {
//Shaking here, DO stuff.
shakeCount = 0;
} else if ([self AccelerationIsShakingLast:self.lastAcceleration current:acceleration threshold:0.7]) {
shakeCount = shakeCount + 5;
}else if (![self AccelerationIsShakingLast:self.lastAcceleration current:acceleration threshold:0.2]) {
if (shakeCount > 0) {
shakeCount--;
}
}
}
self.lastAcceleration = acceleration;
}
- (BOOL) AccelerationIsShakingLast:(UIAcceleration *)last current:(UIAcceleration *)current threshold:(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);
}
PS:
I've set the update interval to 1/15th of a second.
[[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0 / 15)];
In iOS 8.3 (perhaps earlier) with Swift, it's as simple as overriding the motionBegan or motionEnded methods in your view controller:
class ViewController: UIViewController {
override func motionBegan(motion: UIEventSubtype, withEvent event: UIEvent) {
println("started shaking!")
}
override func motionEnded(motion: UIEventSubtype, withEvent event: UIEvent) {
println("ended shaking!")
}
}
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.
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>
Check out the GLPaint example.
http://developer.apple.com/library/ios/#samplecode/GLPaint/Introduction/Intro.html
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
}
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.
First off, I know this is an old post, but it is still relevant, and I found that the two highest voted answers did not detect the shake as early as possible. This is how to do it:
Link CoreMotion to your project in the target's build phases:
In your ViewController:
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake) {
// Shake detected.
}
}
Easiest solution is to derive a new root window for your application:
#implementation OMGWindow : UIWindow
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (event.type == UIEventTypeMotion && motion == UIEventSubtypeMotionShake) {
// via notification or something
}
}
#end
Then in your application delegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[OMGWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
//…
}
If you are using a Storyboard, this may be trickier, I don’t know the code you will need in the application delegate precisely.
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
A swiftease version based on the very first answer!
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
if ( event?.subtype == .motionShake )
{
print("stop shaking me!")
}
}
In swift 5, this is how you can capture motion and check
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
if motion == .motionShake
{
print("shaking")
}
}
To enable this app-wide, I created a category on UIWindow:
#implementation UIWindow (Utils)
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake) {
// Do whatever you want here...
}
}
#end