我想在有人摇动iPhone时做出反应。我并不特别在乎他们如何摇动它,只是一瞬间剧烈地挥舞着它。有人知道如何检测吗?

#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。我不知道为什么;可能需要先查看该视图,然后它才能执行任何操作以开始接收震动事件?

–克里斯托弗·约翰逊(Kristopher Johnson)
09年8月9日,0:54

这比较容易,但不一定更好。如果您想检测到长时间的连续晃动,则此方法没有用,因为iPhone喜欢在晃动实际停止之前触发motionEnded。因此,使用这种方法,您会得到一系列不连贯的短摇,而不是一个长摇。在这种情况下,其他答案会更好。

–aroth
2011年4月13日在0:56

@Kendall-UIViewControllers实现UIResponder并位于响应者链中。最顶层的UIWindow也是如此。 developer.apple.com/library/ios/DOCUMENTATION/EventHandling/…

–DougW
2011年6月23日14:45

[super respondsToSelector:将不会执行您想要的操作,因为它等效于调用[self responsesToSelector:将返回YES。您需要的是[[ShakingView超类] instanceRespondToSelector:。

–Vadim Yelagin
2014年2月21日在8:09

代替使用:if(event.subtype == UIEventSubtypeMotionShake),可以使用:if(motion == UIEventSubtypeMotionShake)

– Hashim Akhtar
2014年3月28日14:41



#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方法。

–肯德尔·赫尔姆斯特·盖尔纳(Kendall Helmstetter Gelner)
09年7月10日在21:15

如果他们在特定轴上精确晃动会怎样?

– jprete
09年8月14日在14:54

最好的答案,因为iOS 3.0 motionBegan和motionEnded事件在检测抖动的确切开始和结束方面不是非常精确。这种方法可以使您尽可能精确。

–aroth
2011年4月13日,0:57



UIAccelerometerDelegate accelerometer:didAccelerate:在iOS5中已弃用。

–谢燕涛
2012年1月20日10:12



这另一个答案是更好和更快地实现stackoverflow.com/a/2405692/396133

–阿布拉莫德
2012-04-20 23:42



#3 楼

我终于使用此撤消/重做管理器教程中的代码示例使其工作。
这正是您需要做的:




Set应用程序的委托中的applicationSupportsShakeToEdit属性:
br />

    - (void)applicationDidFinishLaunching:(UIApplication *)application {

        application.applicationSupportsShakeToEdit = YES;

        [window addSubview:viewController.view];
        [window makeKeyAndVisible];
}



将motionEnded方法添加到视图控制器:


-(BOOL)canBecomeFirstResponder {
    return YES;
}

-(void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self becomeFirstResponder];
}

- (void)viewWillDisappear:(BOOL)animated {
    [self resignFirstResponder];
    [super viewWillDisappear:animated];
}


评论


只是添加到Eran Talmor解决方案中:如果您已在新项目选择器中选择了这样的应用程序,则应使用Navigationcontroller而不是Viewcontroller。

–Rob
10年8月16日在8:34

我尝试使用此功能,但有时剧烈震动或轻微震动就停止了

–vinothp
2012年8月10日在18:19

现在,步骤1是多余的,因为applicationSupportsShakeToEdit的默认值为YES。

–uɥƃnɐʌuop
16-4-22在19:56



为什么调用[self resignFirstResponder];在[super viewWillDisappear:animated]之前;?这似乎很奇怪。

– JaredH
16年11月16日在22:02

#4 楼

首先,肯德尔(Kendall)在7月10日的答覆就当场了。摇动时提醒应用的各个部分。这就是我最终要做的。

首先,我将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];


以观察者的身份离开自己:和viewWillDisappear:与View Controller有关的地方。确保您对震动事件的响应知道它是否“已在进行中”。否则,如果将设备连续摇动两次,将会造成交通拥堵。这样,您就可以忽略其他通知,直到您真正完成对原始通知的响应为止。由你决定。就我而言,效果总是需要在设备静止后才发生(相对于它开始晃动时),因此我使用motionEnded。尝试两者,看看哪个更有意义...或同时检测/通知两者!

这里还有一个(好奇吗?)观察:请注意,此代码中没有急救人员管理的迹象。到目前为止,我仅使用Table View Controllers进行过尝试,并且一切似乎都可以很好地协同工作!我无法保证其他情况。

肯德尔等。 al-谁能说出为什么UIWindow子类可能如此?是因为窗口位于食物链的顶部吗?

评论


那真是太奇怪了。它按原样工作。希望这不是“尽管工作”的情况!请让我知道您在自己的测试中发现的内容。

– Joe D'Andrea
09年9月2日在15:54

我宁愿将此子类化为常规UIView的子类,尤其是因为您只需要将此子类放在一个地方,因此放入窗口子类似乎是很自然的选择。

–蓬松的青蛙
09-10-16在23:56

谢谢jdandrea,我用你的方法,但是发抖了很多次!我只希望用户可以摇晃一次(以为在那里摇晃)...!我该如何处理?我实现这样的东西。我像这样实现我的代码:i-phone.ir/forums/uploadedpictures/…----但是问题是应用程序在应用程序的生命中确实只摇了1次!不仅查看

– Momi
2010年8月10日10:14



Momeks:如果您需要在设备处于静止状态(摇晃后)后发出通知,请使用motionEnded而不是motionBegan。那应该做到的!

– Joe D'Andrea
2010年8月24日14:00

@Joe D'Andrea-它按原样工作,因为UIWindow在响应者链中。如果链上的某个事物拦截并消耗了这些事件,它将不会收到它们。 developer.apple.com/library/ios/DOCUMENTATION/EventHandling/…

–DougW
2011年6月23日14:49

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


评论


如何像全景一样检测设备的移动?

–user2526811
2013年9月17日下午5:37

如果iPhone仅沿X方向移动,如何识别抖动?我不想检测Y或Z方向的抖动。

– Manthan
17年5月24日在10:40



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


评论


你试过了吗?我以为要在应用程序在后台运行时使此功能正常工作,会花费一些额外的精力。

– nhgrif
18年5月11日在19:42

Nope ..很快就会..,但是如果您注意到iPhone 6s,它具有此“拾取唤醒屏幕”功能,当您拿起设备时,屏幕会变亮一段时间。我需要捕捉这种行为

–nr5
18年5月11日在19:45

这应该是公认的答案。无需为CoreMotion烦恼...

– John Scalo
10月30日19:27

#7 楼

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

样品不错iPhone开发者网站上提供的GLPaint示例中,AppController.m底部的accelerometer:didAccelerate:方法中的代码。

#8 楼

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

即:

@interface MyViewController:UIViewController

评论


如何像全景一样检测设备的移动?

–user2526811
2013年9月17日下午5:39

如果iPhone仅沿X方向移动,如何识别抖动?我不想检测Y或Z方向的抖动。

– Manthan
17年5月24日在10:41

#9 楼

查看GLPaint示例。

http://developer.apple.com/library/ios/#samplecode/GLPaint/Introduction/Intro.html

#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的新手,因此我还没有足够的声誉发表评论!一旦视图成为视图层次结构的一部分,请确保设置第一响应者状态。因此,例如,无法在视图控制器中设置第一响应者状态viewDidLoad方法。而且,如果不确定不确定它是否正常工作,则[view becomeFirstResponder]会返回一个可以测试的布尔值。不必要地创建UIView子类。我知道这不是那么麻烦,但仍然可以选择。只需将Kendall放入UIView子类中的代码片段移到控制器中,然后将becomeFirstResponderresignFirstResponder消息(而不是UIView子类)发送到self

评论


这不能为问题提供答案。要批评或要求作者澄清,请在其帖子下方发表评论。

– Alex Salauyou
15年4月20日在23:09

@SashaSalauyou我在第一句话中明确指出,当时我没有足够的声誉来发表评论

–纽兹
2015年4月21日在23:45



抱歉。 5岁以下答案可能会进入评论队列))

– Alex Salauyou
15年4月22日在10:29

#12 楼

首先,我知道这是一个过时的帖子,但是仍然有用,我发现投票率最高的两个答案没有尽早发现抖动。方法是这样的:


在目标的构建阶段将CoreMotion链接到您的项目:



在ViewController中:

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


然后在您的应用程序委托中: br />如果您使用的是Storyboard,这可能会比较棘手,我不知道您在应用程序委托中需要的代码是否准确。

评论


是的,自Xcode 5起,您可以在@implementation中派生基类。

–mxcl
2014年7月12日在12:47

这可行,我在几个应用程序中都使用了它。所以不确定为什么它被否决了。

–mxcl
2014年12月15日在21:11

像魅力一样运作。如果您想知道,可以像这样重写窗口类:stackoverflow.com/a/10580083/709975

–教育
2015年2月10日在2:19

应用程序在后台运行时可以正常工作吗?如果没有其他想法,如何在后台进行检测?

–nr5
18年5月11日在8:04

如果您设置了背景模式,这可能会起作用。

–mxcl
18年5月15日在15:55

#14 楼

只需使用这三种方法即可

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


有关详细信息,您可以在此处检查完整的示例代码

#15 楼

基于第一个答案的快速版本!

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


#16 楼

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

@implementation UIWindow (Utils)

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
    if (motion == UIEventSubtypeMotionShake) {
        // Do whatever you want here...
    }
}

@end