Quiero reaccionar 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?
Quiero reaccionar 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?
Respuestas:
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 desea como primer respondedor para recibir los mensajes de evento de sacudida. 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 cuando se asigna un ver).
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.
motionEnded
antes de que la sacudida se haya detenido. Entonces, usando este enfoque, obtienes una serie desarticulada de batidos cortos en lugar de uno largo. La otra respuesta funciona mucho mejor en este caso.
[super respondsToSelector:
no hará lo que quieras, ya que es equivalente a llamar [self respondsToSelector:
y volverá YES
. Lo que necesita es [[ShakingView superclass] instancesRespondToSelector:
.
Desde 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 detiene la sacudida.
motionBegan
y los motionEnded
eventos no son muy precisos o precisos en términos de detectar el inicio y el final exactos de un batido. Este enfoque le permite ser tan preciso como desee.
Finalmente lo hice funcionar usando ejemplos de código de este Tutorial del Administrador de Deshacer / Rehacer .
Esto es exactamente lo que debes hacer:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
application.applicationSupportsShakeToEdit = YES;
[window addSubview:viewController.view];
[window makeKeyAndVisible];
}
-(BOOL)canBecomeFirstResponder {
return YES;
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated {
[self resignFirstResponder];
[super viewWillDisappear:animated];
}
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake)
{
// your code
}
}
applicationSupportsShakeToEdit
es YES
.
[self resignFirstResponder];
antes [super viewWillDisappear:animated];
? Esto parece peculiar.
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 ocurriera una sacudida. 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
(siéntase libre de 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 de su elección. Guarda el archivo.
Ahora, si usa un MainWindow.xib (material de plantilla Xcode de stock), entre allí y cambie la clase de su objeto Window de UIWindow a MotionWindow o como lo llame. Guarda el xib. Si configura UIWindow mediante programación, use su nueva clase Window allí.
Ahora su aplicación está utilizando la clase especializada UIWindow . Donde quiera que te informen sobre un batido, ¡regístrate para recibir notificaciones! Me gusta esto:
[[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 está "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 haya terminado de responder a la notificación original.
Además: puede elegir salir de motionBegan vs. 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 utilizo 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 muy bien juntos! Sin embargo, 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 alimentaria?
Encontré esta publicación buscando 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 de sacudida" para desencadenarse. 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);
}
PD: he configurado el intervalo de actualización a 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 de 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];
}
Establezca también en el código apropiado en la interfaz. es decir:
@interface MyViewController: UIViewController <UIPickerViewDelegate, UIPickerViewDataSource, UIAccelerometerDelegate>
Mira el ejemplo de GLPaint.
http://developer.apple.com/library/ios/#samplecode/GLPaint/Introduction/Intro.html
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, secundo a @cire para asegurarme de establecer el primer estado de respuesta una vez que la vista es parte de la jerarquía de vistas. Por lo tanto, establecer el estado del primer respondedor en su viewDidLoad
método de controladores de vista no funcionará, por ejemplo. Y si no está seguro de si está funcionando, le [view becomeFirstResponder]
devuelve un valor booleano que puede probar.
Otro punto: puede usar un controlador de vista para capturar el evento de sacudida si no desea crear una subclase UIView innecesariamente. Sé que no es tanta 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 en self
lugar de la subclase UIView.
En primer lugar, sé que esta es una publicación antigua, pero aún es relevante, y descubrí que las dos respuestas mejor votadas no detectaron la sacudida lo antes posible . Asi es como se hace:
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 aplicación delegado:
- (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 sé con precisión el código que necesitará en el delegado de la aplicación.
@implementation
puesto Xcode 5.
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 consultar un código de ejemplo completo 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
viewDidAppear
lugar deviewWillAppear
. No estoy seguro de por qué; ¿Quizás la vista debe ser visible antes de poder hacer lo que sea para comenzar a recibir los eventos de sacudida?