Comment puis-je détecter quand quelqu'un secoue un iPhone?

Je veux réagir quand quelqu'un secoue l'iPhone. Je ne m'inquiète pas particulièrement de la façon dont ils la secouent, juste qu'elle a été agitée vigoureusement pendant une fraction de seconde. Est-ce que quelqu'un sait comment détecter cela?

Dans la version 3.0, il y a maintenant un moyen plus facile – accrocher dans les nouveaux events de mouvement.

L'astuce principale est que vous devez avoir UIView (pas UIViewController) que vous voulez en tant que firstResponder pour recevoir les messages d'événement de tremblement. Voici le code que vous pouvez utiliser dans n'importe quel UIView pour get des events de tremblement:

@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 

Vous pouvez facilement transformer n'importe quel UIView (même les vues système) en une vue qui peut get l'événement shake simplement en sous-classant la vue avec seulement ces methods (puis en sélectionnant ce nouveau type au lieu du type de base dans IB ou en l'utilisant vue).

Dans le controller de vue, vous souhaitez définir cette vue pour devenir le premier répondeur:

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

N'oubliez pas que si vous avez d'autres vues qui deviennent les premiers intervenants des actions de l'user (comme une barre de search ou un champ de saisie de text), vous devrez également restaurer le statut de premier répondeur de la vue agitée lorsque l'autre vue se résout!

Cette méthode fonctionne même si vous définissez applicationSupportsShakeToEdit sur NO.

De mon application Dicehaker :

 // 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 

L'hystérésis empêche l'événement de tremblement de se triggersr plusieurs fois jusqu'à ce que l'user arrête le tremblement.

Je l'ai finalement fait travailler en utilisant des exemples de code de ce tutoriel de gestionnaire Undo / Redo .
C'est exactement ce que vous devez faire:

  • Définissez la propriété applicationSupportsShakeToEdit dans le délégué de l'application:
  •  - (void)applicationDidFinishLaunching:(UIApplication *)application { application.applicationSupportsShakeToEdit = YES; [window addSubview:viewController.view]; [window makeKeyAndVisible]; } 
  • Ajouter / Remplacer canBecomeFirstResponder , viewDidAppear: et viewWillDisappear: methods dans votre View Controller:
  •  -(BOOL)canBecomeFirstResponder { return YES; } -(void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [self becomeFirstResponder]; } - (void)viewWillDisappear:(BOOL)animated { [self resignFirstResponder]; [super viewWillDisappear:animated]; } 
  • Ajoutez la méthode motionEnded à votre View Controller:
  •  - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event { if (motion == UIEventSubtypeMotionShake) { // your code } } 

    Premièrement, la réponse de Kendall le 10 juillet est ponctuelle.

    Maintenant … Je voulais faire quelque chose de similaire (sur iPhone OS 3.0+), seulement dans mon cas je voulais l'utiliser sur l'application pour pouvoir alerter les différentes parties de l'application quand un tremblement se produisait. Voici ce que j'ai fini par faire.

    D'abord, j'ai sous- classé UIWindow . C'est un petit peasy facile. Créer un nouveau file de class avec une interface telle que MotionWindow : UIWindow (n'hésitez pas à choisir votre propre, natch). Ajoutez une méthode comme ceci:

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

    Remplacez @"DeviceShaken" par le nom de la notification de votre choix. Enregistrez le file.

    Maintenant, si vous utilisez un MainWindow.xib (stock de template Xcode), allez là-bas et changez la class de votre object Window de UIWindow à MotionWindow ou quoi que vous l' appeliez . Sauvegardez le xib. Si vous configurez UIWindow par programme, utilisez plutôt votre nouvelle class Window.

    Maintenant, votre application utilise la class UIWindow spécialisée. Partout où vous voulez être informé d'un tremblement, inscrivez-vous pour les notifications! Comme ça:

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

    Pour vous retirer en tant qu'observateur:

     [[NSNotificationCenter defaultCenter] removeObserver:self]; 

    J'ai mis le mien dans viewWillAppear: et viewWillDisappear: où View Controllers sont concernés. Assurez-vous que votre réponse à l'événement Shake sait si elle est déjà en cours ou non. Sinon, si l'appareil est secoué deux fois de suite, vous aurez un embouteillage. De cette façon, vous pouvez ignorer les autres notifications jusqu'à ce que vous ayez vraiment fini de répondre à la notification d'origine.

    En outre: Vous pouvez choisir de vous éloigner de motionBegan vs. motionEnded . C'est à vous. Dans mon cas, l'effet doit toujours avoir lieu après que l'appareil soit au repos (par rapport à quand il commence à trembler), donc j'utilise motionEnded . Essayez les deux et voyez lequel a plus de sens … ou détectez / notifiez pour les deux!

    Encore une observation (curieuse?) Ici: Remarquez qu'il n'y a aucun signe de gestion de premier répondant dans ce code. J'ai seulement essayé ceci avec des controllers de vue de Tableau jusqu'ici et tout semble fonctionner très bien set! Cependant, je ne peux pas garantir d'autres scénarios.

    Kendall, et. al – quelqu'un peut-il expliquer pourquoi cela pourrait être le cas pour les sous-classs UIWindow ? Est-ce parce que la window est au sumt de la string alimentaire?

    Je suis tombé sur ce post à la search d'une implémentation "tremblante". La réponse de millenomi a bien fonctionné pour moi, même si je cherchais quelque chose qui nécessitait un peu plus d'action "tremblante" pour triggersr. J'ai remplacé à la valeur booleanne avec un int shakeCount. J'ai également réimplémenté la méthode L0AccelerationIsShaking () dans Objective-C. Vous pouvez modifier le nombre de secousses requirejses en modifiant l'ammount ajouté à shakeCount. Je ne suis pas sûr d'avoir trouvé les valeurs optimales pour le moment, mais cela semble fonctionner jusqu'à présent. J'espère que cela aide quelqu'un:

     - (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: J'ai mis l'intervalle de mise à jour à 1 / 15ème de seconde.

     [[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0 / 15)]; 

    Vous devez vérifier l'accéléromètre via accéléromètre: didAccelerate: méthode qui fait partie du protocole UIAccelerometerDelegate et vérifier si les valeurs dépassent un seuil pour la quantité de mouvement nécessaire pour un tremblement.

    Il y a un exemple de code décent dans l'accéléromètre: didAccelerate: la méthode en bas de AppController.m dans l'exemple de GLPaint qui est disponible sur le site développeur de l'iPhone.

    Dans iOS 8.3 (peut-être plus tôt) avec Swift, c'est aussi simple que de surcharger les methods motionBegan ou motionEnded dans votre controller de vue:

     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!") } } 

    C'est le code de délégué de base dont vous avez besoin:

     #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]; } 

    Définissez également le code approprié dans l'interface. c'est à dire:

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

    Ajouter les methods suivantes dans le file ViewController.m, fonctionne correctement

      -(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 } 

    Désolé de postr ceci comme une réponse plutôt qu'un commentaire mais comme vous pouvez le voir je suis nouveau sur Stack Overflow et donc je ne suis pas encore assez réputé pour postr des commentaires!

    Quoi qu'il en soit, je deuxième @cire à propos de s'assurer de définir le statut du premier répondeur une fois que la vue fait partie de la hiérarchie de la vue. La définition de l'état du premier répondeur dans la vue viewDidLoad controllers de vue ne fonctionnera donc pas par exemple. Et si vous ne savez pas si cela fonctionne [view becomeFirstResponder] vous renvoie un boolean que vous pouvez tester.

    Un autre point: vous pouvez utiliser un controller de vue pour capturer l'événement shake si vous ne voulez pas créer une sous-class UIView inutilement. Je sais que ce n'est pas beaucoup de tracas mais l'option est toujours là. Déplacez simplement les fragments de code que Kendall a mis dans la sous-class UIView dans votre controller et envoyez les messages becomeFirstResponder et resignFirstResponder à self au lieu de la sous-class UIView.

    Tout d'abord, je sais que c'est un ancien article, mais il est toujours pertinent, et j'ai trouvé que les deux réponses les plus élevées votées n'ont pas détecté le tremblement le plus tôt possible . Voici comment faire:

    1. Liez CoreMotion à votre projet dans les phases de construction de la cible: CoreMotion
    2. Dans votre ViewController:

       - (BOOL)canBecomeFirstResponder { return YES; } - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event { if (motion == UIEventSubtypeMotionShake) { // Shake detected. } } 

    La solution la plus simple consiste à dériver une nouvelle window racine pour votre application:

     @implementation OMGWindow : UIWindow - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event { if (event.type == UIEventTypeMotion && motion == UIEventSubtypeMotionShake) { // via notification or something } } @end 

    Ensuite, dans votre délégué d'application:

     - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[OMGWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; //… } 

    Si vous utilisez un Storyboard, cela peut être plus compliqué, je ne connais pas le code dont vous aurez besoin dans le délégué de l'application.

    Utilisez simplement ces trois methods pour le faire

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

    pour plus de détails, vous pouvez vérifier un exemple de code complet là-bas

    Une version rapide basée sur la toute première réponse!

     override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) { if ( event?.subtype == .motionShake ) { print("stop shaking me!") } } 

    Pour activer cette application à l'échelle, j'ai créé une catégorie sur UIWindow:

     @implementation UIWindow (Utils) - (BOOL)canBecomeFirstResponder { return YES; } - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event { if (motion == UIEventSubtypeMotionShake) { // Do whatever you want here... } } @end