web-dev-qa-db-fra.com

Comment détecter une personne qui secoue un iPhone?

Je veux réagir lorsque quelqu'un secoue l'iPhone. Je ne me soucie pas particulièrement de la façon dont ils secouent la situation, juste du fait qu’il a été agité vigoureusement pendant une fraction de seconde. Est-ce que quelqu'un sait comment détecter cela?

339
Josh Gagnon

Dans la version 3.0, il existe désormais un moyen plus simple: vous connecter aux nouveaux événements de mouvement.

L'astuce principale est que vous devez avoir UIView (et non UIViewController) comme premier répondeur pour recevoir les messages d'événement de secousse. 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 quelle UIView (même les vues système) en une vue pouvant obtenir l’événement shake simplement en sous-classant la vue uniquement avec ces méthodes (puis en sélectionnant ce nouveau type au lieu du type de base dans IB, ou en l’utilisant pour allouer vue).

Dans le contrôleur de vue, vous souhaitez que cette vue devienne le premier répondant:

- (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 devenant le premier intervenant à partir d'actions de l'utilisateur (comme une barre de recherche ou un champ de saisie de texte), vous devrez également restaurer le statut de premier intervenant en cas de tremblement lorsque l'autre vue quittera!

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

292

De mon 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

L'histérèse empêche le déclenchement de l'agitation de se déclencher plusieurs fois jusqu'à ce que l'utilisateur arrête l'agitation.

179
millenomi

Je l'ai finalement fait fonctionner en utilisant des exemples de code de ce Tutoriel d'annulation/rétablissement de gestionnaire .
C’est exactement ce que vous devez faire:

  • Met le applicationSupportsShakeToEdit 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 canBecomeFirstResponderviewDidAppear: et viewWillDisappear: méthodes dans votre contrôleur de vue:
  • 
    -(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
    Eran Talmor

    Premièrement, la réponse de Kendall du 10 juillet est parfaite.

    Maintenant ... je voulais faire quelque chose de similaire (dans iPhone OS 3.0+), seulement dans mon cas, je le voulais au niveau de l'application pour pouvoir alerter divers des parties de l'application en cas de secousse. Voici ce que j'ai fini par faire.

    Tout d'abord, j'ai sous-classé IWindow. C'est facile à faire. Créez un nouveau fichier de classe avec une interface telle que MotionWindow : UIWindow (n'hésitez pas à choisir le vôtre, natch). Ajoutez une méthode comme celle-ci:

    - (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 notification de votre choix. Enregistrez le fichier.

    Maintenant, si vous utilisez un MainWindow.xib (élément stocké dans un modèle Xcode), entrez-y et changez la classe de votre objet Window de IWindow à MotionWindow ou peu importe comment vous l'avez appelé. . Enregistrez le xib. Si vous configurez IWindow par programme, utilisez plutôt votre nouvelle classe Window.

    Maintenant, votre application utilise la classe spécialisée IWindow. Où que vous vouliez être informé d'un shake, inscrivez-vous pour recevoir des 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];
    

    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 s'il est "déjà en cours" ou non. Sinon, si l'appareil est secoué deux fois de suite, vous aurez un embouteillage léger. 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 écarter de motionBegan contre motionEnded. C'est à vous. Dans mon cas, l'effet doit toujours avoir lieu après l'appareil est au repos (par rapport au moment où il commence à trembler), je l'utilise donc motionEnded. Essayez les deux et voyez lequel est le plus logique ... ou détectez/notifiez pour les deux!

    Encore une observation (curieuse?) Ici: Notez qu'il n'y a aucun signe de gestion des premiers répondants dans ce code. Je n’ai essayé jusqu’à présent avec les contrôleurs de vue de table et tout semble bien fonctionner ensemble! Je ne peux cependant pas garantir d'autres scénarios.

    Kendall, et. al - quelqu'un peut-il expliquer pourquoi cela pourrait être ainsi pour IWindow sous-classes? Est-ce parce que la fenêtre se situe au sommet de la chaîne alimentaire?

    94
    Joe D'Andrea

    Je suis tombé sur ce message à la recherche 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 se déclencher. J'ai remplacé la valeur booléenne par un int shakeCount. J'ai également réimplémenté la méthode L0AccelerationIsShaking () dans Objective-C. Vous pouvez modifier le nombre de secousses requis en modifiant le montant ajouté à shakeCount. Je ne suis pas sûr d'avoir trouvé les valeurs optimales pour le moment, mais cela semble bien 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 réglé l'intervalle de mise à jour à 1/15 de seconde.

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

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

    Il existe un exemple de code correct dans l'accéléromètre: didAccelerate: méthode au bas de AppController.m dans l'exemple GLPaint disponible sur le site du développeur iPhone.

    12
    Dave Verwer

    Dans iOS 8.3 (peut-être plus tôt précédemment) avec Swift, il suffit 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!")
        }
    }
    
    12
    nhgrif

    Voici le code de base des délégués 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 dans le code approprié dans l'interface. c'est à dire:

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

    9
    Benjamin Ortuzar

    Ajouter les méthodes suivantes dans le fichier ViewController.m, son fonctionnement correct

        -(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
    Himanshu Mahajan
    7
    camflan

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

    Quoi qu'il en soit, je seconde @ cire sur le fait de veiller à définir le statut de premier répondant une fois que la vue fait partie de la hiérarchie des vues. Donc, définir le statut de premier répondant dans votre méthode de contrôleur de vue viewDidLoad ne fonctionnera pas, par exemple. Et si vous n'êtes pas sûr de son fonctionnement, [view becomeFirstResponder] vous renvoie un booléen que vous pouvez tester.

    Un autre point: vous pouvez utilisez 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 si compliqué, mais l'option est toujours là. Il vous suffit de déplacer les extraits de code que Kendall a placés dans la sous-classe UIView dans votre contrôleur et d'envoyer les messages becomeFirstResponder et resignFirstResponder à self au lieu de la sous-classe UIView.

    5
    Newtz

    Tout d’abord, je sais que c’est un vieux message, mais il reste pertinent et j’ai trouvé que les deux réponses les plus votées n’avaient pas détectons le tremblement le plus tôt possible. Voici comment le 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.
          }
      }
      
    5
    Dennis

    La solution la plus simple consiste à créer 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 compliqué, je ne connais pas précisément le code dont vous aurez besoin dans le délégué d’application.

    4
    mxcl

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

    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
    Mashhadi