Core Motion 的那些事

獲取設備的移動事件

當我們移動,晃動,或者傾斜手機的時候,這些動作都會被設備的硬件捕獲。其實每一個動都都會在 X, Y, Z 三個方向上產(chǎn)生速度上的變化。根據(jù)不同的變化我們可以檢測出來設備的朝向和移動。要檢測設備的朝向和移動,我們有三種方法:

  • 利用 UIDevice,我們可以獲取設備的大致方向,例如屏幕朝上還是朝下。
  • 利用 UIKit 中的 UIEvent,我們可以獲取設備的晃動
  • 利用 Core Motion 來精確的獲取設備在 X Y Z 三個軸上的變化。這個是最精確的。

使用 UIDevice 來獲取設備的方向

首先我們應該告訴 UIDevice 來檢測設備方向變化,然后我們就可以在 UIDevice 中的 orientation 屬性得到設備方向的變化。

typedef NS_ENUM(NSInteger, UIDeviceOrientation) {
    UIDeviceOrientationUnknown,
    UIDeviceOrientationPortrait,            // Device oriented vertically, home button on the bottom
    UIDeviceOrientationPortraitUpsideDown,  // Device oriented vertically, home button on the top
    UIDeviceOrientationLandscapeLeft,       // Device oriented horizontally, home button on the right
    UIDeviceOrientationLandscapeRight,      // Device oriented horizontally, home button on the left
    UIDeviceOrientationFaceUp,              // Device oriented flat, face up
    UIDeviceOrientationFaceDown             // Device oriented flat, face down
} __TVOS_PROHIBITED;

如果你想檢測設備的方向變化,那么你應該監(jiān)聽 UIDeviceOrientationDidChangeNotification 通知,系統(tǒng)會在設備方向發(fā)生變化的時候發(fā)出該通知。

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
    NSLog(@"%ld",[UIDevice currentDevice].orientation);
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cheange:) name:UIDeviceOrientationDidChangeNotification object:nil];
}

- (void)cheange:(NSNotification *)notification {
    UIDevice *device = notification.object;
    NSLog(@"%ld", device.orientation);
}

為了延長電池的使用壽命,我們應該在不使用的時候告訴 UIDevice 來關(guān)閉加速計。

- (void)viewDidDisappear:(BOOL)animated {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
}

使用 UIEvent 來獲取設備搖晃事件

當用戶晃動設備的時候,iOS 系統(tǒng)會評估加速計的數(shù)據(jù),當這些數(shù)據(jù)特征符合搖晃的時候,系統(tǒng)會就會認為用戶在搖晃設備,然后會創(chuàng)建一個 UIEvent 對象來發(fā)送個宿主 APP,讓其處理。
移動事件和點擊事件很像。系統(tǒng)會告訴 APP 一個運動的開始和結(jié)束,但并不是每一個動作發(fā)生時都會這么做。
檢測晃動超級簡單。晃動這個是可以在模擬器中試驗的。

- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {
    NSLog(@"shake");
}

- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
    NSLog(@"shake");
}
shake

設置并檢查硬件是否支持設備移動事件

如果你需要獲取加速計數(shù)據(jù),你應該在 Info.plist 中添加相應的鍵值對,如果在一個沒有陀螺儀的設備上打開需要陀螺儀的應用,那么將會崩潰。App Store 也會根據(jù)你所需要的硬件能力來提醒用戶來是否要下載一個打不開的 APP。

如果你只是獲取設備的大致方向,可以不用添加

使用 Core Motion 來捕獲設備的移動

Core Motion 框架提供加速計和陀螺儀的原始數(shù)據(jù)給 APP 處理,它使用特別的算法來處理收集到的數(shù)據(jù),精確度更高,而且是在自己的線程中處理。

Core Motion 和 UIKit 是不同的,它和 UIEvent 無關(guān),也不隨著響應鏈傳遞,它只是直接的交付數(shù)據(jù)。
Core Motion 所產(chǎn)生的數(shù)據(jù)有三類

  • CMAltimeter 高度計
  • CMPedometer 計步器
  • CMAccelerometerData 捕獲每一個軸上的加速度
  • CMGyroData 捕獲三個軸上的旋轉(zhuǎn)率
    *CMDeviceMotion 包含了幾個不同的測量,數(shù)據(jù)更加精確
    一般來說,一個 APP 只需要創(chuàng)建一個 CMMotionManager 對象,設置其更新間隔,請求數(shù)據(jù),然后處理,每一個數(shù)據(jù)類都是 CMLogItem 的子類,所有的數(shù)據(jù)都會被打上時間的標記并且可以輸出到日志文件中。
    CMMotionManager 有兩種獲取數(shù)據(jù)的方法,pull push
  • pull APP 主動去請求數(shù)據(jù)更新
  • push APP 調(diào)用加速計后,實現(xiàn)回調(diào)的 block,當加速計有數(shù)據(jù)更新的時候,就回去調(diào)用這個 block,然后在其中處理相應的數(shù)據(jù)即可
    一般來說使用 pull 更為方便,因為它的代碼更少,用到了再去取相應的數(shù)據(jù)。
    不用的時候,記得關(guān)閉傳感器,這樣更加省電。

選擇一個合適的更新時間

當你使用 Core Motion 的時候,你需要指定一個更新間隔。這個時間應該是你的 APP 所能接收的最大間隔,因為這樣,可以提升電池的續(xù)航。下邊這個表格提供了常用的更新頻率,很少有 APP 需要每秒鐘獲取 100 次的加速計數(shù)據(jù)。

事件頻率(HZ) 用途
10 - 20 確定設備的當前朝向
30 - 60 游戲或者以設備加速度作為實時的用戶輸入數(shù)據(jù)
70 - 100 適用于 APP 需要高頻率的運動事件,例如,可以根據(jù)這個來判斷用戶快速點擊和搖晃設備

使用高度計 CMAltimeter

在手機中,一般使用的是氣壓高度計,根據(jù)當前位置的大氣壓,獲取當前位置的高度。具體公式為

z=cT log(P0/P)
c 為常數(shù),T 為絕對溫度,P 為在高度 z 處的氣壓,P0 為在海平面處的大氣壓,常數(shù) c 與重力加速度和空氣的摩爾質(zhì)量相關(guān)

我們知道,當高度上升12米,氣壓計中的汞柱下降1mm,標準大氣壓下是760mm的汞柱,一百帕相當于 3/4 mm的汞柱 我們可以得到簡單的計算公式

當前高度 =(760 - 當前大氣壓 * 3 / 4)*12

我們使用 + isRelativeAltitudeAvailable; 來判斷高度計是否可用,使用 - startRelativeAltitudeUpdatesToQueue: withHandler: 來獲得當前高度,在這個回調(diào) block 中,我們會得到一個 CMAltitudeData 對象,該對象有兩個屬性,一個是當前位置的大氣壓,單位是 kPa,一個是當前位置,相對于起始測量位置的高度。例如,當你剛開始測量的時候,該值為 0 ,當你拿著設備運動的時候,這個值會返回你當前位置,相對于起始位置的高度,具體高度,可以根據(jù)當前位置的壓強計算出來,但如果遇到氣壓波動比較大的位置,高度誤差,還是相當大的。

使用計步器 CMPedometer

當人攜帶者設備走動的時候,會產(chǎn)生一定的頻率,不同的運動產(chǎn)生的頻率是不一樣的,或走,或坐,或跑,根據(jù)不同的頻率濾波,識別,最終可以大致確定設備持有者的運動狀態(tài)。
在 iOS 8 以后,我們使用 CMPedometer ,而在 iOS 7 - iOS 8 我們使用 CMStepCounter 來獲取數(shù)據(jù)
在 CMPedometer, 我們可以獲取一下數(shù)據(jù)

  1. 步數(shù)
  2. 距離
  3. 樓層變化
  4. 速度
  5. 節(jié)奏
  6. 運動的開始暫停

當你開始調(diào)用的時候,即使應用被壓到后臺,系統(tǒng)還是可以記錄步數(shù),當你再次返回的時候,系統(tǒng)會把這段時間積累的步數(shù)全部返回給你。我們可以使用系統(tǒng)提供的 API 來查詢某一段時間的運動數(shù)據(jù),但是,系統(tǒng)最多只為我們保存 7 天。

//從某個時間開始記錄步數(shù)
-startPedometerUpdatesFromDate: withHandler             
//查詢某個時間的段的數(shù)據(jù)
-queryPedometerDataFromDate:toDate:withHandler
//停止記錄步數(shù)
-stopPedometerUpdates

使用 Core Motion 獲取加速計數(shù)據(jù)

加速計測量了三個軸的速度改變。在 Core Motion 中,每一個運動都被捕獲進一個 CMAccelerometerData 對象,而這個對象包含了一個 CMAcceleration 結(jié)構(gòu)體。


acceleration_axes.png

首先創(chuàng)建一個 CMMotionManager 對象

  • 使用 pull 方式獲取數(shù)據(jù),當調(diào)用 startAccelerometerUpdates 方法后,Core Motion 會用最新的測量數(shù)據(jù)更新 CMMotionManager 類的 accelerometerData 屬性,之后你可以在一個循環(huán)中或者定時器中獲取 accelerometerData 來判斷設備的運動。
  • 使用 push 方式來獲取數(shù)據(jù),當調(diào)用 startAccelerometerUpdatesToQueue:withHandler: 當數(shù)據(jù)更新的時候,就會自動調(diào)用 handler,在這個 block 中,你可以處理 accelerometerData
    self.mManager = [[CMMotionManager alloc] init];
    if (self.mManager.accelerometerAvailable) {
        NSLog(@"available");
    }
    self.mManager.accelerometerUpdateInterval = 1;
    // pull
    [self.mManager startAccelerometerUpdates];
    [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"%f", self.mManager.accelerometerData.acceleration.x);
    }];
    
    // push
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.name = @"accelerometer";
    [self.mManager startAccelerometerUpdatesToQueue:queue withHandler:^(CMAccelerometerData * _Nullable accelerometerData, NSError * _Nullable error) {
        NSLog(@"x : %f", accelerometerData.acceleration.x);
        NSLog(@"y : %f", accelerometerData.acceleration.y);
        NSLog(@"z : %f", accelerometerData.acceleration.z);
    }];
    // stop 
    - (void)stopUpdates {

     if ([self.mManager isAccelerometerActive] == YES) {
          [self.mManager stopAccelerometerUpdates];
     }
}

處理陀螺儀數(shù)據(jù)

角速度傳感器,測量物體的偏轉(zhuǎn),角速度。在手機上,單憑加速器是無法構(gòu)建完整的 3D 動作,加上陀螺儀,可以讓我們清晰感測到手機的旋轉(zhuǎn)。 陀螺儀測量設備繞著 X Y Z 三個軸的自傳速率

陀螺儀

當你請求陀螺儀數(shù)據(jù)更新的時候,Core Motion 返回的數(shù)據(jù)是有誤差的。它會返回一個 CMGyroData 對象,該對象中有一個 rotationRate 屬性,它是一個 CMRotationRate 結(jié)構(gòu)體,里邊包含了三個方向上的自傳速率,單位是弧度每秒。 用 CMGyroData 是有誤差的,如果想獲取更為精確是自傳速率,可以使用 CMDeviceMotion 。
其更新數(shù)據(jù)方式也是有兩種,分為 startGyroUpdates pull 和 startGyroUpdatesToQueue:withHandler: push,具體方式和加速計獲取方式一樣,取的數(shù)據(jù)在 CMMotionManager 的 gyroData 屬性中,同樣,你也需要設置更新的時間間隔。
代碼就不貼了,和上邊大同小異。

旋轉(zhuǎn)方向

旋轉(zhuǎn)方向遵循右手定則,右手握拳,大拇指指向某一個軸,如果旋轉(zhuǎn)的方向和四個手指的方向一樣的話,即為正,否則就為負

處理設備數(shù)據(jù)

如果設備上既有加速計又有陀螺儀的話,Core Motion 提供了一個 device-motion 的服務用來處理兩個傳感器的原始數(shù)據(jù)。使用這個,可以得到最精確的數(shù)據(jù)。
你可以訪問這個 CMDeviceMotion 對象的 attitude 屬性,每一個 CMAttitude 對象包含了下邊三種數(shù)據(jù)

  1. 一個四元組
  2. 一個旋轉(zhuǎn)率矩陣
  3. 歐拉角

調(diào)用方式和上邊測量加速度發(fā)方式類似,代碼就不貼了。

關(guān)于歐拉角

描述一個物體的旋轉(zhuǎn),往往是最難的。歐拉角是表達旋轉(zhuǎn)的最簡單的一種方式,形式上它是一個三維向量,其值分別代表物體繞坐標系三個軸(x,y,z軸)的旋轉(zhuǎn)角度。

pitch.gif
roll

yaw.gif

但是,這么描述的話,會有一個問題,那就是萬向鎖。

萬向鎖.jpg

歐拉旋轉(zhuǎn)可以靠這種順序讓一個物體指到任何一個想要的方向,但如果在旋轉(zhuǎn)中不幸讓某些坐標軸重合了就會發(fā)生萬向節(jié)鎖,這時就會丟失一個方向上的旋轉(zhuǎn)能力,也就是說在這種狀態(tài)下我們無論怎么旋轉(zhuǎn)(當然還是要原先的順序)都不可能得到某些想要的旋轉(zhuǎn)效果。具體可以點這里來了解。
由于大多數(shù)都是物理知識,我這個農(nóng)民工,了解的還不是很詳細,數(shù)據(jù)我們是得到了,具體怎么應用到現(xiàn)實中,還要慢慢摸索。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容