ios 如何检测有人摇晃 iPhone?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/150446/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 15:17:28  来源:igfitidea点击:

How do I detect when someone shakes an iPhone?

iosaccelerometermotion-detectionshake

提问by Josh Gagnon

I want to react when somebody shakes the iPhone. I don't particularly care how they shake it, just that it was waved vigorously about for a split second. Does anyone know how to detect this?

当有人摇晃 iPhone 时,我想做出反应。我并不特别关心他们如何摇晃它,只是它被猛烈地挥动了一瞬间。有谁知道如何检测这个?

采纳答案by Kendall Helmstetter Gelner

In 3.0, there's now an easier way - hook into the new motion events.

在 3.0 中,现在有一种更简单的方法 - 连接到新的运动事件。

The main trick is that you need to have some UIView (not UIViewController) that you want as firstResponder to receive the shake event messages. Here's the code that you can use in any UIView to get shake events:

主要技巧是您需要有一些 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

You can easily transform any UIView (even system views) into a view that can get the shake event simply by subclassing the view with only these methods (and then selecting this new type instead of the base type in IB, or using it when allocating a view).

您可以轻松地将任何 UIView(甚至系统视图)转换为可以通过仅使用这些方法对视图进行子类化(然后选择这个新类型而不是 IB 中的基类型,或者在分配一个看法)。

In the view controller, you want to set this view to become first responder:

在视图控制器中,您希望将此视图设置为第一响应者:

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

Don't forget that if you have other views that become first responder from user actions (like a search bar or text entry field) you'll also need to restore the shaking view first responder status when the other view resigns!

不要忘记,如果您有其他视图从用户操作(例如搜索栏或文本输入字段)成为第一响应者,您还需要在其他视图退出时恢复摇动视图第一响应者状态!

This method works even if you set applicationSupportsShakeToEdit to NO.

即使您将 applicationSupportsShakeToEdit 设置为 NO,此方法也有效。

回答by millenomi

From my Diceshakerapplication:

从我的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

The histeresis prevents the shake event from triggering multiple times until the user stops the shake.

滞后可防止多次触发抖动事件,直到用户停止抖动。

回答by Eran Talmor

I finally made it work using code examples from this Undo/Redo Manager Tutorial.
This is exactly what you need to do:

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

  • Set the applicationSupportsShakeToEditproperty in the App's Delegate:
  • 应用程序的委托中设置applicationSupportsShakeToEdit属性:
  • 
        - (void)applicationDidFinishLaunching:(UIApplication *)application {
    
            application.applicationSupportsShakeToEdit = YES;
    
            [window addSubview:viewController.view];
            [window makeKeyAndVisible];
    }
    

  • Add/Override canBecomeFirstResponder, viewDidAppear:and viewWillDisappear:methods in your View Controller:
  • 在您的视图控制器中添加/覆盖canBecomeFirstResponderviewDidAppear:viewWillDisappear:方法:
  • 
    -(BOOL)canBecomeFirstResponder {
        return YES;
    }
    
    -(void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
        [self becomeFirstResponder];
    }
    
    - (void)viewWillDisappear:(BOOL)animated {
        [self resignFirstResponder];
        [super viewWillDisappear:animated];
    }
    

  • Add the motionEndedmethod to your View Controller:
  • motionEnded方法添加到您的视图控制器:
  • 
    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
    {
        if (motion == UIEventSubtypeMotionShake)
        {
            // your code
        }
    }
    

    回答by Joe D'Andrea

    First, Kendall's July 10th answer is spot-on.

    首先,肯德尔在 7 月 10 日的回答是准确的。

    Now ... I wanted to do something similar (in iPhone OS 3.0+), only in my case I wanted it app-wide so I could alert variousparts of the app when a shake occurred. Here's what I ended up doing.

    现在......我想做一些类似的事情(在 iPhone OS 3.0+ 中),只有在我的情况下,我希望它在应用程序范围内使用,这样我就可以在发生震动时提醒应用程序的各个部分。这就是我最终做的。

    First, I subclassed UIWindow. This is easy peasy. Create a new class file with an interface such as MotionWindow : UIWindow(feel free to pick your own, natch). Add a method like so:

    首先,我继承了 UIWindow。这很容易。创建一个带有接口的新类文件,例如MotionWindow : UIWindow(随意选择你自己的,natch)。添加一个像这样的方法:

    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
        if (event.type == UIEventTypeMotion && event.subtype == UIEventSubtypeMotionShake) {
            [[NSNotificationCenter defaultCenter] postNotificationName:@"DeviceShaken" object:self];
        }
    }
    

    Change @"DeviceShaken"to the notification name of your choice. Save the file.

    更改@"DeviceShaken"为您选择的通知名称。保存文件。

    Now, if you use a MainWindow.xib (stock Xcode template stuff), go in there and change the class of your Window object from UIWindowto MotionWindowor whatever you called it. Save the xib. If you set up UIWindowprogrammatically, use your new Window class there instead.

    现在,如果您使用 MainWindow.xib(库存 Xcode 模板内容),请进入并将 Window 对象的类从UIWindow更改为MotionWindow或您调用的任何内容。保存xib。如果您以编程方式设置UIWindow,请改用您的新 Window 类。

    Now your app is using the specialized UIWindowclass. Wherever you want to be told about a shake, sign up for them notifications! Like this:

    现在您的应用程序正在使用专门的UIWindow类。无论您想在哪里听到有关摇晃的消息,请注册他们的通知!像这样:

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

    To remove yourself as an observer:

    删除自己作为观察者:

    [[NSNotificationCenter defaultCenter] removeObserver:self];
    

    I put mine in viewWillAppear:and viewWillDisappear:where View Controllers are concerned. Be sure your response to the shake event knows if it is "already in progress" or not. Otherwise, if the device is shaken twice in succession, you'll have a li'l traffic jam. This way you can ignore other notifications until you're truly done responding to the original notification.

    我把我的放在viewWillAppear:viewWillDisappear:中查看控制器。确保你对震动事件的反应知道它是否“已经在进行中”。否则,如果设备连续摇晃两次,您将遇到轻微的交通拥堵。这样您就可以忽略其他通知,直到您真正完成对原始通知的响应。

    Also: You may choose to cue off of motionBeganvs. motionEnded. It's up to you. In my case, the effect always needs to take place afterthe device is at rest (vs. when it starts shaking), so I use motionEnded. Try both and see which one makes more sense ... or detect/notify for both!

    另外:您可以选择从motionBeganmotionEnded 中进行提示。由你决定。就我而言,其效果总是需要发生后,设备处于静止(与当它开始摇晃),所以我用motionEnded。两者都尝试一下,看看哪个更有意义……或者检测/通知两者!

    One more (curious?) observation here: Notice there's no sign of first responder management in this code. I've only tried this with Table View Controllers so far and everything seems to work quite nicely together! I can't vouch for other scenarios though.

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

    Kendall, et. al - can anyone speak to why this might be so for UIWindowsubclasses? Is it because the window is at the top of the food chain?

    肯德尔等。al - 任何人都可以说为什么UIWindow子类可能会这样?是不是因为窗户在食物链的顶端?

    回答by Joe D'Andrea

    I came across this post looking for a "shaking" implementation. millenomi's answer worked well for me, although i was looking for something that required a bit more "shaking action" to trigger. I've replaced to Boolean value with an int shakeCount. I also reimplemented the L0AccelerationIsShaking() method in Objective-C. You can tweak the ammount of shaking required by tweaking the ammount added to shakeCount. I'm not sure i've found the optimal values yet, but it seems to be working well so far. Hope this helps someone:

    我遇到了这篇文章,正在寻找一个“震动”的实现。millenomi 的回答对我来说效果很好,尽管我正在寻找需要更多“摇晃动作”才能触发的东西。我已经用一个 intshakeCount 替换为布尔值。我还在 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: I've set the update interval to 1/15th of a second.

    PS:我已将更新间隔设置为 1/15 秒。

    [[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0 / 15)];
    

    回答by Dave Verwer

    You need to check the accelerometer via accelerometer:didAccelerate: method which is part of the UIAccelerometerDelegate protocol and check whether the values go over a threshold for the amount of movement needed for a shake.

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

    There is decent sample code in the accelerometer:didAccelerate: method right at the bottom of AppController.m in the GLPaint example which is available on the iPhone developer site.

    iPhone 开发者站点上的 GLPaint 示例中 AppController.m 底部的加速计:didAccelerate: 方法中有不错的示例代码。

    回答by nhgrif

    In iOS 8.3 (perhaps earlier) with Swift, it's as simple as overriding the motionBeganor motionEndedmethods in your view controller:

    在带有 Swift 的 iOS 8.3(也许更早版本)中,它就像覆盖视图控制器中的motionBeganormotionEnded方法一样简单:

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

    回答by Benjamin Ortuzar

    This is the basic delegate code you need:

    这是您需要的基本委托代码:

    #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];   
        }
    

    Also set the in the appropriate code in the Interface. i.e:

    还要在接口中的相应代码中设置 。IE:

    @interface MyViewController : UIViewController <UIPickerViewDelegate, UIPickerViewDataSource, UIAccelerometerDelegate>

    @interface MyViewController : UIViewController <UIPickerViewDelegate, UIPickerViewDataSource, UIAccelerometerDelegate>

    回答by Himanshu Mahajan

    Add Following methods in ViewController.m file, its working properly

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