346 votes

Comment détecter si 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 il est secoué, mais simplement du fait qu'il a été agité vigoureusement pendant une fraction de seconde. Quelqu'un sait-il comment détecter cela ?

293voto

Dans la version 3.0, il existe désormais un moyen plus simple : il suffit de se brancher sur les nouveaux événements de mouvement.

L'astuce principale est que vous devez avoir un UIView (pas UIViewController) que vous voulez 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 recevoir les événements de secousses :

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

Dans le contrôleur de vue, vous voulez que cette vue devienne le premier intervenant :

- (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 le premier répondant à partir des actions de l'utilisateur (comme une barre de recherche ou un champ de saisie de texte), vous devrez également rétablir le statut de premier répondant de la vue tremblante lorsque l'autre vue démissionne !

Cette méthode fonctionne même si vous avez donné la valeur NO à l'option ApplicationSupportsShakeToEdit.

5 votes

Ça n'a pas vraiment marché pour moi : J'avais besoin de remplacer la fonction viewDidAppear au lieu de viewWillAppear . Je ne suis pas sûr de la raison ; peut-être la vue doit-elle être visible avant de pouvoir faire ce qu'elle fait pour commencer à recevoir les événements de secousses ?

0 votes

J'ai remarqué que dans la version 3.1, quelque chose semblait bizarre, c'était peut-être ça - il se peut que la vue ne prenne pas le statut de premier répondant jusqu'à ce moment-là (ou c'est un bug). Je vais faire des recherches et déposer un radar ou mettre à jour mon code.

0 votes

Bizarre, j'ai testé à nouveau (sous 3.0) et le code fonctionne - bien qu'en 3.1, dans le simulateur, il a cessé de fonctionner comme je l'avais (il fonctionne toujours sur l'appareil cependant).

180voto

millenomi Points 5143

De mon Casse-gueule 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'histeresis empêche l'événement de secousse de se déclencher plusieurs fois jusqu'à ce que l'utilisateur arrête la secousse.

7 votes

Note J'ai ajouté une réponse ci-dessous présentant une méthode plus simple pour 3.0.

2 votes

Que se passe-t-il s'ils le secouent précisément sur un axe particulier ?

5 votes

La meilleure réponse, parce que l'iOS 3.0 motionBegan y motionEnded Les événements ne sont pas très précis ou exacts en termes de détection du début et de la fin exacts d'une secousse. Cette approche vous permet d'être aussi précis que vous le souhaitez.

154voto

Eran Talmor Points 1230

J'ai finalement réussi à le faire fonctionner à l'aide d'exemples de code tirés de ce document Tutoriel sur le gestionnaire d'annulation et de rétablissement .
C'est exactement ce que vous devez faire :

  • Définissez le applicationSupportsShakeToEdit dans le délégué de l'application :

    - (void)applicationDidFinishLaunching:(UIApplication *)application {
    
        application.applicationSupportsShakeToEdit = YES;
    
        [window addSubview:viewController.view];
        [window makeKeyAndVisible];

    }

  • Ajouter/supprimer peutDevenirPremierRépondant , viewDidAppear : y viewWillDisappear : 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 à votre contrôleur de vue :

    • (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event { if (motion == UIEventSubtypeMotionShake) { // your code } }

0 votes

Juste pour ajouter à la solution d'Eran Talmor : vous devriez utiliser un contrôleur de navigation au lieu d'un contrôleur de vue dans le cas où vous avez choisi une telle application dans le sélecteur de nouveau projet.

0 votes

J'ai essayé de l'utiliser, mais parfois il y avait des secousses importantes ou légères et il s'arrêtait.

0 votes

L'étape 1 est maintenant redondante, car la valeur par défaut de l'option applicationSupportsShakeToEdit es YES .

94voto

Joe D'Andrea Points 3588

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

Maintenant ... J'ai voulu faire quelque chose de similaire (dans l'iPhone OS 3.0+), mais dans mon cas, je voulais que cela s'applique à l'ensemble de l'application afin de pouvoir alerter les utilisateurs. divers des parties de l'application lorsqu'une secousse s'est produite. Voici ce que j'ai fini par faire.

D'abord, j'ai sous-classé UIWindow . C'est facile. Créez un nouveau fichier de classe avec une interface telle que MotionWindow : UIWindow (n'hésitez pas à choisir les vôtres, bien sûr). 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];
    }
}

Changement @"DeviceShaken" au nom de la notification de votre choix. Sauvegardez le fichier.

Maintenant, si vous utilisez un MainWindow.xib (le modèle standard de Xcode), allez-y et changez la classe de votre objet Window de la manière suivante UIWindow a MotionWindow ou quel que soit le nom que vous lui donnez. Enregistrez le xib. Si vous avez configuré UIWindow de manière programmatique, utilisez votre nouvelle classe Window à la place.

Maintenant, votre application utilise le système spécialisé UIWindow classe. Partout où vous voulez être informé d'un shake, inscrivez-vous aux notifications ! Comme ça :

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

Se retirer en tant qu'observateur :

[[NSNotificationCenter defaultCenter] removeObserver:self];

J'ai mis le mien dans viewWillAppear : y viewWillDisappear : où les contrôleurs de vue sont concernés. Veillez à ce que votre réponse à l'événement de secousse sache si elle est "déjà en cours" ou non. Sinon, si l'appareil est secoué deux fois de suite, vous aurez un petit embouteillage. De cette façon, vous pouvez ignorer les autres notifications jusqu'à ce que vous ayez vraiment fini de répondre à la notification initiale.

Aussi : Vous pouvez choisir de faire un signal à partir de motionBegan vs. motionEnded . C'est vous qui décidez. Dans mon cas, l'effet doit toujours se produire après l'appareil est au repos (par opposition au moment où il commence à trembler), j'utilise donc motionEnded . Essayez les deux et voyez lequel a le plus de sens ... ou détectez/notifiez pour les deux !

Une autre observation (curieuse ?) ici : Remarquez qu'il n'y a aucun signe de gestion du premier répondant dans ce code. Pour l'instant, je n'ai essayé qu'avec des contrôleurs de vue de tableau et tout semble fonctionner parfaitement ensemble ! Je ne peux cependant pas me porter garant pour d'autres scénarios.

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

1 votes

C'est ça qui est bizarre. Il fonctionne tel quel. Espérons qu'il ne s'agit pas d'un cas de "fonctionnement malgré soi" ! N'hésitez pas à me faire part des résultats de vos propres tests.

0 votes

Je préfère cela à la sous-classification d'un UIView ordinaire, en particulier parce que vous n'avez besoin que d'un seul endroit pour exister, et que la sous-classification d'une fenêtre semble naturelle.

0 votes

Merci jdandrea , j'ai utilisé votre méthode mais le secouage se produit plusieurs fois ! !! je veux juste que les utilisateurs puissent secouer une fois (sur une vue que le secouage fait là) ... ! comment puis-je le gérer ? j'implémente quelque chose comme ceci. j'implémente mon code un peu comme ceci : i-phone.ir/forums/uploadedpictures/ ---- mais le problème c'est que l'application ne tremble qu'une seule fois pendant toute la durée de vie de l'application ! et pas seulement à l'affichage

35voto

La réponse de Millenomi a bien fonctionné pour moi, même si je cherchais quelque chose qui nécessitait un peu plus de "secousses" 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() en Objective-C. Vous pouvez modifier le nombre de secousses nécessaires en modifiant le nombre ajouté à shakeCount. Je ne suis pas sûr d'avoir trouvé les valeurs optimales, mais cela semble bien fonctionner jusqu'à présent. J'espère que cela aidera 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 sur 1/15ème de seconde.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X