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

Je veux réagir quand quelqu'un secoue l'iPhone. Je ne me soucie pas particulièrement de la façon dont ils le secouent, juste qu'il a été agité vigoureusement pendant une fraction de seconde. Personne ne sait comment le détecter?

336
demandé sur James Webster 2008-09-30 00:14:59

16 réponses

Dans 3.0, il y a maintenant un moyen plus facile - crochet dans les nouveaux événements 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 shake. Voici le code que vous pouvez utiliser dans N'importe quel UIView pour obtenir des événements shake:

@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 obtenir l'événement shake simplement en sous-classant la vue avec seulement ces méthodes (puis en sélectionnant cette nouvelle type au lieu du type de base dans IB, ou l'utiliser lors de l'allocation d'une vue).

Dans le contrôleur de vue, vous souhaitez définir cette vue pour qu'elle devienne 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 premier répondeur à partir des actions de l'utilisateur (comme une barre de recherche ou un champ de saisie de texte), vous devrez également restaurer le statut de premier répondeur de la vue tremblante lorsque l'autre vue démissionne!

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

292
répondu Kendall Helmstetter Gelner 2011-10-28 20:42:14

De mon application Diceshaker :

// 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'histeresis empêche l'événement shake de se déclencher plusieurs fois jusqu'à ce que l'utilisateur arrête le shake.

179
répondu millenomi 2008-10-01 20:43:58

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

  • définir le applicationsupportsshaketoemodifier propriété 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: les méthodes de votre point de Vue Contrôleur:
  • 
    -(BOOL)canBecomeFirstResponder {
        return YES;
    }
    
    -(void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
        [self becomeFirstResponder];
    }
    
    - (void)viewWillDisappear:(BOOL)animated {
        [self resignFirstResponder];
        [super viewWillDisappear:animated];
    }
    

  • ajouter le motionEnded méthode à votre contrôleur de vue:
  • 
    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
    {
        if (motion == UIEventSubtypeMotionShake)
        {
            // your code
        }
    }
    
    154
    répondu Eran Talmor 2011-10-28 20:43:05

    Tout d'abord, la réponse de Kendall le 10 Juillet est ponctuelle.

    Maintenant ... Je voulais faire quelque chose de similaire (dans iPhone OS 3.0+), seulement dans mon cas, je le voulais à l'échelle de l'application afin que je puisse alerter diverses parties de l'application quand un tremblement s'est produit. Voilà ce que j'ai fini par faire.

    Tout d'abord, je sous-classe UIWindow. C'est facile comme bonjour. Créez un nouveau fichier de classe avec une interface telle que MotionWindow : UIWindow (n'hésitez pas à choisir le vôtre, natch). Ajouter 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];
        }
    }
    

    Changez @"DeviceShaken" pour nom de notification de votre choix. Enregistrez le fichier.

    Maintenant, si vous utilisez une fenêtre principale.xib (stock Xcode template stuff), allez-y et changez la classe de votre objet Window de UIWindow à MotionWindow {[13] } ou quoi que vous l'appeliez. Sauvez le xib. Si vous configurez UIWindow par programmation, utilisez votre nouvelle classe Window à la place.

    Maintenant, votre application utilise la classe spécialisée UIWindow . Partout où vous voulez être informé d'une secousse, inscrivez-vous pour eux notifications! Comme ceci:

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

    Pour vous retirer en tant qu'observateur:

    [[NSNotificationCenter defaultCenter] removeObserver:self];
    

    Je mets le mien dans viewWillAppear: et viewWillDisappear: où les contrôleurs de vue 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 p'embouteillage. De cette façon, vous pouvez ignorer les autres notifications jusqu'à ce que vous avez vraiment fini de répondre à l'original notification.

    Aussi: Vous pouvez choisir de repère hors de motionBegan vs motionEnded. C'est à vous. Dans mon cas, l'effet doit toujours avoir lieu après l'appareil est au repos (contre quand il se met à trembler), donc j'utilise motionEnded. Essayez les deux et voyez lequel a le plus de sens ... ou détecter / Notifier pour les deux!

    Un de plus (curieux?) observation ici: notez qu'il n'y a aucun signe de gestion des premiers intervenants dans ce code. J'ai seulement essayé cela avec Contrôleurs de vue de Table jusqu'à présent et tout semble fonctionner assez bien ensemble! Je ne peux pas garantir d'autres scénarios cependant.

    Kendall, et. al-quelqu'un peut-il expliquer pourquoi cela pourrait être le cas pour les sous-classes UIWindow? Est-ce parce que la fenêtre est au sommet de la chaîne alimentaire?

    94
    répondu Joe D'Andrea 2009-08-29 14:36:20

    Je suis tombé sur ce post à la recherche d'une implémentation" tremblante". la réponse de millenomi a bien fonctionné pour moi, bien que je cherchais quelque chose qui nécessitait un peu plus "d'action tremblante" pour déclencher. J'ai remplacé la valeur booléenne par un shakeCount int. J'ai également réimplémenté la méthode L0AccelerationIsShaking() dans Objective-C. Vous pouvez modifier le ammount de shaking requis en modifiant le ammount ajouté à shakeCount. Je ne suis pas sûr d'avoir encore trouvé les valeurs optimales, mais cela semble fonctionner bien pour l'instant. 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)];
    
    34
    répondu 2008-11-10 17:14:52

    Vous devez vérifier l'accéléromètre via accelerometer: 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 une secousse.

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

    12
    répondu Dave Verwer 2008-09-29 21:17:44

    Dans iOS 8.3 (peut-être plus tôt) avec Swift, c'est aussi simple que de remplacer les méthodes motionBegan ou motionEnded dans votre contrôleur 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!")
        }
    }
    
    11
    répondu nhgrif 2015-04-20 22:39:56

    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. j'.e:

    @ interface MyViewController: UIViewController

    9
    répondu Benjamin Ortuzar 2011-10-04 07:00:53
    7
    répondu camflan 2011-10-28 20:44:09

    Ajoutez les méthodes suivantes dans ViewController.m fichier, son fonctionnement 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 
         }
    
    7
    répondu Himanshu Mahajan 2012-10-15 12:11:16

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

    Quoi qu'il en soit, je seconde @cire pour m'assurer de définir le statut du premier répondant une fois que la vue fait partie de la hiérarchie de la vue. Ainsi, la définition de l'état du premier répondant dans votre méthode view controllers viewDidLoad ne fonctionnera pas par exemple. Et si vous n'êtes pas sûr de savoir si cela fonctionne [view becomeFirstResponder] vous renvoie un booléen que vous pouvez test.

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

    5
    répondu Newtz 2011-10-28 20:44:49

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

    1. lier 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.
          }
      }
      
    4
    répondu Dennis 2015-03-13 14:59:13

    Solution la plus Simple consiste à dériver une nouvelle fenêtre 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
    

    Puis 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 délicat, Je ne connais pas précisément le code dont vous aurez besoin dans le délégué de l'application.

    3
    répondu mxcl 2014-07-12 12:46:34

    Il suffit d'utiliser ces trois méthodes 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 sur

    1
    répondu Mashhadi 2013-02-21 09:30:47

    Une version swiftease 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!")
        }
    }
    
    1
    répondu user3069232 2017-09-10 07:44:21

    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
    
    -1
    répondu Mike 2017-09-15 19:45:23