簡體   English   中英

如何檢測有人搖晃 iPhone?

[英]How do I detect when someone shakes an iPhone?

當有人搖晃 iPhone 時,我想做出反應。 我並不特別關心他們如何搖晃它,只是它被猛烈地揮動了一瞬間。 有誰知道如何檢測這個?

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

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

滯后可防止多次觸發抖動事件,直到用戶停止抖動。

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

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

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

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

    首先,肯德爾在 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子類可能會這樣? 是不是因為窗戶在食物鏈的頂端?

    我遇到了這篇文章,正在尋找一個“震動”的實現。 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:我已將更新間隔設置為 1/15 秒。

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

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

    您需要通過 accelerometer:didAccelerate: 方法檢查加速度計,該方法是 UIAccelerometerDelegate 協議的一部分,並檢查這些值是否超過了震動所需移動量的閾值。

    iPhone 開發者站點上的 GLPaint 示例中 AppController.m 底部的加速計:didAccelerate: 方法中有不錯的示例代碼。

    這是您需要的基本委托代碼:

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

    還要在接口中的相應代碼中設置 。 IE:

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

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

    很抱歉將此作為答案而不是評論發布,但正如您所看到的,我是 Stack Overflow 的新手,因此我的聲譽還不足以發表評論!

    無論如何,我第二次@cire 關於確保一旦視圖成為視圖層次結構的一部分就設置第一響應者狀態。 因此,例如在您的視圖控制器viewDidLoad方法中設置第一響應者狀態將不起作用。 如果您不確定它是否正常工作, [view becomeFirstResponder]返回一個可以測試的布爾值。

    另一點:如果您不想不必要地創建 UIView 子類,您可以使用視圖控制器來捕獲抖動事件。 我知道這不是那么麻煩,但仍然有選擇。 只需將 Kendall 放入 UIView 子類的代碼片段移動到您的控制器中,並將becomeFirstResponderresignFirstResponder消息發送給self而不是 UIView 子類。

    首先,我知道這是一個舊帖子,但它仍然是相關的,我發現投票最高的兩個答案沒有盡早檢測到震動 這是如何做到的:

    1. 在目標的構建階段將 CoreMotion 鏈接到您的項目:核心運動
    2. 在您的 ViewController 中:

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

    最簡單的解決方案是為您的應用程序派生一個新的根窗口:

    @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,這可能會更棘手,我不知道您在應用程序委托中需要的代碼。

    用這三種方法就可以搞定

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

    有關詳細信息,您可以查看那里的完整示例代碼

    基於第一個答案的 swiftease 版本!

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

    在 swift 5 中,這就是你如何捕捉動作和檢查

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

    為了在應用程序范圍內啟用此應用程序,我在 UIWindow 上創建了一個類別:

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

    暫無
    暫無

    聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

     
    粵ICP備18138465號  © 2020-2024 STACKOOM.COM