当有人摇晃iPhone时,我该如何察觉?

当有人摇晃iPhone时,我想做出反应。我并不特别在意他们是如何摇晃它的,只在意它在一瞬间被使劲地挥舞着。有人知道怎么检测吗?

126955 次浏览

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

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

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

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

我偶然发现这篇文章是为了寻找一个“摇动”的实现。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);
}
< p > PS: 我将更新间隔设置为1/15秒

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

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

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

也在接口中的适当代码中设置。即:

UIPickerViewDelegate, UIPickerViewDataSource, UIAccelerometerDelegate>

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

抱歉把这篇文章作为一个答案而不是评论,但正如你所看到的,我是Stack Overflow的新手,所以我还没有足够的声誉来发表评论!

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

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

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

现在…我想做一些类似的事情(在iPhone OS 3.0+),只是在我的情况下,我想它应用程序范围内,这样我可以提醒各种各样的部分的应用程序时,震动发生。这是我最后做的事情。

首先,我子类化了ui窗口。这很容易。创建一个带有MotionWindow : UIWindow接口的新类文件(当然,你可以自己选择)。添加一个这样的方法:

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

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

现在,如果你使用主窗口。xib (Xcode模板中常用的东西),然后把Window对象的类从ui窗口改为MotionWindow或者其他你叫它的东西。保存xib。如果你以编程方式设置ui窗口,则使用你的新Window类。

现在你的应用程序正在使用专门的ui窗口类。无论你想在哪里被告知摇晃,注册他们的通知!是这样的:

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

把自己从观察者的身份移开:

[[NSNotificationCenter defaultCenter] removeObserver:self];

我把我的放在那些:viewWillDisappear:中,其中涉及到视图控制器。确保您对震动事件的响应知道它是否“已经在进行中”。否则,如果设备连续摇晃两次,你就会遇到小塞车。通过这种方式,您可以忽略其他通知,直到真正完成对原始通知的响应。

另外:你可以选择motionBeganmotionEnded的cue off。由你决定。在我的情况下,效果总是需要发生设备处于休息状态(而不是当它开始摇晃时),所以我使用motionEnded。两个都试一下,看看哪个更有意义……或者检测/通知两者!

这里还有一个(奇怪的?)观察:注意,这段代码中没有第一响应器管理的迹象。到目前为止,我只尝试了表视图控制器,一切似乎都很好地工作在一起!但我不能保证其他情况。

Kendall,等等——谁能解释一下为什么ui窗口子类可能如此?是因为窗户位于食物链的顶端吗?

我最终使它工作使用的代码示例从这个撤消/重做管理器教程.
这正是你需要做的:

  • 在App的委托中设置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
    }
    }
    

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

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

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

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

    最简单的解决方案是为你的应用程序派生一个新的根窗口:

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

    如果你使用的是故事板,这可能会更棘手,我不知道你在应用委托中需要精确的代码。

    首先,我知道这是一个老帖子,但它仍然是相关的,我发现两个投票最高的答案没有尽早发现震动。下面是如何做到这一点:

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

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

    在iOS 8.3(也许更早)的Swift中,重写视图控制器中的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!")
    }
    }
    

    一个基于第一个答案的swif挑逗版本!

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

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

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

    在swift 5中,这就是如何捕捉运动并进行检查

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