Как я могу обнаружить, когда кто-то трясет iPhone?


Я хочу реагировать, когда кто-то трясет iPhone. Меня не особенно волнует, как они его трясут, только то, что он энергично размахивал в течение доли секунды. Кто-нибудь знает, как это обнаружить?

16 336

16 ответов:

в 3.0 теперь есть более простой способ-подключаться к новым событиям движения.

главный трюк заключается в том, что вам нужно иметь некоторый UIView (не UIViewController), который вы хотите в качестве firstResponder получать сообщения о событиях shake. Вот код, который вы можете использовать в любом UIView, чтобы получить события 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

вы можете легко преобразовать любой UIView (даже системные представления) в представление, которое может получить событие shake, просто подклассировав представление только этими методами (а затем выбор этого нового типа вместо базового типа В IB или его использование при выделении представления).

в контроллере вида, вы хотите установить этот вид, чтобы стать первым ответчиком:

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

Не забывайте, что если у вас есть другие представления, которые становятся первым ответчиком от действий пользователя (например, строка поиска или поле ввода текста), вам также необходимо восстановить статус первого ответчика shaking view, когда другой вид уходит в отставку!

этот метод работает, даже если вы установите applicationSupportsShakeToEdit to NO.

от меня 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

histeresis предотвращает событие встряхивания от запуска несколько раз, пока пользователь не остановит встряхивание.

я наконец-то заставил его работать, используя примеры кода из этого Отменить / Повторить Менеджер Учебник.
Это именно то, что вам нужно сделать:

  • установить applicationSupportsShakeToEdit свойство в делегате приложения:
  • 
        - (void)applicationDidFinishLaunching:(UIApplication *)application {
    
            application.applicationSupportsShakeToEdit = YES;
    
            [window addSubview:viewController.view];
            [window makeKeyAndVisible];
    }
    

  • Добавить/Переопределить canBecomeFirstResponder,viewDidAppear: и viewWillDisappear: методы в контроллере вид:
  • 
    -(BOOL)canBecomeFirstResponder {
        return YES;
    }
    
    -(void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
        [self becomeFirstResponder];
    }
    
    - (void)viewWillDisappear:(BOOL)animated {
        [self resignFirstResponder];
        [super viewWillDisappear:animated];
    }
    

  • добавить motionEnded метод для вашего контроллера вида:
  • 
    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
    {
        if (motion == UIEventSubtypeMotionShake)
        {
            // your code
        }
    }
    

    во-первых, Кендалл 10 июля ответ на месте.

    сейчас ... Я хотел сделать что-то подобное (в iPhone OS 3.0+), только в моем случае я хотел, чтобы это было приложение, чтобы я мог предупредить различные части приложения, когда произошло встряхивание. Вот что я в итоге сделал.

    во-первых, я подкласса UIWindow. Это очень просто. Создайте новый файл класса с интерфейсом, таким как MotionWindow : UIWindow (не стесняйтесь выбрать свой собственный, естественно). Добавьте такой метод, как Итак:

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

    изменить @"DeviceShaken" к имени уведомления по вашему выбору. Сохранить файл.

    теперь, если вы используете MainWindow.xib (stock Xcode template stuff), зайдите туда и измените класс вашего объекта Window с UIWindow до MotionWindow или как ты там это назвал. Сохранить xib. Если вы настроили UIWindow программно, используйте свой новый класс окна там вместо этого.

    теперь ваше приложение использует специализированный UIWindow класса. Везде, где вы хотите, чтобы вам сказали о встряске, подпишитесь на них уведомления! Вот так:

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

    удалить себя в качестве наблюдателя:

    [[NSNotificationCenter defaultCenter] removeObserver:self];
    

    Я положил свой в viewWillAppear: и viewWillDisappear: когда речь идет о контроллерах вида. Убедитесь, что ваш ответ на событие shake знает, если он "уже выполняется" или нет. В противном случае, если устройство встряхнуть два раза подряд, вы будете есть небольшая пробка. Таким образом, вы можете игнорировать другие уведомления, пока вы действительно сделали отвечая на сообщения.

    кроме того: вы можете выбрать, чтобы кий от motionBegan и motionEnded. Это зависит от тебя. В моем случае, эффект всегда должен иметь место после устройство находится в состоянии покоя (по сравнению с тем, когда оно начинает трястись), поэтому я использую motionEnded. Попробуйте оба и посмотрите, какой из них имеет больше смысла ... или обнаружить / уведомить для обоих!

    один (любопытно?) наблюдение здесь: обратите внимание, что в этом коде нет никаких признаков управления первым ответчиком. Я только пробовал это с контроллерами представления таблицы до сих пор, и все, кажется, работает довольно хорошо вместе! Однако я не могу поручиться за другие сценарии.

    Kendall, et. Аль - кто-нибудь может сказать, почему это может быть так UIWindow подклассы? Это потому, что окно находится в верхней части пищевой цепи?

    я наткнулся на этот пост в поисках" тряской " реализации. ответ милленоми хорошо работал для меня, хотя я искал что-то, что требовало немного больше "тряски", чтобы вызвать. Я заменил логическое значение на int shakeCount. Я также переопределил метод L0AccelerationIsShaking () в Objective-C. Вы можете настроить количество встряхивания, необходимое для настройки ammount, добавленного в shakeCount. Я не уверен, что нашел оптимальные значения, но, похоже, это так работает хорошо до сих пор. Надеюсь, это кому-то поможет:

    - (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: Я установил интервал обновления до 1/15 секунды.

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

    вам нужно проверить акселерометр с помощью accelerometer: didAccelerate: метод, который является частью протокола UIAccelerometerDelegate и проверить, превышают ли значения порог для количества движения, необходимого для встряхивания.

    есть приличный пример кода в акселерометре: didAccelerate: метод прямо в нижней части AppController.m в Примере GLPaint, который доступен на сайте разработчика iPhone.

    в iOS 8.3 (возможно, раньше) с Swift, это так же просто, как переопределение motionBegan или motionEnded методы в контроллере вид:

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

    Это основной код делегата вам необходимо:

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

    также установите в соответствующем коде в интерфейсе. я.е:

    @interface MyViewController : UIViewController

    добавьте следующие методы в ViewController.M файл, его работа правильно

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

    Извините, что публикую это как ответ, а не комментарий, но, как вы можете видеть, я новичок в Stack Overflow, и поэтому я еще не достаточно авторитетен, чтобы публиковать комментарии!

    во всяком случае, я второй @cire о том, чтобы установить статус первого ответчика, как только представление является частью иерархии представлений. Поэтому установка статуса первого ответчика в контроллерах вида viewDidLoad метод не будет работать, например. И если вы не уверены, работает ли он [view becomeFirstResponder] возвращает логическое значение, которое вы можете тест.

    еще один момент: Вы можете используйте контроллер вида для захвата события shake, если вы не хотите создавать подкласс UIView без необходимости. Я знаю, что это не так много хлопот, но все же вариант есть. Просто переместите фрагменты кода, которые Кендалл поместил в подкласс UIView в свой контроллер и отправьте becomeFirstResponder и resignFirstResponder сообщения self вместо подкласса UIView.

    во-первых, я знаю, что это старый пост, но он по-прежнему актуален, и я обнаружил, что два самых высоких ответа проголосовали не обнаружить встряску как можно раньше. Вот как это сделать:

    1. свяжите CoreMotion с вашим проектом на этапах сборки цели: CoreMotion
    2. в вашем ViewController:

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

    самое простое решение-создать новое корневое окно для вашего приложения:

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

    затем в вашем делегате приложения:

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

    Если вы используете раскадровку, это может быть сложнее, я не знаю, какой код вам понадобится в делегате приложения точно.

    просто использовать эти три метода, чтобы сделать это

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

    для более подробной информации вы можете проверить полный пример кода здесь

    версия swiftease основана на самом первом ответе!

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

    чтобы включить это приложение в целом, я создал категорию на UIWindow:

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