web-dev-qa-db-ja.com

誰かがiPhoneを振ったことを検出するにはどうすればよいですか?

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

339
Josh Gagnon

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

これらのメソッドのみでビューをサブクラス化するだけで、UIView(システムビューも)をシェイクイベントを取得できるビューに簡単に変換できます(そして、IBのベースタイプの代わりにこの新しいタイプを選択するか、表示)。

View Controllerで、このビューをファーストレスポンダーになるように設定します。

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

ユーザーアクションからファーストレスポンダーになる他のビュー(検索バーやテキスト入力フィールドなど)がある場合、他のビューが辞任したときに、揺れているビューファーストレスポンダーステータスを復元する必要があることも忘れないでください!

ApplicationSupportsShakeToEditをNOに設定しても、このメソッドは機能します。

私の 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

ヒステリシスは、ユーザーがシェイクを停止するまで、シェイクイベントが複数回トリガーされるのを防ぎます。

179
millenomi

最後に、この ndo/Redo Manager Tutorial のコード例を使用して動作するようにしました。
これはまさにあなたがする必要があることです:

  • をセットする applicationSupportsShakeToEdit アプリのデリゲートのプロパティ:
  • 
        - (void)applicationDidFinishLaunching:(UIApplication *)application {
    
            application.applicationSupportsShakeToEdit = YES;
    
            [window addSubview:viewController.view];
            [window makeKeyAndVisible];
    }
    
  • 追加/上書き canBecomeFirstResponder、 viewDidAppear: そして viewWillDisappear: View Controllerのメソッド:
  • 
    -(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
        }
    }
    
    154
    Eran Talmor

    まず、ケンダルの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];
    

    View Controllerが関係するviewWillAppear:およびviewWillDisappear:に鉱山を置きます。シェイクイベントへの応答が、「進行中」かどうかを確認してください。それ以外の場合、デバイスが連続して2回振られると、交通渋滞が発生します。この方法では、元の通知への応答が本当に完了するまで、他の通知を無視できます。

    また、motionBeganvs.motionEndedからキューオフすることもできます。それはあなた次第です。私の場合、効果は常に発生する必要がありますafterデバイスが静止している(vs.揺れ始めるとき)ので、motionEnded。両方を試して、どちらがより理にかなっているかを確認してください...

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

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

    94
    Joe D'Andrea

    この投稿に出会って、「揺れ動く」実装を探しました。ミレノミの答えは私にとってはうまくいきましたが、トリガーするためにもう少し「揺さぶるアクション」が必要なものを探していました。ブール値をint shakeCountに置き換えました。また、Objective-CでL0AccelerationIsShaking()メソッドを再実装しました。 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)];
    
    34
    Jeremy Ellison

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

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

    12
    Dave Verwer

    Swiftを使用したiOS 8.3(おそらくそれ以前)では、View Controllerで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
    nhgrif

    これは、必要な基本的なデリゲートコードです。

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

    9

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

    コメントではなく回答としてこれを投稿して申し訳ありませんが、ご覧のとおり、私はStack Overflowが初めてなので、コメントを投稿するにはまだ評判が良くありません!

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

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

    5
    Newtz

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

    1. ターゲットのビルドフェーズでCoreMotionをプロジェクトにリンクします。 CoreMotion
    2. ViewControllerで:

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

    最も簡単な解決策は、アプリケーションの新しいルートウィンドウを派生させることです。

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

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

    4
    mxcl

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

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

    これらの3つの方法を使用するだけです

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

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

    1
    Mashhadi