¿Cómo puedo detectar cuando alguien sacude un iPhone?

Quiero reactjsr cuando alguien sacude el iPhone. No me importa particularmente cómo lo sacuden, solo que se agitó vigorosamente durante una fracción de segundo. ¿Alguien sabe cómo detectar esto?

En 3.0, ahora hay una manera más fácil: engancharse a los nuevos eventos de movimiento.

El truco principal es que necesitas tener algún UIView (no UIViewController) que desees como firstResponder para recibir los mensajes del evento shake. Aquí está el código que puede usar en cualquier UIView para obtener eventos 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 

Puede transformar fácilmente cualquier UIView (incluso vistas del sistema) en una vista que pueda obtener el evento shake simplemente subclasando la vista con solo estos métodos (y luego seleccionando este nuevo tipo en lugar del tipo base en IB, o utilizándolo al asignar un ver).

En el controlador de vista, desea configurar esta vista para que se convierta en primera respuesta:

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

No olvide que si tiene otras vistas que se convierten en primera respuesta de las acciones de los usuarios (como una barra de búsqueda o un campo de entrada de texto) también deberá restaurar el estado de la primera respuesta de la vista temblorosa cuando la otra vista termine.

Este método funciona incluso si establece applicationSupportsShakeToEdit en NO.

De mi aplicación 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  { 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 

La histeresis impide que el evento de sacudida se dispare varias veces hasta que el usuario detiene el batido.

Finalmente lo hice funcionar usando ejemplos de código de este Tutorial de Undo / Redo Manager .
Esto es exactamente lo que debes hacer:

  • Establezca la propiedad applicationSupportsShakeToEdit en el delegado de la aplicación:
  •  - (void)applicationDidFinishLaunching:(UIApplication *)application { application.applicationSupportsShakeToEdit = YES; [window addSubview:viewController.view]; [window makeKeyAndVisible]; } 
  • Agregar / Reemplazar los métodos canBecomeFirstResponder , viewDidAppear: y viewWillDisappear: en su Controlador de Vista:
  •  -(BOOL)canBecomeFirstResponder { return YES; } -(void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [self becomeFirstResponder]; } - (void)viewWillDisappear:(BOOL)animated { [self resignFirstResponder]; [super viewWillDisappear:animated]; } 
  • Agregue el método motionEnded a su Controlador de Vista:
  •  - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event { if (motion == UIEventSubtypeMotionShake) { // your code } } 

    Primero, la respuesta de Kendall del 10 de julio es inmediata.

    Ahora … Quería hacer algo similar (en iPhone OS 3.0+), solo que en mi caso lo quería para toda la aplicación, así podía alertar varias partes de la aplicación cuando ocurría un batido. Esto es lo que terminé haciendo.

    Primero, subclassed UIWindow . Esto es fácil peasy. Cree un nuevo archivo de clase con una interfaz como MotionWindow : UIWindow (no dude en elegir la suya propia, natch). Agrega un método como ese:

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

    Cambie @"DeviceShaken" al nombre de notificación de su elección. Guarda el archivo.

    Ahora, si usa MainWindow.xib (material de la plantilla de Xcode), entre allí y cambie la clase de su objeto Window de UIWindow a MotionWindow o como lo llame. Guarde el xib. Si configura UIWindow programmatically, use su nueva clase Window allí.

    Ahora su aplicación está utilizando la clase especializada UIWindow . ¡Donde quiera que le digan sobre un batido, regístrese para recibir notificaciones! Me gusta esto:

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

    Para eliminarte como observador:

     [[NSNotificationCenter defaultCenter] removeObserver:self]; 

    Puse el mío a la vista WillAppear: y viewWillDisappear: donde se ven los controladores de vista. Asegúrese de que su respuesta al evento shake sepa si está “en progreso” o no. De lo contrario, si el dispositivo se agita dos veces seguidas, tendrá un atasco de tráfico. De esta forma, puede ignorar otras notificaciones hasta que haya terminado de responder a la notificación original.

    Además: puede elegir indiciarse de movimientoBegan contra movimientoEnded . Tu decides. En mi caso, el efecto siempre debe tener lugar después de que el dispositivo está en reposo (frente a cuando comienza a temblar), entonces uso motionEnded . Pruebe ambos y vea cuál tiene más sentido … o ¡detecte / notifique para ambos!

    Una observación más (¿curioso?) Aquí: fíjese que no hay señales de gestión de primeros respondedores en este código. ¡Solo he intentado esto con Table View Controllers hasta ahora y todo parece funcionar bastante bien! Aunque no puedo responder por otros escenarios.

    Kendall, et. ¿Alguien puede hablar de por qué esto podría ser así para las subclases de UIWindow ? ¿Es porque la ventana está en la parte superior de la cadena alimenticia?

    Encontré esta publicación buscando una implementación de “sacudida”. La respuesta de millenomi funcionó bien para mí, aunque estaba buscando algo que requiriera un poco más de “acción de sacudida” para disparar. Reemplacé al valor booleano con un int shakeCount. También reimplementé el método L0AccelerationIsShaking () en Objective-C. Puede ajustar la cantidad de temblores requeridos ajustando la cantidad agregada a shakeCount. No estoy seguro de haber encontrado los valores óptimos todavía, pero parece estar funcionando bien hasta ahora. Espero que esto ayude a alguien:

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

    PD: He configurado el intervalo de actualización en 1/15 de segundo.

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

    Debe verificar el acelerómetro a través del acelerómetro: método didAccelerate: que forma parte del protocolo UIAccelerometerDelegate y comprobar si los valores superan un umbral para la cantidad de movimiento necesaria para un batido.

    Hay un código de muestra decente en el acelerómetro: didAccelerate: método justo en la parte inferior de AppController.m en el ejemplo de GLPaint que está disponible en el sitio del desarrollador de iPhone.

    En iOS 8.3 (quizás antes) con Swift, es tan simple como anular los métodos motionBegan o motionEnded en su controlador de 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!") } } 

    Este es el código de delegado básico que necesita:

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

    También configure el en el código apropiado en la interfaz. es decir:

    @interface MyViewController: UIViewController

    Agregue los siguientes métodos en el archivo ViewController.m, funciona correctamente

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

    Perdón por publicar esto como una respuesta en lugar de un comentario, pero como pueden ver, soy nuevo en Stack Overflow y por eso aún no soy lo suficientemente respetable como para publicar comentarios.

    De todos modos, en segundo lugar, le pido a @Cire que se asegure de establecer el estado del primer respondedor una vez que la vista sea parte de la jerarquía de la vista. Por lo tanto, establecer el estado de la primera respuesta en los controladores de vista viewDidLoad método viewDidLoad no funcionará, por ejemplo. Y si no está seguro de si está funcionando [view becomeFirstResponder] devuelve un booleano que puede probar.

    Otro punto: puede usar un controlador de vista para capturar el evento shake si no desea crear una subclase UIView innecesariamente. Sé que no es mucha molestia, pero aún así la opción está ahí. Simplemente mueva los fragmentos de código que Kendall puso en la subclase UIView en su controlador y envíe los mensajes becomeFirstResponder y resignFirstResponder a self lugar de a la subclase UIView.

    En primer lugar, sé que esta es una publicación anterior, pero sigue siendo relevante, y encontré que las dos respuestas más votadas no detectaron el temblor lo antes posible . Asi es como se hace:

    1. Enlace CoreMotion a su proyecto en las fases de construcción del objective: CoreMotion
    2. En su ViewController:

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

    La solución más sencilla es derivar una nueva ventana raíz para su aplicación:

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

    Luego en tu aplicación delegada:

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

    Si está utilizando un Storyboard, esto puede ser más complicado, no conozco el código que necesitará en la aplicación delegada con precisión.

    Solo usa estos tres métodos para hacerlo

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

    para más detalles, puede verificar un código de ejemplo completo allí

    ¡Una versión de Swiftease basada en la primera respuesta!

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

    Para habilitar esta aplicación, creé una categoría en UIWindow:

     @implementation UIWindow (Utils) - (BOOL)canBecomeFirstResponder { return YES; } - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event { if (motion == UIEventSubtypeMotionShake) { // Do whatever you want here... } } @end