Как определить, когда кто-то трясет iPhone?

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

Ответ 1

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

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

Ответ 2

Из моего приложения 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

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

Ответ 3

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

Задайте свойство 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];
}

Добавьте к вашему контроллеру View метод motionEnded:

- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
    if (motion == UIEventSubtypeMotionShake)
    {
        // your code
    }
}

Ответ 4

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

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

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

- (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:, где рассматривались контроллеры View. Убедитесь, что ваш ответ на событие shake знает, что он "уже выполняется" или нет. В противном случае, если устройство встряхивается два раза подряд, у вас будет заторможенный сигнал. Таким образом, вы можете игнорировать другие уведомления до тех пор, пока вы действительно не выполните ответ на исходное уведомление.

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

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

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

Ответ 8

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

#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 < UIPickerViewDelegate, UIPickerViewDataSource, UIAccelerometerDelegate >

Ответ 10

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

Ответ 11

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

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

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

Ответ 12

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

  • Свяжите CoreMotion с вашим проектом в фазах целевой сборки: CoreMotion
  • В вашем ViewController:

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

Ответ 13

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

@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];
    //…
}

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

Ответ 14

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

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

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

Ответ 15

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

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

Ответ 16

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

@implementation UIWindow (Utils)

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
    if (motion == UIEventSubtypeMotionShake) {
        // Do whatever you want here...
    }
}

@end