如何检测有人摇动iPhone的时间?

时间:2008-09-29 20:15:00

标签: ios accelerometer motion-detection shake

当有人动摇iPhone时,我想做出反应。我并不特别在乎它们是如何摇晃它的,只是它在一瞬间大力挥手。有谁知道怎么检测到这个?

17 个答案:

答案 0 :(得分:291)

在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。

,此方法仍然有效

答案 1 :(得分: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

该组合可以防止震动事件多次触发,直到用户停止震动为止。

答案 2 :(得分:154)

我最终使用此Undo/Redo Manager Tutorial中的代码示例使其工作 这正是您需要做的事情:

  • 在App的代表中设置 applicationSupportsShakeToEdit 属性:
  • 
        - (void)applicationDidFinishLaunching:(UIApplication *)application {
    
            application.applicationSupportsShakeToEdit = YES;
    
            [window addSubview:viewController.view];
            [window makeKeyAndVisible];
    }
    

  • 添加/覆盖 canBecomeFirstResponder viewDidAppear: < i> 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
        }
    }
    

    答案 3 :(得分:94)

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

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

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

    Kendall,et。 al - 任何人都可以说明为什么 UIWindow 子类可能会如此?是因为窗口位于食物链的顶端吗?

    答案 4 :(得分:34)

    我发现这篇帖子正在寻找一个“摇晃”的实现。 millenomi的回答对我很有用,虽然我正在寻找一些需要更多“摇动动作”才能触发的东西。我用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)];
    

    答案 5 :(得分:12)

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

    加速度计中有一些不错的示例代码:didAccelerate:方法,位于iPhone开发者网站上可用的GLPaint示例中AppController.m的底部。

    答案 6 :(得分:12)

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

    答案 7 :(得分: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&lt; UIPickerViewDelegate,UIPickerViewDataSource,UIAccelerometerDelegate&gt;

    答案 8 :(得分: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 
         }
    

    答案 9 :(得分:7)

    答案 10 :(得分:5)

    很抱歉发布这个作为答案而非评论但是你可以看到我是Stack Overflow的新手,所以我还不足以发表评论!

    无论如何,我第二次@cire关于确保在视图是视图层次结构的一部分后设置第一个响应者状态。因此,在视图控制器viewDidLoad方法中设置第一响应者状态将不起作用。如果你不确定它是否正常工作[view becomeFirstResponder]会返回一个你可以测试的布尔值。

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

    答案 11 :(得分:5)

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

    1. 在目标的构建阶段将CoreMotion链接到您的项目: CoreMotion
    2. 在ViewController中:

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

    答案 12 :(得分: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];
        //…
    }
    

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

    答案 13 :(得分:1)

    只需使用这三种方法即可完成

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

    有关详细信息,您可以查看there

    上的完整示例代码

    答案 14 :(得分:1)

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

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

    答案 15 :(得分:0)

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

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

    答案 16 :(得分:-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