獲取設備的移動事件
當我們移動,晃動,或者傾斜手機的時候,這些動作都會被設備的硬件捕獲。其實每一個動都都會在 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");
}
設置并檢查硬件是否支持設備移動事件
如果你需要獲取加速計數(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ù)
- 步數(shù)
- 距離
- 樓層變化
- 速度
- 節(jié)奏
- 運動的開始暫停
當你開始調(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)體。
首先創(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)的方向和四個手指的方向一樣的話,即為正,否則就為負
處理設備數(shù)據(jù)
如果設備上既有加速計又有陀螺儀的話,Core Motion 提供了一個 device-motion 的服務用來處理兩個傳感器的原始數(shù)據(jù)。使用這個,可以得到最精確的數(shù)據(jù)。
你可以訪問這個 CMDeviceMotion 對象的 attitude 屬性,每一個 CMAttitude 對象包含了下邊三種數(shù)據(jù)
- 一個四元組
- 一個旋轉(zhuǎn)率矩陣
- 歐拉角
調(diào)用方式和上邊測量加速度發(fā)方式類似,代碼就不貼了。
關(guān)于歐拉角
描述一個物體的旋轉(zhuǎn),往往是最難的。歐拉角是表達旋轉(zhuǎn)的最簡單的一種方式,形式上它是一個三維向量,其值分別代表物體繞坐標系三個軸(x,y,z軸)的旋轉(zhuǎn)角度。
但是,這么描述的話,會有一個問題,那就是萬向鎖。
歐拉旋轉(zhuǎn)可以靠這種順序讓一個物體指到任何一個想要的方向,但如果在旋轉(zhuǎn)中不幸讓某些坐標軸重合了就會發(fā)生萬向節(jié)鎖,這時就會丟失一個方向上的旋轉(zhuǎn)能力,也就是說在這種狀態(tài)下我們無論怎么旋轉(zhuǎn)(當然還是要原先的順序)都不可能得到某些想要的旋轉(zhuǎn)效果。具體可以點這里來了解。
由于大多數(shù)都是物理知識,我這個農(nóng)民工,了解的還不是很詳細,數(shù)據(jù)我們是得到了,具體怎么應用到現(xiàn)實中,還要慢慢摸索。