Як визначити, коли хтось струшує iPhone?


340

Я хочу реагувати, коли хтось трясе iPhone. Мені особливо не байдуже, як вони його струшують, тільки що він енергійно махав на частку секунди. Хтось знає, як це виявити?

Відповіді:


292

У версії 3.0 тепер є простіший спосіб - підключити до нових подій руху.

Основна хитрість полягає в тому, що вам потрібно мати деякий UIView (а не UIViewController), який ви хочете як першийResponder отримувати повідомлення про події тремтіння. Ось код, який можна використовувати в будь-якому 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 (навіть системні подання) у вигляд, який може отримати подію похитування, просто підкласифікувавши перегляд лише цими методами (а потім вибравши цей новий тип замість базового типу в ІБ або використовуючи його при розподілі вид).

У контролері перегляду потрібно встановити цей вид, щоб він став першим відповіддю:

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

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

Цей метод працює, навіть якщо встановити applicationSupportsShakeToEdit на NO.


5
Це не дуже спрацювало для мене: мені потрібно було замінити viewDidAppearзамість контролера viewWillAppear. Я не впевнений, чому; може бути, погляд повинен бути видно, перш ніж він зможе зробити все, що робити, щоб почати отримувати потрясіння?
Крістофер Джонсон

1
Це простіше, але не обов’язково краще. Якщо ви хочете виявити тривалий безперервний тремтіння, такий підхід не корисний, оскільки iPhone любить стріляти motionEndedперед тим, як струс фактично припинився. Таким чином, використовуючи такий підхід, ви отримуєте роз'єднану серію коротких струсів замість одного довгого. Інша відповідь в цьому випадку працює набагато краще.
aroth

3
@Kendall - UIViewControllers реалізують UIResponder і перебувають у ланцюжку відповідей. Також найвище UIWindow. developer.apple.com/library/ios/DOCUMENTATION/EventHandling/…
DougW

1
[super respondsToSelector:не зробить те, що ви хочете, оскільки це рівносильно дзвінку, [self respondsToSelector:який повернеться YES. Те, що вам потрібно, це [[ShakingView superclass] instancesRespondToSelector:.
Вадим Єлагін

2
Замість використання: якщо (event.subtype == UIEventSubtypeMotionShake) ви можете використовувати: if (рух == UIEventSubtypeMotionShake)
Хашим Ахтар

179

З моєї програми 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

Гістерезис запобігає повороту події тремтіння кілька разів, поки користувач не зупинить тремтіння.


7
Примітка. Я додав відповідь нижче, представляючи більш простий метод для 3.0.
Кендалл Гельмстетер Гельнер

2
Що станеться, якщо вони трясуть його точно на певній осі?
jprete

5
Найкраща відповідь, оскільки iOS 3.0 motionBeganта motionEndedподії не дуже точні чи точні з точки зору виявлення точного початку та кінця тремтіння. Такий підхід дозволяє бути точним, наскільки ви хочете.
aroth

2
UIAccelerometerDelegate акселерометр: didAccelerate: був застарілий в iOS5.
Янтао Сі Сі

Цей інший відповідь краще і швидше реалізувати stackoverflow.com/a/2405692/396133
Abramodj

154

Нарешті я змусив це працювати, використовуючи приклади коду з цього підручника для скасування / повторного управління .
Це саме те, що вам потрібно зробити:

  • Встановіть властивість 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
        }
    }

    Просто для додання рішення Ерана Талмора: вам слід скористатися навігаційним контролером замість контролера перегляду, якщо ви вибрали таку програму в новому програмі вибору проекту.
    Роб

    Я намагався скористатися цим, але іноді важким потрясінням або легким потрясінням просто зупинився
    vinothp

    Крок 1 тепер надлишково, як значення за замовчуванням applicationSupportsShakeToEditIS YES.
    DonVaughn

    1
    Навіщо дзвонити [self resignFirstResponder];попереду [super viewWillDisappear:animated];? Це здається своєрідним.
    JaredH

    94

    По-перше, відповідь Кендалла 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: де йдеться про контролери перегляду. Будьте впевнені, що ваша відповідь на подію тремтіння знає, чи вона "вже триває" чи ні. В іншому випадку, якщо пристрій струшуватиметься два рази поспіль, у вас з'явиться затор. Таким чином ви можете ігнорувати інші сповіщення до тих пір, поки по-справжньому не будете відповідати на оригінальне сповіщення.

    Також: Ви можете вирішити режим руху Біг проти руху Енд . Тобі вирішувати. У моєму випадку ефект завжди має відбуватися після спокою пристрою (порівняно, коли він починає тремтіти ), тому я використовую motionEnded . Спробуйте обидва і подивіться, який з них має більше сенсу ... або виявити / повідомити для обох!

    Ще одне (цікаве?) Зауваження тут: Зауважте, у цьому коді немає ознак управління першим відповіддю. Я пробував це тільки з контролерами таблиці View до цих пір, і все, здається, працює дуже добре разом! Я не можу поручитися на інші сценарії.

    Кендалл та ін. al - чи може хтось говорити, чому це може бути так для підкласів UIWindow ? Це тому, що вікно знаходиться вгорі ланцюга харчування?


    1
    Ось що так дивно. Він працює як є. Сподіваємось, це не випадок "працювати незважаючи на себе", хоча! Будь ласка, дайте мені знати, що ви дізнаєтесь у власному тестуванні.
    Джо Д'Андреа

    Я вважаю за краще це підкласирование звичайного UIView, тим більше, що вам потрібно лише це існувати в одному місці, тому введення у підклас вікна здається природним придатним.
    Shaggy Frog

    Дякую вам, jdandrea, я використовую ваш метод, але струшуючи, отримуючи кілька разів !!! Я просто хочу, щоб користувачі могли один раз похитнутись (на думку, що струшування є) ...! як я можу це впоратися? я реалізую що-небудь подібне. я реалізую свій код smht так: i-phone.ir/forums/uploadedpictures/… ---- але проблема в тому, що додаток трясе лише 1 раз протягом життя програми! не лише перегляд
    Momi

    1
    Момекс: Якщо вам потрібно повідомити, коли пристрій перебуває в спокої (після тремтіння), використовуйте motionEnded замість motionBegan. Це треба робити!
    Джо Д'Андреа

    1
    @Joe D'Andrea - Це працює так, тому що UIWindow знаходиться у ланцюжку відповідей. Якщо щось вище в ланцюжку перехопило і поглинуло ці події, воно не отримало б їх. developer.apple.com/library/ios/DOCUMENTATION/EventHandling/…
    DougW

    34

    Я натрапив на цю посаду, шукаючи «тремтячої» реалізації. Відповідь тисячоліття спрацювало для мене добре, хоча я шукав щось, що потребувало б трохи більше «тремтячих дій», щоб спрацювати. Я замінив булеве значення на 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)];

    Як я можу виявити рух пристрою так само, як панорама?
    користувач2526811,

    Як визначити тремтіння, якщо iphone переміщується лише у напрямку X? Я не хочу виявляти тремтіння в напрямку Y або Z.
    Мантан

    12

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

    У акселерометрі є пристойний зразок коду: didAccelerate: метод внизу AppController.m у прикладі GLPaint, який доступний на сайті розробників iPhone.


    12

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

    Ви пробували? Я б припустив, що для того, щоб це працювало, коли додаток знаходиться у фоновому режимі, знадобиться трохи зайвої роботи.
    nhgrif

    Ні, скоро .. але якщо ви помітили iPhone 6s, у нього є ця функція "забрати для пробудження", де екран підсвічується на деякий час, коли ви піднімаєте пристрій. Мені потрібно зафіксувати таку поведінку
    nr5

    9

    Це основний код делегата, який вам потрібен:

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


    Як я можу виявити рух пристрою так само, як панорама?
    користувач2526811

    Як визначити тремтіння, якщо iphone переміщується лише у напрямку X? Я не хочу виявляти тремтіння в напрямку Y або Z.
    Мантан


    7

    Додайте наступні методи у файл 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 
         }

    5

    Вибачте, що опублікували це як відповідь, а не коментар, але, як ви бачите, я новачок у Стек Overflow, тому я ще не досить авторитетний, щоб розміщувати коментарі!

    У будь-якому разі я другий @cire про те, щоб встановити статус першого відповіді, як тільки перегляд є частиною ієрархії перегляду. Тому встановлення статусу першого відповіді у вашому viewDidLoadметоді контролерів перегляду , наприклад, не працюватиме. І якщо ви не впевнені, чи працює він[view becomeFirstResponder] ви повернете собі булевий виклик, який ви можете перевірити.

    Ще один момент: ви можете використовувати контролер перегляду для зйомки події тремтіння, якщо ви не хочете створювати підклас UIView без потреби. Я знаю, що це не так вже й багато клопоту, але все ж є варіант. Просто перемістіть фрагменти коду, які Кендалл помістив у підклас UIView у свій контролер, і надішліть повідомлення becomeFirstResponderта resignFirstResponderповідомлення selfзамість підкласу UIView.


    Це не дає відповіді на запитання. Щоб критикувати або вимагати роз'яснення у автора, залиште коментар під їх дописом.
    Алекс Салайоу

    @SashaSalauyou Я чітко заявив, що в першому реченні тут мені не вистачило репутації, щоб опублікувати коментар у той час
    Newtz

    вибачте. Тут можна за 5-річну відповідь потрапити в чергу огляду))
    Alex Salauyou

    5

    По-перше, я знаю, що це стара публікація, але вона все ще актуальна, і я виявив, що два найвищих відповіді, які не проголосували, не виявили тремтіння якомога раніше . Ось як це зробити:

    1. Пов’яжіть CoreMotion зі своїм проектом на етапах складання цілі: CoreMotion
    2. У вашому ViewController:

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

    4

    Найпростіше рішення - отримати нове кореневе вікно для вашої програми:

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

    Якщо ви використовуєте Інструментарій, це може бути складніше, я не знаю код, який вам знадобиться точно в делегаті програми.


    І так, ви можете отримати свій базовий клас @implementationз Xcode 5.
    mxcl

    Це працює, я використовую його в декількох додатках. Тож не впевнений, чому це було проголосовано.
    mxcl

    працював як шарм. Якщо вам цікаво, ви можете змінити клас вікон таким чином: stackoverflow.com/a/10580083/709975
    Edu

    Чи буде це працювати, коли додаток знаходиться у фоновому режимі? Якщо не будь-яка інша ідея, як визначити, коли у фоновому режимі?
    nr5

    Це, ймовірно, спрацює, якщо у вас встановлений фоновий режим.
    mxcl

    1

    Просто використовуйте ці три методи для цього

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

    для деталей ви можете перевірити повний приклад коду там


    1

    Версія swiftease, заснована на першій відповіді!

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

    -1

    Щоб увімкнути цю програму, я створив категорію в UIWindow:

    @implementation UIWindow (Utils)
    
    - (BOOL)canBecomeFirstResponder
    {
        return YES;
    }
    
    - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
    {
        if (motion == UIEventSubtypeMotionShake) {
            // Do whatever you want here...
        }
    }
    
    @end
    Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
    Licensed under cc by-sa 3.0 with attribution required.