【问题标题】:How do I detect when someone shakes an iPhone?如何检测有人摇晃 iPhone?
【发布时间】:2010-09-14 02:34:42
【问题描述】:

我想在有人摇晃 iPhone 时做出反应。我并不特别关心他们如何摇晃它,只是它被大力挥动了一瞬间。有人知道如何检测吗?

【问题讨论】:

    标签: ios accelerometer motion-detection shake


    【解决方案1】:

    在 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,此方法也有效。

    【讨论】:

    • 这对我来说不太适用:我需要覆盖控制器的 viewDidAppear 而不是 viewWillAppear。我不知道为什么;也许视图需要可见才能开始接收震动事件?
    • 这更容易但不一定更好。如果您想检测长时间的连续抖动,这种方法没有用,因为 iPhone 喜欢在抖动实际停止之前触发motionEnded。因此,使用这种方法,您会得到一系列不连贯的短摇,而不是长摇。在这种情况下,另一个答案效果更好。
    • @Kendall - UIViewControllers 实现 UIResponder 并且在响应者链中。最顶层的 UIWindow 也是如此。 developer.apple.com/library/ios/DOCUMENTATION/EventHandling/…
    • [super respondsToSelector: 不会做你想做的事,因为它相当于调用[self respondsToSelector: 将返回YES。你需要的是[[ShakingView superclass] instancesRespondToSelector:
    • 你可以使用 if ( event.subtype == UIEventSubtypeMotionShake ) 而不是使用: if ( motion == UIEventSubtypeMotionShake )
    【解决方案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.0 提供了一种更简单的方法。
    • 如果他们在特定轴上精确摇动会发生什么?
    • 最佳答案,因为 iOS 3.0 motionBeganmotionEnded 事件在检测摇动的确切开始和结束方面不是很精确或准确。这种方法可以让您尽可能精确。
    • UIAccelerometerDelegate accelerometer:didAccelerate: 在 iOS5 中已被弃用。
    • 这个其他答案更好更快地实现stackoverflow.com/a/2405692/396133
    【解决方案3】:

    我终于使用来自 Undo/Redo Manager Tutorial 的代码示例使其工作。
    这正是您需要做的:

  • 在 App 的 Delegate 中设置 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 方法添加到您的视图控制器:
  • 
    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
    {
        if (motion == UIEventSubtypeMotionShake)
        {
            // your code
        }
    }
    

    【讨论】:

    • 只是为了添加到 Eran Talmor 解决方案:如果您在新项目选择器中选择了这样的应用程序,您应该使用导航控制器而不是视图控制器。
    • 我试过用这个,但有时重摇或轻摇它就停止了
    • 第 1 步现在是多余的,因为 applicationSupportsShakeToEdit 的默认值是 YES
    • 为什么在[super viewWillDisappear:animated]; 之前调用[self resignFirstResponder];?这似乎很奇怪。
    【解决方案4】:

    首先,肯德尔 7 月 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: 中,其中涉及视图控制器。确保您对抖动事件的响应知道它是否“已经在进行中”。否则,如果设备连续摇晃两次,您将遇到交通堵塞。这样您就可以忽略其他通知,直到您真正完成对原始通知的响应。

    另外:您可以选择提示 motionBeganmotionEnded。由你决定。在我的情况下,效果总是需要在设备静止后发生(相对于它开始摇晃时),所以我使用motionEnded。尝试两者,看看哪一个更有意义......或检测/通知两者!

    这里还有一个(好奇的?)观察:请注意,此代码中没有第一响应者管理的迹象。到目前为止,我只用 Table View Controllers 尝试过这个,一切似乎都很好地协同工作!不过我不能保证其他情况。

    肯德尔等人。 al - 谁能说出为什么 UIWindow 子类会这样?是不是因为窗口位于食物链的顶端?

    【讨论】:

    • 这很奇怪。它按原样工作。希望这不是“不顾一切地工作”的情况!请让我知道您在自己的测试中发现了什么。
    • 我更喜欢它而不是继承常规 UIView,特别是因为你只需要它存在于一个地方,所以放入一个窗口子类似乎很自然。
    • 谢谢你jdandrea,我用你的方法,但多次摇晃!!!我只希望用户可以摇一次(认为摇晃确实存在)...!我该如何处理?我是这样实现的。我像这样实现我的代码 smht:i-phone.ir/forums/uploadedpictures/… ---- 但问题是应用程序在应用程序的生命周期中只晃动 1 次!不仅查看
    • Momeks:如果您需要在设备静止(摇晃后)时发出通知,请使用 motionEnded 而不是 motionBegan。应该这样做!
    • @Joe D'Andrea - 它按原样工作,因为 UIWindow 在响应者链中。如果链上更高的东西拦截并消耗了这些事件,它将接收它们。 developer.apple.com/library/ios/DOCUMENTATION/EventHandling/…
    【解决方案5】:

    我发现这篇文章是为了寻找一个“摇晃”的实现。 millenomi 的回答对我来说效果很好,尽管我正在寻找需要更多“摇晃动作”才能触发的东西。我已经用一个 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)];
    

    【讨论】:

    • 如何像全景图一样检测设备移动?
    • 如果iphone仅在X方向移动,如何识别抖动?我不想检测 Y 或 Z 方向的抖动。
    【解决方案6】:

    在带有 Swift 的 iOS 8.3(可能更早)中,就像在视图控制器中覆盖 motionBeganmotionEnded 方法一样简单:

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

    【讨论】:

    • 你试过了吗?我假设要在应用程序在后台运行时让它工作需要一些额外的工作。
    • 不.. 很快就会.. 但是如果你注意到 iPhone 6s 它有这个“拿起唤醒屏幕”功能,当你拿起设备时屏幕会亮一段时间。我需要捕捉这种行为
    • 这应该是公认的答案。无需为 CoreMotion 操心……
    【解决方案7】:

    您需要通过 accelerometer:didAccelerate: 方法检查加速度计,该方法是 UIAccelerometerDelegate 协议的一部分,并检查值是否超过摇动所需移动量的阈值。

    在 GLPaint 示例中 AppController.m 底部的 accelerometer:didAccelerate: 方法中有不错的示例代码,该示例可在 iPhone 开发者网站上找到。

    【讨论】:

      【解决方案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

      【讨论】:

      • 如何像全景图一样检测设备移动?
      • 如果iphone仅在X方向移动,如何识别抖动?我不想检测 Y 或 Z 方向的抖动。
      【解决方案9】:
      【解决方案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】:

        很抱歉将其作为答案而不是评论发布,但正如您所见,我是 Stack Overflow 的新手,所以我还没有足够的声誉来发布 cmets!

        无论如何,我第二次@cire 关于确保一旦视图成为视图层次结构的一部分就设置第一响应者状态。因此,例如在您的视图控制器 viewDidLoad 方法中设置第一响应者状态将不起作用。如果您不确定它是否有效,[view becomeFirstResponder] 会返回一个布尔值,您可以对其进行测试。

        另外一点:如果您不想不必要地创建 UIView 子类,您可以使用视图控制器来捕获抖动事件。我知道这不是那么麻烦,但仍然有选择。只需将 Kendall 放入 UIView 子类的代码 sn-ps 移动到您的控制器中,然后将 becomeFirstResponderresignFirstResponder 消息发送到 self 而不是 UIView 子类。

        【讨论】:

        • 这并没有提供问题的答案。要批评或要求作者澄清,请在其帖子下方发表评论。
        • @SashaSalauyou 我在这里的第一句话中明确指出我当时没有足够的声誉发表评论
        • 对不起。这里有可能让 5 岁的答案进入审核队列))
        【解决方案12】:

        首先,我知道这是一个旧帖子,但它仍然具有相关性,我发现两个最高投票的答案没有尽早检测到抖动。这是怎么做的:

        1. 在目标的构建阶段将 CoreMotion 链接到您的项目:
        2. 在您的视图控制器中:

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

          如果您使用 Storyboard,这可能会比较棘手,我不知道您在应用程序委托中需要的代码。

          【讨论】:

          • 是的,您可以从 Xcode 5 开始在 @implementation 中派生您的基类。
          • 这行得通,我在几个应用程序中使用它。所以不知道为什么它被否决了。
          • 就像一个魅力。如果您想知道,您可以像这样覆盖窗口类:stackoverflow.com/a/10580083/709975
          • 当应用程序在后台时这会起作用吗?如果没有其他想法如何检测何时在后台?
          • 如果您设置了背景模式,这可能会起作用。
          【解决方案14】:

          只要用这三种方法就可以了

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

          有关详细信息,您可以查看there 上的完整示例代码

          【讨论】:

            【解决方案15】:

            基于第一个答案的 swiftease 版本!

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

            【讨论】:

              【解决方案16】:

              在 swift 5 中,这就是您捕捉动作和检查的方式

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

              【讨论】:

                【解决方案17】:

                为了在应用范围内启用这个应用程序,我在 UIWindow 上创建了一个类别:

                @implementation UIWindow (Utils)
                
                - (BOOL)canBecomeFirstResponder
                {
                    return YES;
                }
                
                - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
                {
                    if (motion == UIEventSubtypeMotionShake) {
                        // Do whatever you want here...
                    }
                }
                
                @end
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2023-03-15
                  • 1970-01-01
                  • 2011-07-11
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多