Вопрос

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

Это было полезно?

Решение

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

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

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

Этот метод работает, даже если вы установили для applicationSupportsShakeToEdit значение NO.

Другие советы

От моего Измельчитель кубиков применение:

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

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

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

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

  • Добавление /Переопределение Может стать первым ответчиком, viewDidAppear - вид, который появляется: и Просмотр исчезнет: методы в вашем контроллере представления:
  • 
    -(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
        }
    }
    

    Во-первых, ответ Кендалла от 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 (стандартный шаблон Xcode), зайдите туда и измените класс вашего объекта Window с UIWindow ( Пользовательское окно ) Для Окно движения или как бы вы это ни называли.Спасите xib.Если вы настроите UIWindow ( Пользовательское окно ) программно используйте вместо этого свой новый класс Window.

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

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

    Отстраниться от роли наблюдателя:

    [[NSNotificationCenter defaultCenter] removeObserver:self];
    

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

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

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

    Кендалл и др.ал - кто-нибудь может объяснить, почему это может быть так для UIWindow ( Пользовательское окно ) подклассы?Это потому, что окно находится на вершине пищевой цепочки?

    Я наткнулся на этот пост в поисках "встряхивающей" реализации.ответ милленоми хорошо сработал для меня, хотя я искал что-то, для запуска чего требовалось немного больше "встряхивающих действий".Я заменил логическое значение на значение int shakeCount.Я также переопределил метод L0AccelerationIsShaking() в Objective-C.Вы можете настроить требуемое количество встряхиваний, изменив количество, добавленное в 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];   
        }
    

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

    @интерфейс MyViewController :UIViewController ( пользовательский контроллер ) <UIPickerViewDelegate, UIPickerViewDataSource,="" UIAccelerometerDelegate="">

    Добавьте следующие методы в файл 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{
    

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

    Быстрая версия, основанная на самом первом ответе!

    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
    
    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top