Как определить, когда кто-то трясет iPhone? - PullRequest
338 голосов
/ 30 сентября 2008

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

Ответы [ 16 ]

291 голосов
/ 11 июля 2009

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

Основная хитрость в том, что вам нужно иметь некоторый UIView (не UIViewController), который вы хотите, чтобы firstResponder получал сообщения о событиях встряхивания. Вот код, который вы можете использовать в любом UIView для получения событий встряски:

@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 (даже системные представления) в представление, которое может получить событие встряхивания, просто подклассифицировав представление только этими методами (и затем выбрав этот новый тип вместо базового типа в IB, или используя его при выделении вида).

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

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

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

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

179 голосов
/ 02 октября 2008

Из моего 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

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

154 голосов
/ 09 марта 2010

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

Установите свойство 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
    }
}
94 голосов
/ 29 августа 2009

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

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

Сначала я вложил в подкласс UIWindow . Это легко peasy. Создайте новый файл класса с интерфейсом, таким как 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 на MotionWindow или как вы там назвали Это. Сохранить XIB. Если вы установили UIWindow программно, используйте вместо этого новый класс Window.

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

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

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

[[NSNotificationCenter defaultCenter] removeObserver:self];

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

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

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

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

34 голосов
/ 10 ноября 2008

Я наткнулся на этот пост в поисках "потрясающей" реализации. Ответ millenomi хорошо сработал для меня, хотя я искал что-то, что требовало более «дрожащего действия», чтобы сработать. Я заменил на логическое значение 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)];
12 голосов
/ 21 апреля 2015

В 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!")
    }
}
12 голосов
/ 30 сентября 2008

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

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

9 голосов
/ 23 марта 2009

Это базовый код делегата, который вам нужен:

#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

7 голосов
/ 15 октября 2012

Добавить следующие методы в файл 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 
     }
7 голосов
/ 30 сентября 2008
...