原文連接:http://nshipster.com/cmdevicemotion/閉包
陀螺儀和加速器通常都難入咱們法眼。ide
Core Motion framework
使咱們能夠很容易駕馭這些傳感器,讓用戶動動手指點劃滑就能夠完成相關的交互。性能
含M7或M8處理器中的動態處理器的設備還特別提供了獲取已保存動態活動的功能,就好比走了多少步,爬樓梯,還有另一些運動狀態(走圈圈等)。測試
CM讓開發者能夠經過傳感器(加速器、陀螺儀還有磁力儀)發送原始或經處理過的混合數據來觀察並響應iOS設備朝向。動畫
加速器和陀螺儀數據以iOS設備上3維座標來展現。當手機呈水平放置的時候(以下圖),X軸從左(負值)到右(正值),Y周從下(負值)到上(正值),還有就是Z軸垂直方向上從背屏(負值)到屏幕(正值)。spa
CoreMotionManager
類提供iOS設備動態數據的存取方法。有意思的是,CM提供了 "拉"和"推"兩種方式的獲取方式。 你能夠經過獲取當前任何傳感器的狀態或是CoreMotionManager
的只讀屬性組合數據來「拉」動態數據。你能夠經過block閉包在特定的時間間隔來獲取你想要的更新數據集合來捕獲「推」數據。線程
爲了保證在高層表現上的性能,蘋果推薦你在APP中使用單例CoreMotionManager
。翻譯
CoreMotionManager
爲4類數據(傳感器,加速器,陀螺儀,磁力儀)提供了一致的接口。舉個栗子,與陀螺儀交互 - 獲取你想要的數據。code
let manager = CoreMotionManager() if manager.gyroAvailable { // ... }
栗子中manager是視圖控制器的一個屬性orm
manager.gyroUpdateInterval = 0.1
時間單位用的是NSTimeInterval
,反應靈敏下降了,可是CPU使用率減小了。
manager.startGyroUpdates()
經過調用上述方法,manager.gyroData
就能夠獲取到設備當前的陀螺儀數據。
let queue = NSOperationQueue.mainQueue manager.startGyroUpdatesToQueue(queue) { (data, error) in // ... }
句柄閉包在給定的時間間隔會被不斷的調用。
manager.stopGyroUpdates()
舉個栗子給APP增長一個有趣的功能,不論設備怎麼傾斜,背景圖始終指向重力方向。
思考以下代碼:
首先咱們覈實確認咱們設備能夠獲取加速器的數據,緊接着咱們設定一個高頻更新率,而後咱們開始在回調閉包中處理圖片得旋轉角度:
if manager.accelerometerAvailable { manager.accelerometerUpdateInterval = 0.01 manager.startAccelerometerUpdatesToQueue(NSOperationQueue.mainQueue()) { [weak self] (data: CMAccelerometerData!, error: NSError!) in let rotation = atan2(data.acceleration.x, data.acceleration.y) - M_PI self?.imageView.transform = CGAffineTransformMakeRotation(CGFloat(rotation)) } }
每一個CMAccelerometerData
包中都包含了x,y,z值 - 每一個值都展現重力在該座標軸加速量。也就是說,若是你的設備豎直方向精緻放置,那麼值將是 (0, -1, 0);在桌子上方面了值會是(0, 0, -1);右傾斜個35度將會是(0.707, -0.707, 0)。
咱們計算旋轉角度是經過將加速器數據中的x,y份量進行arctan2的計算,而後在CGAffineTransform
中使用該角度。咱們圖像將會一直保持在右邊,不論咱們怎麼轉動手機。
戳這裏動圖地址。。
結果不盡人意,圖片動畫有點鈍,空間移動設備的影響與轉動影響比起來一樣甚至更爲厲害。咱們能夠經過屢次讀取並取它們的均值來抵消此負面影響,可是咱們仍是開看下當咱們引入陀螺儀的時候會發生什麼。
咱們不從使用startGyroUpdates
獲取陀螺儀的原始數據開始。。。咱們直接從deviceMotion
數據類型中獲取加速器和陀螺儀的組合數據。CM將用戶的運動與重力加速度兩塊分割開來而且提供了CMDeviceMotionData
實例變量作句柄。上述栗子的簡單代碼以下:
if manager.deviceMotionAvailable { manager.deviceMotionUpdateInterval = 0.01 manager.startDeviceMotionUpdatesToQueue(NSOperationQueue.mainQueue()) { [weak self] (data: CMDeviceMotionData!, error: NSError!) in let rotation = atan2(data.gravity.x, data.gravity.y) - M_PI self?.imageView.transform = CGAffineTransformMakeRotation(CGFloat(rotation)) } }
這樣看上去就好多了。。。原圖戳這裏。
咱們一樣可使用gyro/acceleration
中其餘非重力部分數據來添加交互方法。在這種情形下,咱們可使用CMDeviceMotionData
的userAcceleration
屬性來讓用戶經過觸碰設備左側的方式來回退導航層級。
記得X軸左邊對應負值。若是咱們能夠感應到用戶向作加速並大於2.5Gs的時候,這時候將促使咱們的試圖控制器出棧。實現代碼僅需數行:
if manager.deviceMotionAvailable { manager.deviceMotionUpdateInterval = 0.02 manager.startDeviceMotionUpdatesToQueue(NSOperationQueue.mainQueue()) { [weak self] (data: CMDeviceMotion!, error: NSError!) in if data.userAcceleration.x < -2.5 { self?.navigationController?.popViewControllerAnimated(true) } } }
效果是否是很棒,動圖展現戳這裏。
咱們從陀螺儀數據中能獲取的加速數據並非惟一的好東東,同時咱們從中還能知道設備在空間中的朝向。咱們能夠從CMDeviceMotionData
中attitude
屬性獲取CMAttitude
實例。CMAttitude
中含有三個能表明設備朝向的值:歐拉角度,四元組,還有一個旋轉矩陣。
每一個CMAttitude
都與相對應的幀相關。
// 略一段。。。
- CMAttitudeReferenceFrameXArbitraryZVertical 描述一個設備平鋪狀態,Z軸垂直X軸任性。在實際中,當你第一次啓動設備運動狀態更新的時候X軸將會被修正爲設備的朝向。
- CMAttitudeReferenceFrameXArbitraryCorrectedZVertical 本質上與上面相同可是在陀螺儀測試過程當中使用磁力儀進行校訂。使用磁力儀將增長CPU的開銷。
- CMAttitudeReferenceFrameXMagneticNorthZVertical 描述了設備平躺,而且X軸執行北極。這種設定可能須要你的用戶進行八個方向的校準操做。
- CMAttitudeReferenceFrameXTrueNorthZVertical 與第三個相同,可是這種北極的校訂差別故在除了磁力儀之後還須要地理位置數據。
咱們給出的意見是儘管「任性」。
三個維度的表達中,歐拉角度是最容易懂得,由於他們簡單描述了咱們對每一個座標軸轉動。pitch
是X周方向的轉動,增長的時候表示設備正朝你傾斜,減小的時候表示疏遠;roll
是Y軸的轉向,值減小的時候表示正往左邊轉,增長的時候往右;yaw
是Z軸轉向,減小是時候是順時針,增長的時候是逆時針。
上面的特性否知足右手定則。(是否是忽然想起了高中物理,咦不對我啥時候讀太高中,高中是什麼。。),即往手指指向方向走勢正值,反之負值。
解謎遊戲APP,朝向你的時候是題目+答案,背向你的時候是題目。
當出謎題的人點擊按鈕開始出題玩遊戲,咱們首相確認交互 - 注意用initialAttitude
方法拉動下deviceMotion
:
// get magnitude of vector via Pythagorean theorem func magnitudeFromAttitude(attitude: CMAttitude) -> Double { return sqrt(pow(attitude.roll, 2) + pow(attitude.yaw, 2) + pow(attitude.pitch, 2)) } // initial configuration var initialAttitude = manager.deviceMotion.attitude var showingPrompt = false // trigger values - a gap so there isn't a flicker zone let showPromptTrigger = 1.0 let showAnswerTrigger = 0.8
而後就是咱們所熟悉的方法調用startDeviceMotionUpdates
,咱們計算出三個歐拉角度的矢量大小而且使用它們做爲展現謎題答案的觸發器:
if manager.deviceMotionAvailable { manager.startDeviceMotionUpdatesToQueue(NSOperationQueue.mainQueue()) { [weak self] (data: CMDeviceMotion!, error: NSError!) in // translate the attitude data.attitude.multiplyByInverseOfAttitude(initialAttitude) // calculate magnitude of the change from our initial attitude let magnitude = magnitudeFromAttitude(data.attitude) ?? 0 // show the prompt if !showingPrompt && magnitude > showPromptTrigger { if let promptViewController = self?.storyboard?.instantiateViewControllerWithIdentifier("PromptViewController") as? PromptViewController { showingPrompt = true promptViewController.modalTransitionStyle = UIModalTransitionStyle.CrossDissolve self!.presentViewController(promptViewController, animated: true, completion: nil) } } // hide the prompt if showingPrompt && magnitude < showAnswerTrigger { showingPrompt = false self?.dismissViewControllerAnimated(true, completion: nil) } } }
效果戳這裏。
略
爲了避免影響用戶交互性,咱們通常把CoreMotionManager
的更新放在專屬的線程隊列而不是主線程隊列。NSOperationQueue
提供了addOperationWithBlock
便於咱們實現:
et queue = NSOperationQueue() manager.startDeviceMotionUpdatesToQueue(queue) { [weak self] (data: CMDeviceMotion!, error: NSError!) in // motion processing here NSOperationQueue.mainQueue().addOperationWithBlock { // update UI here } }
最後做者交代了下誤操做帶來一些不要的用戶體驗,在使用CM的時候要通過深思熟慮。好的電子可讓APP變得生動而且取悅用戶。