Pregunta

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

¿Fue útil?

Solución

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

El truco principal es que necesita tener algo de 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 de sacudida:

@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 subclasificando la vista solo con estos métodos (y luego seleccionando este nuevo tipo en lugar del tipo base en IB, o usándolo al asignar una vista).

En el controlador de vista, desea configurar esta vista para convertirse 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 necesitará 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.

Otros consejos

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 shake se active varias veces hasta que el usuario detiene el shake.

Finalmente lo hice funcionar usando ejemplos de código de este Tutorial para deshacer / rehacer administrador .
Esto es exactamente lo que debe 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 / Anular canBecomeFirstResponder , viewDidAppear: y < i> viewWillDisappear: métodos 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 del 10 de julio de Kendall es acertada.

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

    Primero, subclasifiqué UIWindow . Esto es fácil, fácil. Cree un nuevo archivo de clase con una interfaz como MotionWindow : UIWindow (no dude en elegir el suyo, natch). Agregue 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];
        }
    }
    

    Cambie @"DeviceShaken" al nombre de notificación que elija. Guarde el archivo.

    Ahora, si usa MainWindow.xib (material de plantilla Xcode de stock), entre allí y cambie la clase de su objeto Window de UIWindow a MotionWindow o lo que sea lo llamaste 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 te informen sobre un batido, ¡regístrate para recibir notificaciones! Así:

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

    Para eliminarse como observador:

    [[NSNotificationCenter defaultCenter] removeObserver:self];
    

    Puse 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 es & Quot; ya está en progreso & Quot; 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 haya terminado de responder a la notificación original.

    Además: puede optar por desactivar motionBegan frente a motionEnded . 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), por lo que uso motionEnded . Pruebe ambos y vea cuál tiene más sentido ... ¡o detecte / notifique a ambos!

    Una observación más (¿curiosa?) aquí: observe que no hay signos de administración de primeros respondedores en este código. ¡Hasta ahora solo he probado esto con Table View Controllers y todo parece funcionar bastante bien juntos! Sin embargo, no puedo responder por otros escenarios.

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

    Encontré esta publicación buscando un " temblando " implementación. 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 He reemplazado el valor booleano con un int shakeCount. También reimplementé el método L0AccelerationIsShaking () en Objective-C. Puede ajustar la cantidad de temblor requerida 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);
    }
    

    PS: Establecí 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: didAccelerate: método que forma parte del protocolo UIAccelerometerDelegate y verificar si los valores superan un umbral para la cantidad de movimiento necesaria para una sacudida.

    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 GLPaint que está disponible en el sitio para desarrolladores 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];   
        }
    

    Configure también en el código apropiado en la interfaz. es decir:

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

    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 
         }
    

    Lamento publicar esto como una respuesta en lugar de un comentario, pero como puede ver, soy nuevo en Stack Overflow y, por lo tanto, todavía no tengo la reputación suficiente para publicar comentarios.

    De todos modos, apoyo a @cire para asegurarme de establecer el estado del primer respondedor una vez que la vista sea parte de la jerarquía de vistas.Entonces, configurar el estado del primer respondedor en sus controladores de vista viewDidLoad El método no funcionará, por ejemplo.Y si no está seguro de si está funcionando [view becomeFirstResponder] te devuelve un booleano que puedes probar.

    Otro punto:tú poder use un controlador de vista para capturar el evento de vibración 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 el becomeFirstResponder y resignFirstResponder mensajes a self en lugar de la subclase UIView.

    En primer lugar, sé que esta es una publicación antigua, pero sigue siendo relevante, y descubrí que las dos respuestas mejor votadas no detectaron el batido lo antes posible . Así es como se hace:

    1. Enlace CoreMotion a su proyecto en las fases de compilación del objetivo: 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 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 aplicación:

    - (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 & # 8217; no sé con precisión el código que necesitará en el delegado de la aplicación.

    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 detalles, puede consultar un código de ejemplo completo en allí

    ¡Una versión rápida 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
    
    Licenciado bajo: CC-BY-SA con atribución
    No afiliado a StackOverflow
    scroll top