Come posso rilevare quando qualcuno scuote un iPhone?


340

Voglio reagire quando qualcuno scuote l'iPhone. Non mi interessa particolarmente come lo scuotono, solo che è stato agitato vigorosamente per una frazione di secondo. Qualcuno sa come rilevarlo?

Risposte:


292

In 3.0, ora c'è un modo più semplice: agganciare i nuovi eventi di movimento.

Il trucco principale è che devi avere un UIView (non UIViewController) che desideri come firstResponder per ricevere i messaggi dell'evento shake. Ecco il codice che puoi utilizzare in qualsiasi UIView per ottenere eventi di agitazione:

@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

Puoi facilmente trasformare qualsiasi UIView (anche le viste di sistema) in una vista che può ottenere l'evento shake semplicemente eseguendo la sottoclasse della vista solo con questi metodi (e quindi selezionando questo nuovo tipo invece del tipo base in IB, o usandolo quando si alloca un Visualizza).

Nel controller di visualizzazione, si desidera impostare questa visualizzazione per diventare il primo risponditore:

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

Non dimenticare che se hai altre viste che diventano le prime risposte dalle azioni dell'utente (come una barra di ricerca o un campo di immissione testo) dovrai anche ripristinare lo stato di prima risposta della vista tremolante quando l'altra vista si dimette!

Questo metodo funziona anche se si imposta applicationSupportsShakeToEdit su NO.


5
Questo non ha funzionato abbastanza per me: dovevo sostituire il mio controller viewDidAppearinvece di viewWillAppear. Non sono sicuro del perché; forse la vista deve essere visibile prima di poter fare tutto ciò che fa per iniziare a ricevere gli eventi di vibrazione?
Kristopher Johnson,

1
Questo è più facile ma non necessariamente migliore. Se si desidera rilevare una vibrazione lunga e continua, questo approccio non è utile in quanto all'iPhone piace sparare motionEndedprima che la vibrazione si sia effettivamente fermata. Quindi usando questo approccio ottieni una serie sconnessa di brevi scosse invece di una lunga. L'altra risposta funziona molto meglio in questo caso.
aroth

3
@Kendall - UIViewControllers implementa UIResponder e fa parte della catena di responder. Anche la UIWindow più in alto è. developer.apple.com/library/ios/DOCUMENTATION/EventHandling/…
DougW,

1
[super respondsToSelector:non farà quello che vuoi, dal momento che equivale a chiamare [self respondsToSelector:che restituirà YES. Quello di cui hai bisogno è [[ShakingView superclass] instancesRespondToSelector:.
Vadim Yelagin,

2
Invece di usare: if (event.subtype == UIEventSubtypeMotionShake) puoi usare: if (motion == UIEventSubtypeMotionShake)
Hashim Akhtar

179

Dalla mia applicazione 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'isteresi impedisce che l'evento shake si attivi più volte fino a quando l'utente non interrompe il shake.


7
Nota Ho aggiunto una risposta di seguito che presenta un metodo più semplice per 3.0.
Kendall Helmstetter Gelner,

2
Cosa succede se lo agitano precisamente su un determinato asse?
giovedì

5
La risposta migliore, perché iOS 3.0 motionBegane gli motionEndedeventi non sono molto precisi o accurati in termini di rilevamento dell'esatto inizio e fine di una vibrazione. Questo approccio ti consente di essere preciso come desideri.
aroth

2
UIAccelerometer Accelerometro delegato : didAccelerate: è stato deprecato in iOS5.
Yantao Xie,

Questa altra risposta è migliore e più veloce per implementare stackoverflow.com/a/2405692/396133
Abramodj

154

Alla fine l'ho fatto funzionare usando esempi di codice di questo tutorial di Undo / Redo Manager .
Questo è esattamente ciò che devi fare:

  • Impostare la proprietà applicationSupportsShakeToEdit nel delegato dell'app:
  • 
        - (void)applicationDidFinishLaunching:(UIApplication *)application {
    
            application.applicationSupportsShakeToEdit = YES;
    
            [window addSubview:viewController.view];
            [window makeKeyAndVisible];
    }

  • Aggiungi / Sostituisci canBecomeFirstResponder , viewDidAppear: e viewWillDisappear: metodi nel View Controller:
  • 
    -(BOOL)canBecomeFirstResponder {
        return YES;
    }
    
    -(void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
        [self becomeFirstResponder];
    }
    
    - (void)viewWillDisappear:(BOOL)animated {
        [self resignFirstResponder];
        [super viewWillDisappear:animated];
    }

  • Aggiungi il metodo motionEnded al tuo controller di visualizzazione:
  • 
    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
    {
        if (motion == UIEventSubtypeMotionShake)
        {
            // your code
        }
    }

    Solo per aggiungere alla soluzione Eran Talmor: dovresti usare un controller di navigazione invece di un viewcontroller nel caso in cui tu abbia scelto una tale applicazione nel nuovo selettore di progetti.
    Rob,

    Ho provato a usare questo, ma a volte l'highyshake o il light shake si sono fermati
    vinothp

    Il passaggio 1 è ora ridondante, poiché il valore predefinito applicationSupportsShakeToEditè YES.
    DonVaughn,

    1
    Perché chiamare [self resignFirstResponder];prima [super viewWillDisappear:animated];? Questo sembra strano.
    JaredH,

    94

    Innanzitutto, la risposta del 10 luglio di Kendall è perfetta.

    Ora ... volevo fare qualcosa di simile (in iPhone OS 3.0+), solo nel mio caso lo volevo a livello di app in modo da poter avvisare varie parti dell'app quando si verificava una vibrazione. Ecco cosa ho finito per fare.

    Innanzitutto, ho eseguito la sottoclasse di UIWindow . Questo è facile. Crea un nuovo file di classe con un'interfaccia come MotionWindow : UIWindow(sentiti libero di scegliere il tuo, natch). Aggiungi un metodo in questo modo:

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

    Passa @"DeviceShaken"al nome della notifica che preferisci. Salva il file.

    Ora, se usi MainWindow.xib (materiale modello Xcode stock), vai lì e cambia la classe del tuo oggetto Window da UIWindow a MotionWindow o come lo hai chiamato. Salva lo xib. Se imposti UIWindow a livello di codice , usa invece la tua nuova classe Window.

    Ora la tua app utilizza la classe UIWindow specializzata . Ovunque tu voglia sentirti dire di una scossa, iscriviti per ricevere le notifiche! Come questo:

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

    Per rimuovere te stesso come osservatore:

    [[NSNotificationCenter defaultCenter] removeObserver:self];

    Ho messo il mio in viewWillAppear: e viewWillDisappear: per quanto riguarda i controller di visualizzazione. Assicurati che la tua risposta all'evento shake sappia se è "già in corso" oppure no. Altrimenti, se il dispositivo viene scosso due volte in successione, avrai un ingorgo. In questo modo puoi ignorare altre notifiche fino a quando non hai veramente risposto alla notifica originale.

    Inoltre: Si può scegliere di cue off di motionBegan vs. motionEnded . Tocca a voi. Nel mio caso, l'effetto deve sempre verificarsi dopo che il dispositivo è a riposo (rispetto a quando inizia a tremare), quindi utilizzo motionEnded . Prova entrambi e vedi quale ha più senso ... o rileva / notifica per entrambi!

    Un'altra osservazione (curiosa?) Qui: Notare che non c'è alcun segno della gestione del primo soccorritore in questo codice. Finora ci ho provato solo con i controller di visualizzazione tabella e tutto sembra funzionare abbastanza bene insieme! Non posso garantire altri scenari però.

    Kendall, et. al - qualcuno può parlare del perché questo potrebbe essere così per le sottoclassi di UIWindow ? È perché la finestra è in cima alla catena alimentare?


    1
    Questo è ciò che è così strano. Funziona così com'è. Spero non sia un caso di "lavorare nonostante se stesso"! Per favore fatemi sapere cosa scoprite nei vostri test.
    Joe D'Andrea,

    Preferisco questo alla sottoclasse di una UIView normale, soprattutto perché ne hai bisogno solo per esistere in un posto, quindi inserire una sottoclasse di finestre sembra una scelta naturale.
    Shaggy Frog,

    Grazie jdandrea, io uso il tuo metodo ma tremando ottenendo più volte !!! voglio solo che gli utenti possano agitare una volta (dal punto di vista che l'agitazione fa lì) ...! come posso gestirlo? implemento sth in questo modo. implemento il mio codice in questo modo: i-phone.ir/forums/uploadedpictures/… ---- ma il problema è che l'app scuote solo 1 volta per tutta la vita dell'applicazione! non solo vista
    Momi

    1
    Momeks: se è necessaria la notifica una volta che il dispositivo è a riposo (post-vibrazione), utilizzare motionEnded anziché motionBegan. Dovrebbe farlo!
    Joe D'Andrea,

    1
    @Joe D'Andrea - Funziona così com'è perché UIWindow è nella catena di responder. Se qualcosa di più in alto nella catena intercettasse e consumasse questi eventi, non li riceverebbe. developer.apple.com/library/ios/DOCUMENTATION/EventHandling/…
    DougW,

    34

    Mi sono imbattuto in questo post in cerca di un'implementazione "scuotente". La risposta di Millenomi ha funzionato bene per me, anche se stavo cercando qualcosa che richiedesse un po 'più di "azione tremolante" per innescare. Ho sostituito il valore booleano con un shakeCount int. Ho anche reimplementato il metodo L0AccelerationIsShaking () in Objective-C. È possibile modificare la quantità di agitazione richiesta modificando la quantità aggiunta a shakeCount. Non sono sicuro di aver ancora trovato i valori ottimali, ma sembra che stia funzionando bene finora. Spero che questo aiuti qualcuno:

    - (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: ho impostato l'intervallo di aggiornamento su 1/15 di secondo.

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

    Come posso rilevare il movimento del dispositivo proprio come panorama?
    user2526811

    Come identificare la vibrazione se l'iPhone viene spostato solo nella direzione X? Non voglio rilevare la vibrazione se nella direzione Y o Z.
    Manthan,

    12

    È necessario controllare l'accelerometro tramite accelerometro: didAccelerate: metodo che fa parte del protocollo UIAccelerometerDelegate e verificare se i valori superano una soglia per la quantità di movimento necessaria per uno shake.

    Nell'accelerometro esiste un codice di esempio decente: didAccelerate: metodo proprio nella parte inferiore di AppController.m nell'esempio GLPaint, disponibile sul sito degli sviluppatori di iPhone.


    12

    In iOS 8.3 (forse in precedenza) con Swift, è semplice come sovrascrivere i metodi motionBegano motionEndednel controller della vista:

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

    Hai provato? Suppongo che per farlo funzionare quando l'app è in background richiederebbe un po 'di lavoro extra.
    nhgrif,

    No ... presto .. ma se hai notato iPhone 6s ha questa funzione "pick up to wake screen", in cui lo schermo si illumina per qualche tempo, quando raccogli il dispositivo. Devo catturare questo comportamento
    nr5,

    9

    Questo è il codice delegato di base necessario:

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

    Impostare anche il codice appropriato nell'interfaccia. vale a dire:

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


    Come posso rilevare il movimento del dispositivo proprio come panorama?
    user2526811

    Come identificare la vibrazione se l'iPhone viene spostato solo nella direzione X? Non voglio rilevare la vibrazione se nella direzione Y o Z.
    Manthan,


    7

    Aggiungi i seguenti metodi nel file ViewController.m, funziona correttamente

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

    Mi dispiace pubblicare questo come una risposta piuttosto che come un commento, ma come puoi vedere sono nuovo di Stack Overflow e quindi non sono ancora abbastanza affidabile per pubblicare commenti!

    Ad ogni modo, secondo @cire, mi assicuro di impostare lo stato del primo risponditore quando la vista fa parte della gerarchia della vista. Pertanto, ad esempio, l'impostazione dello stato del primo risponditore nel viewDidLoadmetodo dei controller della vista non funzionerà. E se non sei sicuro che funzioni, [view becomeFirstResponder]ti restituisce un valore booleano che puoi testare.

    Un altro punto: puoi utilizzare un controller di visualizzazione per acquisire l'evento shake se non si desidera creare inutilmente una sottoclasse UIView. So che non è così tanto fastidio, ma l'opzione è ancora lì. Basta spostare i frammenti di codice che Kendall ha inserito nella sottoclasse UIView nel controller e inviare i messaggi becomeFirstRespondere invece della sottoclasse UIView.resignFirstResponderself


    Questo non fornisce una risposta alla domanda. Per criticare o richiedere chiarimenti a un autore, lascia un commento sotto il suo post.
    Alex Salauyou,

    @SashaSalauyou Ho affermato chiaramente nella prima frase qui che non avevo abbastanza reputazione per pubblicare un commento in quel momento
    Newtz

    spiacente. Qui è possibile che la risposta a 5 anni
    arrivi

    5

    Prima di tutto, so che si tratta di un vecchio post, ma è ancora pertinente e ho scoperto che le due risposte più votate non hanno rilevato il tremito il prima possibile . Così è come si fa:

    1. Collega CoreMotion al tuo progetto nelle fasi di costruzione del target: CoreMotion
    2. Nel tuo ViewController:

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

    4

    La soluzione più semplice è derivare una nuova finestra di root per la tua applicazione:

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

    Quindi nel delegato dell'applicazione:

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

    Se stai usando uno Storyboard, questo potrebbe essere più complicato, non conosco esattamente il codice di cui avrai bisogno nel delegato dell'applicazione.


    E sì, puoi derivare la tua classe di base @implementationdal Xcode 5.
    mxcl

    Funziona, lo uso in diverse app. Quindi non sono sicuro del perché sia ​​stato votato verso il basso.
    mxcl

    ha funzionato come un fascino. Nel caso ti stia chiedendo, puoi ignorare la classe della finestra in questo modo: stackoverflow.com/a/10580083/709975
    Edu,

    Funzionerà quando l'app è in background? Se non hai altre idee su come rilevare in background?
    nr5

    Questo probabilmente funzionerà se hai impostato una modalità di sfondo.
    mxcl,

    1

    Usa questi tre metodi per farlo

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

    per i dettagli è possibile controllare un codice di esempio completo laggiù


    1

    Una versione rapida basata sulla prima risposta!

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

    -1

    Per abilitare questa app, ho creato una categoria su UIWindow:

    @implementation UIWindow (Utils)
    
    - (BOOL)canBecomeFirstResponder
    {
        return YES;
    }
    
    - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
    {
        if (motion == UIEventSubtypeMotionShake) {
            // Do whatever you want here...
        }
    }
    
    @end
    Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
    Licensed under cc by-sa 3.0 with attribution required.