Wie erkenne ich, wenn jemand ein iPhone schüttelt?


340

Ich möchte reagieren, wenn jemand das iPhone schüttelt. Es ist mir egal, wie sie es schütteln, nur dass es für den Bruchteil einer Sekunde heftig geschwenkt wurde. Weiß jemand, wie man das erkennt?

Antworten:


292

In 3.0 gibt es jetzt einen einfacheren Weg - haken Sie sich in die neuen Bewegungsereignisse ein.

Der Haupttrick besteht darin, dass Sie über UIView (nicht UIViewController) verfügen müssen, das als firstResponder die Shake-Ereignismeldungen empfangen soll. Hier ist der Code, den Sie in jedem UIView verwenden können, um Shake-Ereignisse abzurufen:

@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

Sie können jede UIView (auch Systemansichten) einfach in eine Ansicht umwandeln, die das Shake-Ereignis abrufen kann, indem Sie die Ansicht nur mit diesen Methoden in Unterklassen unterteilen (und dann diesen neuen Typ anstelle des Basistyps in IB auswählen oder ihn beim Zuweisen von a verwenden Aussicht).

Im Ansichts-Controller möchten Sie diese Ansicht als Ersthelfer festlegen:

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

Vergessen Sie nicht, dass Sie, wenn Sie andere Ansichten haben, die aufgrund von Benutzeraktionen zum Ersthelfer werden (z. B. eine Suchleiste oder ein Texteingabefeld), auch den Ersthelferstatus der erschütternden Ansicht wiederherstellen müssen, wenn die andere Ansicht zurücktritt!

Diese Methode funktioniert auch, wenn Sie applicationSupportsShakeToEdit auf NO setzen.


5
Das hat bei mir nicht ganz funktioniert: Ich musste viewDidAppearstattdessen meine Controller überschreiben viewWillAppear. Ich bin mir nicht sicher warum; Vielleicht muss die Ansicht sichtbar sein, bevor sie alles tun kann, um die Shake-Ereignisse zu empfangen?
Kristopher Johnson

1
Das ist einfacher, aber nicht unbedingt besser. Wenn Sie ein langes, kontinuierliches Verwackeln feststellen möchten, ist dieser Ansatz nicht sinnvoll, da das iPhone gerne feuert, motionEndedbevor das Verwackeln tatsächlich aufgehört hat. Wenn Sie diesen Ansatz verwenden, erhalten Sie eine unzusammenhängende Reihe von kurzen Shakes anstelle eines langen. Die andere Antwort funktioniert in diesem Fall viel besser.
aroth

3
@Kendall - UIViewController implementieren UIResponder und befinden sich in der Responderkette. Das oberste UIWindow ist ebenfalls. developer.apple.com/library/ios/DOCUMENTATION/EventHandling/…
DougW

1
[super respondsToSelector:wird nicht tun, was Sie wollen, da es gleichbedeutend ist mit einem Anruf, [self respondsToSelector:der zurückkehren wird YES. Was Sie brauchen ist [[ShakingView superclass] instancesRespondToSelector:.
Vadim Yelagin

2
Anstatt zu verwenden: if (event.subtype == UIEventSubtypeMotionShake) können Sie verwenden: if (motion == UIEventSubtypeMotionShake)
Hashim Akhtar

179

Aus meiner Diceshaker- Anwendung:

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

Die Histerese verhindert, dass das Shake-Ereignis mehrmals ausgelöst wird, bis der Benutzer das Shake stoppt.


7
Hinweis Ich habe unten eine Antwort hinzugefügt, die eine einfachere Methode für 3.0 darstellt.
Kendall Helmstetter Gelner

2
Was passiert, wenn sie genau auf einer bestimmten Achse schütteln?
jprete

5
Die beste Antwort, da iOS 3.0 motionBeganund motionEndedEreignisse nicht sehr genau oder genau sind, um den genauen Beginn und das genaue Ende eines Shakes zu ermitteln. Mit diesem Ansatz können Sie so präzise sein, wie Sie möchten.
aroth

2
UIAccelerometerDelegate Beschleunigungsmesser: didAccelerate: ist in iOS5 veraltet.
Yantao Xie

Diese andere Antwort ist besser und schneller zu implementieren stackoverflow.com/a/2405692/396133
Abramodj

154

Ich habe es endlich mit Codebeispielen aus diesem Undo / Redo Manager-Tutorial zum Laufen gebracht .
Genau das müssen Sie tun:

  • Legen Sie die applicationSupportsShakeToEdit- Eigenschaft im Delegaten der App fest:
  • 
        - (void)applicationDidFinishLaunching:(UIApplication *)application {
    
            application.applicationSupportsShakeToEdit = YES;
    
            [window addSubview:viewController.view];
            [window makeKeyAndVisible];
    }

  • Hinzufügen / Überschreiben der Methoden canBecomeFirstResponder , viewDidAppear: und viewWillDisappear: in Ihrem View Controller:
  • 
    -(BOOL)canBecomeFirstResponder {
        return YES;
    }
    
    -(void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
        [self becomeFirstResponder];
    }
    
    - (void)viewWillDisappear:(BOOL)animated {
        [self resignFirstResponder];
        [super viewWillDisappear:animated];
    }

  • Fügen Sie Ihrem View Controller die motionEnded- Methode hinzu:
  • 
    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
    {
        if (motion == UIEventSubtypeMotionShake)
        {
            // your code
        }
    }

    Nur um die Eran Talmor-Lösung zu erweitern: Sie sollten einen Navigationscontroller anstelle eines Ansichtscontrollers verwenden, falls Sie eine solche Anwendung in der neuen Projektauswahl ausgewählt haben.
    Rob

    Ich habe versucht, dies zu verwenden, aber manchmal hat Heavyshake oder Light Shake einfach aufgehört
    Vinothp

    Schritt 1 ist jetzt redundant, da der Standardwert von applicationSupportsShakeToEditist YES.
    DonVaughn

    1
    Warum [self resignFirstResponder];vorher anrufen [super viewWillDisappear:animated];? Das scheint eigenartig.
    JaredH

    94

    Erstens ist Kendalls Antwort vom 10. Juli genau richtig.

    Jetzt ... wollte ich etwas Ähnliches tun (in iPhone OS 3.0+), nur in meinem Fall wollte ich es App-weit, damit ich verschiedene Teile der App benachrichtigen konnte, wenn ein Verwackeln auftrat. Folgendes habe ich getan.

    Zuerst habe ich UIWindow unterklassifiziert . Das ist einfach peasy. Erstellen Sie eine neue Klassendatei mit einer Schnittstelle wie MotionWindow : UIWindow(Sie können auch Ihre eigene auswählen, natch). Fügen Sie eine Methode wie folgt hinzu:

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

    Ändern Sie @"DeviceShaken"den Benachrichtigungsnamen Ihrer Wahl. Speicher die Datei.

    Wenn Sie jetzt eine MainWindow.xib (Standard-Xcode-Vorlage) verwenden, gehen Sie dort hinein und ändern Sie die Klasse Ihres Window-Objekts von UIWindow in MotionWindow oder wie auch immer Sie es genannt haben. Speichern Sie die xib. Wenn Sie UIWindow einrichten programmgesteuert , verwenden Sie stattdessen dort Ihre neue Window-Klasse.

    Jetzt verwendet Ihre App die spezialisierte UIWindow- Klasse. Wo immer Sie über einen Shake informiert werden möchten, melden Sie sich für diese Benachrichtigungen an! So was:

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

    Um sich als Beobachter zu entfernen:

    [[NSNotificationCenter defaultCenter] removeObserver:self];

    Ich habe meine in viewWillAppear: und viewWillDisappear: für View Controller eingefügt . Stellen Sie sicher, dass Ihre Antwort auf das Shake-Ereignis weiß, ob es "bereits ausgeführt" wird oder nicht. Andernfalls tritt ein Stau auf, wenn das Gerät zweimal hintereinander geschüttelt wird. Auf diese Weise können Sie andere Benachrichtigungen ignorieren, bis Sie wirklich fertig sind, auf die ursprüngliche Benachrichtigung zu antworten.

    Außerdem: Sie können wählen, ob Sie abrufen möchten motionBegan vs. motionEnded abzurufen . Es liegt an dir. In meinem Fall muss der Effekt immer stattfinden , nachdem das Gerät im Ruhezustand ist (gegenüber, wenn es beginnt zu schütteln), so dass ich motionEnded . Probieren Sie beide aus und finden Sie heraus, welche sinnvoller ist ... oder erkennen / benachrichtigen Sie beide!

    Noch eine (merkwürdige?) Beobachtung hier: Beachten Sie, dass dieser Code keine Anzeichen für eine Ersthelferverwaltung enthält. Ich habe dies bisher nur mit Table View Controllern versucht und alles scheint ganz gut zusammenzuarbeiten! Ich kann jedoch nicht für andere Szenarien bürgen.

    Kendall et al. al - kann jemand darüber sprechen, warum dies für UIWindow so sein könnte ? Unterklassen ? Liegt es daran, dass sich das Fenster oben in der Nahrungskette befindet?


    1
    Das ist es, was so komisch ist. Es funktioniert wie es ist. Hoffentlich geht es aber nicht darum, "trotz allem zu arbeiten"! Bitte lassen Sie mich wissen, was Sie in Ihren eigenen Tests herausfinden.
    Joe D'Andrea

    Ich ziehe dies der Unterklasse einer regulären UIView vor, zumal diese nur an einer Stelle vorhanden sein muss, sodass das Einfügen einer Fensterunterklasse eine natürliche Passform ist.
    Shaggy Frog

    Danke jdandrea, ich benutze deine Methode aber zittere immer wieder !!! Ich möchte nur, dass Benutzer einmal schütteln können (in einer Ansicht, die das Schütteln dort tut) ...! Wie kann ich damit umgehen? Ich implementiere etw so. Ich implementiere meinen Code wie folgt: i-phone.ir/forums/uploadedpictures/… ---- aber das Problem ist, dass die App nur 1 Mal für die Lebensdauer der Anwendung wackelt ! nicht nur ansehen
    Momi

    1
    Momeks: Wenn die Benachrichtigung im Ruhezustand des Geräts (nach dem Schütteln) erfolgen soll, verwenden Sie motionEnded anstelle von motionBegan. Das sollte es tun!
    Joe D'Andrea

    1
    @ Joe D'Andrea - Es funktioniert wie es ist, weil UIWindow in der Responderkette ist. Wenn etwas Höheres in der Kette diese Ereignisse abfing und konsumierte, würde es sie nicht empfangen. developer.apple.com/library/ios/DOCUMENTATION/EventHandling/…
    DougW

    34

    Ich bin auf diesen Beitrag gestoßen, als ich nach einer "erschütternden" Implementierung gesucht habe. millenomis antwort funktionierte gut für mich, obwohl ich nach etwas suchte, das etwas mehr "zittern" erfordert, um auszulösen. Ich habe den booleschen Wert durch einen intakeCount ersetzt. Ich habe auch die L0AccelerationIsShaking () -Methode in Objective-C neu implementiert. Sie können die erforderliche Menge an Schütteln anpassen, indem Sie die Menge an ShakeCount anpassen. Ich bin mir nicht sicher, ob ich die optimalen Werte gefunden habe, aber es scheint bisher gut zu funktionieren. Hoffe das hilft jemandem:

    - (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: Ich habe das Aktualisierungsintervall auf 1/15 Sekunde eingestellt.

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

    Wie kann ich die Gerätebewegung genau wie ein Panorama erkennen?
    user2526811

    Wie kann man ein Verwackeln erkennen, wenn das iPhone nur in X-Richtung bewegt wird? Ich möchte kein Verwackeln in Y- oder Z-Richtung erkennen.
    Manthan

    12

    Sie müssen den Beschleunigungsmesser über den Beschleunigungsmesser überprüfen: didAccelerate: Methode, die Teil des UIAccelerometerDelegate-Protokolls ist, und prüfen, ob die Werte einen Schwellenwert für die für ein Schütteln erforderliche Bewegungsmenge überschreiten.

    Im Beschleunigungsmesser befindet sich ein anständiger Beispielcode: didAccelerate: -Methode ganz unten in AppController.m im GLPaint-Beispiel, das auf der iPhone-Entwicklerseite verfügbar ist.


    12

    In iOS 8.3 (möglicherweise früher) mit Swift ist es so einfach wie das Überschreiben der motionBeganoder motionEndedMethoden in Ihrem 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!")
        }
    }

    Hast Du es versucht? Ich würde davon ausgehen, dass dies ein wenig zusätzliche Arbeit erfordert, um dies zum Laufen zu bringen, wenn sich die App im Hintergrund befindet.
    nhgrif

    Nein .. wird bald .. aber wenn Sie das iPhone 6s bemerkt haben, hat es diese Funktion "Bildschirm aufwecken", bei der der Bildschirm für einige Zeit aufhellt, wenn Sie das Gerät abholen. Ich muss dieses Verhalten erfassen
    Nr. 5

    9

    Dies ist der grundlegende Delegatencode, den Sie benötigen:

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

    Stellen Sie auch den entsprechenden Code in der Schnittstelle ein. dh:

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


    Wie kann ich die Gerätebewegung genau wie ein Panorama erkennen?
    user2526811

    Wie kann man ein Verwackeln erkennen, wenn das iPhone nur in X-Richtung bewegt wird? Ich möchte kein Verwackeln in Y- oder Z-Richtung erkennen.
    Manthan


    7

    Fügen Sie die folgenden Methoden in die Datei ViewController.m ein, damit sie ordnungsgemäß funktionieren

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

    5

    Es tut mir leid, dies als Antwort und nicht als Kommentar zu veröffentlichen, aber wie Sie sehen, bin ich neu bei Stack Overflow und daher noch nicht seriös genug, um Kommentare zu veröffentlichen!

    Wie auch immer, ich möchte @cire als Zweites sicherstellen, dass der Ersthelferstatus festgelegt wird, sobald die Ansicht Teil der Ansichtshierarchie ist. Das Festlegen des Ersthelferstatus in Ihrer View Controller- viewDidLoadMethode funktioniert beispielsweise nicht. Und wenn Sie sich nicht sicher sind, ob es funktioniert, erhalten [view becomeFirstResponder]Sie einen Booleschen Wert, den Sie testen können.

    Ein weiterer Punkt: Sie können einen View-Controller verwenden, um das Shake-Ereignis zu erfassen, wenn Sie nicht unnötig eine UIView-Unterklasse erstellen möchten. Ich weiß, es ist nicht so viel Ärger, aber die Option ist immer noch da. Verschieben Sie einfach die Codefragmente, die Kendall in die UIView-Unterklasse eingefügt hat, in Ihren Controller und senden Sie die becomeFirstResponderund resignFirstResponder-Nachrichten an selfanstelle der UIView-Unterklasse.


    Dies gibt keine Antwort auf die Frage. Um einen Autor zu kritisieren oder um Klarstellung zu bitten, hinterlassen Sie einen Kommentar unter seinem Beitrag.
    Alex Salauyou

    @ SashaSalauyou Ich habe im allerersten Satz hier klar gesagt, dass ich nicht genug Ruf hatte, um zu der Zeit einen Kommentar zu
    schreiben

    Es tut uns leid. Es ist hier möglich, dass eine 5-jährige Antwort in die Überprüfungswarteschlange
    gelangt.

    5

    Zunächst einmal weiß ich, dass dies ein alter Beitrag ist, aber er ist immer noch relevant, und ich stellte fest, dass die beiden Antworten mit den höchsten Stimmen den Shake nicht so früh wie möglich erkannt haben . So geht's:

    1. Verknüpfen Sie CoreMotion in den Erstellungsphasen des Ziels mit Ihrem Projekt: CoreMotion
    2. In Ihrem ViewController:

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

    4

    Die einfachste Lösung besteht darin, ein neues Stammfenster für Ihre Anwendung abzuleiten:

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

    Dann in Ihrem Bewerbungsdelegierten:

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

    Wenn Sie ein Storyboard verwenden, ist dies möglicherweise schwieriger. Ich kenne den Code, den Sie im Anwendungsdelegierten benötigen, nicht genau.


    Und ja, Sie können Ihre Basisklasse im @implementationseit Xcode 5
    ableiten

    Das funktioniert, ich benutze es in mehreren Apps. Also nicht sicher, warum es abgelehnt wurde.
    mxcl

    Lief wie am Schnürchen. Falls Sie sich fragen, können Sie die Fensterklasse wie folgt
    Edu

    Funktioniert dies, wenn sich die App im Hintergrund befindet? Wenn nicht eine andere Idee, wie man im Hintergrund erkennt?
    Nr. 5

    Dies wird wahrscheinlich funktionieren, wenn Sie einen Hintergrundmodus eingestellt haben.
    mxcl

    1

    Verwenden Sie dazu einfach diese drei Methoden

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

    Einzelheiten können Sie über ein komplettes Beispielcode überprüfen dort


    1

    Eine Swiftease-Version basierend auf der allerersten Antwort!

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

    -1

    Um diese App-weit zu aktivieren, habe ich eine Kategorie in UIWindow erstellt:

    @implementation UIWindow (Utils)
    
    - (BOOL)canBecomeFirstResponder
    {
        return YES;
    }
    
    - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
    {
        if (motion == UIEventSubtypeMotionShake) {
            // Do whatever you want here...
        }
    }
    
    @end
    Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
    Licensed under cc by-sa 3.0 with attribution required.