誰かがiPhoneを振ったことをどのように検出しますか?


340

誰かがiPhoneを振ったときに反応したい。私は彼らがそれをどのように振るのか特に気にしません、それがほんの一瞬のために活発に振られたというだけです。誰かがこれを検出する方法を知っていますか?

回答:


292

3.0では、より簡単な方法があります。新しいモーションイベントに接続します。

主なトリックは、シェイクイベントメッセージを受信するfirstResponderとして必要なUIView(UIViewControllerではない)が必要なことです。シェイクイベントを取得するために任意の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

これらのメソッドのみでビューをサブクラス化する(そして、IBでベースタイプの代わりにこの新しいタイプを選択する、またはIBを割り当てるときにそれを使用する)だけで、任意の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
これは簡単ですが、必ずしも優れているとは限りません。長くて連続的なシェイクを検出したい場合motionEnded、シェイクが実際に停止する前にiPhoneが発砲するのを好むため、このアプローチは役に立ちません。したがって、このアプローチを使用すると、1つの長い揺れではなく、バラバラな一連の短い揺れが得られます。この場合、他の答えがはるかにうまく機能します。
2011

3
@Kendall-UIViewControllersはUIResponderを実装し、レスポンダーチェーンにあります。最上位のUIWindowも同様です。 developer.apple.com/library/ios/DOCUMENTATION/EventHandling/...
DougW

1
[super respondsToSelector:は、[self respondsToSelector:を返すと同等であるため、意図したとおりに動作しませんYES。必要なのは[[ShakingView superclass] instancesRespondToSelector:
Vadim Yelagin 2014

2
代わりに:if(event.subtype == UIEventSubtypeMotionShake)を使用できます:if(motion == UIEventSubtypeMotionShake)
Hashim Akhtar

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のより簡単な方法を示す回答を以下に追加しました。
Kendall Helmstetter Gelner、

2
特定の軸で正確に振っている場合はどうなりますか?
jprete 2009

5
シェイクの正確な開始と終了を検出するという点では、iOS 3.0 motionBeganmotionEndedイベントはあまり正確ではないため、最良の答えです。このアプローチにより、必要なだけ正確にすることができます。
2011

2
UIAccelerometerDelegate accelerometer:didAccelerate:はiOS5で廃止されました。
Yantao Xie 2012

この他の答えは、実装するために、より良く、より高速であるstackoverflow.com/a/2405692/396133
Abramodj

154

最後に、この「元に戻す/やり直しマネージャーのチュートリアル」のコード例を使用して動作させました。
これはまさにあなたがする必要があることです:

  • アプリのデリゲートでapplicationSupportsShakeToEditプロパティを設定します
  • 
        - (void)applicationDidFinishLaunching:(UIApplication *)application {
    
            application.applicationSupportsShakeToEdit = YES;
    
            [window addSubview:viewController.view];
            [window makeKeyAndVisible];
    }

  • ビューコントローラのcanBecomeFirstResponderviewDidAppear :、およびviewWillDisappear:メソッドを追加/オーバーライドします。
  • 
    -(BOOL)canBecomeFirstResponder {
        return YES;
    }
    
    -(void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
        [self becomeFirstResponder];
    }
    
    - (void)viewWillDisappear:(BOOL)animated {
        [self resignFirstResponder];
        [super viewWillDisappear:animated];
    }

  • motionEndedメソッドをView Controllerに追加します。
  • 
    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
    {
        if (motion == UIEventSubtypeMotionShake)
        {
            // your code
        }
    }

    Eran Talmorソリューションに追加するだけです。新しいプロジェクトチューザーでこのようなアプリケーションを選択した場合は、ビューコントローラーではなくナビゲーションコントローラーを使用する必要があります。
    Rob

    私はこれを試してみましたが、時々ヘビーシェイクまたはライトシェイクで止まりました
    vinothp

    のデフォルト値はでapplicationSupportsShakeToEditあるため、ステップ1は冗長になりましたYES
    DonVaughn

    1
    なぜ[self resignFirstResponder];先に電話するの[super viewWillDisappear:animated];ですか?これは奇妙に思えます。
    JaredH 2016年

    94

    まず、ケンドールの7月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からMotionWindowまたは呼び出したものに変更します。xibを保存します。プログラムでUIWindowを設定する場合は、代わりに新しいWindowクラスを使用してください。

    これで、アプリは特殊なUIWindowクラスを使用しています。シェイクについて知らせたいところはどこでも、通知にサインアップしてください!このような:

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

    オブザーバーから自分を削除するには:

    [[NSNotificationCenter defaultCenter] removeObserver:self];

    私はビューコントローラーが関係するviewWillAppear:viewWillDisappear:に配置しました。シェイクイベントへの応答で、「すでに進行中」かどうかを確認してください。そうしないと、デバイスが2回続けて振られると、交通渋滞が発生します。この方法では、元の通知への応答が本当に完了するまで、他の通知を無視できます。

    また:あなたはのオフ頭出しすることもできますmotionBeganmotionEnded。それはあなた次第です。私の場合、効果は常に場所を取る必要がある、私が使用してデバイスは、残りの部分(それは揺れを開始対)であるmotionEndedを。両方を試して、どちらがより意味があるかを確認してください...または、両方を検出/通知してください!

    ここでもう1つ(おもしろい?)観察:このコードには、ファーストレスポンダ管理の兆候がないことに注意してください。私はこれまでTable View Controllersでこれを試しただけで、すべてが非常にうまく連携しているようです!他のシナリオについては保証できません。

    ケンドール他 al-なぜこれがUIWindowサブクラスにそうなるのか、誰かが話すことができますか?窓が食物連鎖の最上部にあるからですか?


    1
    それはとても奇妙なことです。そのまま動作します。うまくいけば、それが「それ自身で働いている」というケースではないのです!あなた自身のテストであなたが見つけたものを私に知らせてください。
    ジョーD'アンドレア

    私はこれを通常のUIViewのサブクラス化よりも好みます。これは1か所に存在する必要があるだけなので、ウィンドウサブクラスを入れるのは自然なことのようです。
    シャギーフロッグ

    jdandreaに感謝します。私はあなたの方法を使用しますが、何度も揺れています!!! ユーザーが1回振ることができるようにしたいだけです(そこで振ることができるという観点から)...!どうすればそれを処理できますか?私はこのようにsthを実装します。私は次のようにコードを実装します:i-phone.ir/forums/uploadedpictures/… ----しかし問題は、アプリがアプリケーションの存続期間中1回だけシェイクすることです!表示するだけではありません
    Momi

    1
    Momeks:デバイスが静止した後(シェイク後)に通知を行う必要がある場合は、motionBeganではなくmotionEndedを使用します。それはそれをすべきです!
    Joe D'Andrea、

    1
    @Joe D'Andrea-UIWindowがレスポンダーチェーンにあるため、そのまま動作します。チェーンの上位にある何かがこれらのイベントを傍受して消費した場合、イベント受信されませんdeveloper.apple.com/library/ios/DOCUMENTATION/EventHandling/...
    DougW

    34

    私は「揺さぶる」実装を探しているこの投稿に出くわしました。ミレノミの答えは私にはうまくいきましたが、トリガーするのにもう少し「振るアクション」が必要なものを探していました。ブール値に置き換えて、intのshakeCountを使用します。Objective-CのL0AccelerationIsShaking()メソッドも再実装しました。shakeCountに追加されたammountを微調整することにより、必要なシェイクの量を微調整できます。まだ最適な値が見つかったかどうかはわかりませんが、これまでのところうまくいっているようです。これが誰かを助けることを願っています:

    - (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)];

    パノラマのようにデバイスの動きをどのように検出できますか?
    user2526811 2013

    iPhoneがX方向のみに動かされた場合、シェイクを識別する方法は?Y方向またはZ方向の振れを検出したくありません。
    マンタン2017年

    12

    UIAccelerometerDelegateプロトコルの一部であるaccelerometer:didAccelerate:メソッドを介して加速度計を確認し、値がシェイクに必要な動きの量のしきい値を超えているかどうかを確認する必要があります。

    iPhone開発者サイトで入手できるGLPaintサンプルのAppController.mのすぐ下にあるaccelerometer:didAccelerate:メソッドに適切なサンプルコードがあります。


    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>


    パノラマのようにデバイスの動きをどのように検出できますか?
    user2526811 2013

    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

    コメントではなく回答として投稿して申し訳ありませんが、ご覧のとおり、私はStack Overflowを初めて使用するため、コメントを投稿するのに十分な評判がありません。

    とにかく、ビューがビュー階層の一部になったら、最初のレスポンダのステータスを設定するように@cireを2番目に設定します。したがってviewDidLoad、たとえば、ビューコントローラーメソッドでファーストレスポンダーステータスを設定しても機能しません。そして、それが機能しているかどうかわからない[view becomeFirstResponder]場合は、テストできるブール値を返します。

    別のポイント:不必要にUIViewサブクラスを作成したくない場合は、ビューコントローラーを使用してシェイクイベントをキャプチャできます。それほど面倒ではありませんが、それでも選択肢はあります。KendallがUIViewサブクラスに配置したコードスニペットをコントローラーに移動し、UIViewサブクラスの代わりにbecomeFirstResponderおよびresignFirstResponderメッセージを送信するだけselfです。


    これは質問に対する答えを提供しません。批評したり、著者に説明を要求するには、投稿の下にコメントを残してください。
    Alex Salauyou 2015

    @SashaSalauyou私はここの最初の文ではっきりと述べましたが、コメントを投稿するのに十分な評判がありませんでした
    Newtz

    ごめんなさい。5
    年間の

    5

    まず、これは古い投稿であることは知っていますが、それでも関連性があり、投票数の多い2つの回答では揺れをできるだけ早く検出できなかったことがわかりました。これを行う方法は次のとおりです。

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

    Storyboardを使用している場合、これはよりトリッキーになる可能性があります。アプリケーションデリゲートで必要となるコードを正確には知りません。


    そして、はい、あなたは可能で、あなたのベースクラスを派生@implementationXcodeの5以降
    MXCL

    これは機能し、いくつかのアプリで使用しています。なぜそれが反対票を投じられたのかわからない。
    mxcl、2014

    魅力のように働いた。ご参考までに、次のようにウィンドウクラスをオーバーライドできます:stackoverflow.com/a/10580083/709975
    Edu

    アプリがバックグラウンドにあるときにこれは機能しますか?バックグラウンドで検出する方法が他にない場合はどうしますか?
    nr5 2018年

    バックグラウンドモードが設定されている場合、これはおそらく機能します。
    mxcl 2018年

    1

    これを行うには、これら3つの方法を使用してください

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

    詳細については、完全なサンプルコードをそちらで確認できます


    1

    最初の回答に基づいた迅速なバージョン!

    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
    弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
    Licensed under cc by-sa 3.0 with attribution required.