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


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

Author: James Webster, 2008-09-30

16 answers

En la versión 3.0, ahora hay una forma más fácil: engancharse a los nuevos eventos de movimiento.

El truco principal es que necesita tener algún UIView (no UIViewController) que desee 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 puede obtener el evento shake simplemente subclasificando la vista con solo estos métodos (y luego seleccionando este nuevo escriba en lugar del tipo base en IB, o usándolo al asignar una vista).

En el controlador de vista, desea configurar esta vista para que se convierta en el primer respondedor:

- (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 el primer respondedor de las acciones del usuario (como una barra de búsqueda o un campo de entrada de texto), también deberá restaurar el estado del primer respondedor de la vista temblorosa cuando la otra vista renuncie.

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

 292
Author: Kendall Helmstetter Gelner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-28 20:42:14

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

La histéresis evita que el evento de sacudida se active varias veces hasta que el usuario detenga la sacudida.

 179
Author: millenomi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-01 20:43:58

Finalmente lo hice funcionar usando ejemplos de código de este Tutorial del Administrador de Deshacer/Rehacer .
Esto es exactamente lo que necesitas hacer:

  • Establecer el Apoyo a la aplicacióneditar propiedad en el Delegado de la Aplicación:
  • 
        - (void)applicationDidFinishLaunching:(UIApplication *)application {
    
            application.applicationSupportsShakeToEdit = YES;
    
            [window addSubview:viewController.view];
            [window makeKeyAndVisible];
    }
    

  • Añadir/Reemplazar canBecomeFirstResponder, viewDidAppear: y viewWillDisappear: métodos que, en su Opinión Controlador:
  • 
    -(BOOL)canBecomeFirstResponder {
        return YES;
    }
    
    -(void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
        [self becomeFirstResponder];
    }
    
    - (void)viewWillDisappear:(BOOL)animated {
        [self resignFirstResponder];
        [super viewWillDisappear:animated];
    }
    

  • Añadir el motionEnded método para su Controlador de vista:
  • 
    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
    {
        if (motion == UIEventSubtypeMotionShake)
        {
            // your code
        }
    }
    
     154
    Author: Eran Talmor,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2011-10-28 20:43:05

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

    Ahora ... Quería hacer algo similar (en iPhone OS 3.0+), solo que en mi caso lo quería en toda la aplicación para poder alertar varias partes de la aplicación cuando se produjo una sacudida. Esto es lo que terminé haciendo.

    Primero, subclase UIWindow. Esto es pan comido. Cree un nuevo archivo de clase con una interfaz como MotionWindow : UIWindow (siéntase libre de elegir el suyo propio, natch). Añade un método como este:

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

    Cambiar @"DeviceShaken" a la nombre de notificación de su elección. Guarde el archivo.

    Ahora, si usa una ventana principal.xib (stock Xcode template stuff), entra ahí y cambia la clase de tu objeto Window de UIWindowa MotionWindow o como lo llames. Guarda el xib. Si configura UIWindow mediante programación, use su nueva clase Window allí en su lugar.

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

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

    Para quitarse a sí mismo como un observador:

    [[NSNotificationCenter defaultCenter] removeObserver:self];
    

    Pongo el mío en viewWillAppear:y viewWillDisappear: en lo que respecta a los Controladores de vista. Asegúrese de que su respuesta al evento shake sepa si está "ya en progreso" o no. De lo contrario, si el dispositivo se agita dos veces seguidas, tendrá un pequeño atasco de tráfico. De esta manera, puede ignorar otras notificaciones hasta que realmente haya terminado de responder a la original notificación.

    También: Usted puede elegir cue fuera de motionBegan vs motionEnded. Depende de ti. En mi caso, el efecto siempre tiene que tener lugar después de el dispositivo está en reposo (vs.cuando comienza a temblar), por lo que uso motionEnded. Pruebe ambos y vea cuál tiene más sentido ... o detectar / notificar para ambos!

    Uno más (curioso?) observación aquí: Observe que no hay señales de administración de primeros auxilios en este código. Sólo he intentado esto con Controladores de vista de tabla hasta ahora y todo parece funcionar muy bien juntos! Sin embargo, no puedo responder por otros escenarios.

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

     94
    Author: Joe D'Andrea,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2009-08-29 14:36:20

    Me encontré con este post en busca de una implementación "temblorosa". la respuesta de millenomi funcionó bien para mí, aunque estaba buscando algo que requiriera un poco más de "acción sacudida" para activarlo. He reemplazado a valor booleano con un int shakeCount. También reimplementé el método L0AccelerationIsShaking () en Objective-C. Puedes ajustar la cantidad de agitación requerida ajustando la cantidad de agitación agregada a shakeCount. No estoy seguro de haber encontrado los valores óptimos todavía, pero parece estar funcionando bueno, 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);
    }
    

    PS: He establecido el intervalo de actualización a 1/15 de segundo.

    [[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0 / 15)];
    
     34
    Author: ,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2008-11-10 17:14:52

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

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

     12
    Author: Dave Verwer,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2008-09-29 21:17:44

    En iOS 8.3 (quizás anterior) 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!")
        }
    }
    
     11
    Author: nhgrif,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2015-04-20 22:39:56

    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 establezca el código correspondiente en la Interfaz. i. e:

    @interfaz MyViewController: UIViewController

     9
    Author: Benjamin Ortuzar,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2011-10-04 07:00:53
     7
    Author: camflan,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2011-10-28 20:44:09

    Agregue los siguientes métodos en ViewController.m archivo, está funcionando 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 
         }
    
     7
    Author: Himanshu Mahajan,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2012-10-15 12:11:16

    Lo sentimos para publicar esto como una respuesta en lugar de un comentario, pero como se puede ver soy nuevo en la pila de Desbordamiento y por lo que todavía no soy lo suficientemente buena reputación para publicar comentarios!

    De todos modos, secundo @cire para asegurarme 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 del primer respondedor en su método view controllers viewDidLoad no funcionará, por ejemplo. Y si no está seguro de si está funcionando [view becomeFirstResponder] le devuelve un booleano que puede prueba.

    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 a su controlador y envíe los mensajes becomeFirstResponder y resignFirstResponder a self en lugar de la subclase UIView.

     5
    Author: Newtz,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2011-10-28 20:44:49

    En primer lugar, sé que este es un post antiguo, pero sigue siendo relevante, y descubrí que las dos respuestas más votadas no detectaron el temblor lo antes posible . Así es como se hace:

    1. Enlaza CoreMotion con tu proyecto en las fases de compilación del objetivo: CoreMotion
    2. En tu ViewController:

      - (BOOL)canBecomeFirstResponder {
          return YES;
      }
      
      - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
      {
          if (motion == UIEventSubtypeMotionShake) {
              // Shake detected.
          }
      }
      
     4
    Author: Dennis,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2015-03-13 14:59:13

    La solución más fácil 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 su delegado de solicitud:

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

    Si está utilizando un guion gráfico, esto puede ser más complicado, no conozco el código que necesitará en el delegado de la aplicación precisamente.

     3
    Author: mxcl,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2014-07-12 12:46:34

    Simplemente use 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 obtener más detalles, puede consultar un código de ejemplo completo en allí

     1
    Author: Mashhadi,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2013-02-21 09:30:47

    Una versión swiftease basada en la primera respuesta!

    override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
        if ( event?.subtype == .motionShake )
        {
            print("stop shaking me!")
        }
    }
    
     1
    Author: user3069232,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2017-09-10 07:44:21

    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
    
     -1
    Author: Mike,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2017-09-15 19:45:23