僅做翻譯抒痒。
原文鏈接:https://nshipster.com/cmdevicemotion/
侵刪浓若。
在每臺 iPhone 光滑的玻璃下面犀概,一組傳感器依偎在邏輯板上野哭,向運動協(xié)處理器發(fā)送穩(wěn)定的數(shù)據(jù)流。
核心運動框架使這些傳感器的使用變得異常簡單胶逢,為用戶的交互打開了大門扳缕,超越了我們每天的輕敲和滑動。
Core Motion 讓您可以觀察 iOS 或 WatchOS 設(shè)備的位置和方向的變化并作出響應(yīng)举畸。得益于其專用的運動協(xié)處理器查排,iPhone、iPad 和 Apple Watch 可以連續(xù)讀取和處理來自內(nèi)置傳感器的輸入抄沮,而不必消耗 CPU 或耗盡電池電量跋核。
加速度計和陀螺儀數(shù)據(jù)被投影到一個三維坐標(biāo)空間岖瑰,設(shè)備的中心在原點。
對于縱向放置的iPhone:
- X軸從左(負值)到右(正值)移動設(shè)備的寬度砂代,
- Y軸從底部(-)到頂部(+)運行設(shè)備的高度蹋订,
- Z軸從后(-)到前(+)垂直穿過屏幕。
CMMotionManager
CMMotionManage 類負責(zé)提供有關(guān)當(dāng)前設(shè)備運動的數(shù)據(jù)刻伊。為了保持最高水平的性能露戒,在整個應(yīng)用程序中創(chuàng)建和使用一個共享的 CMMotionManager 實例。
TODO: OC 與 Swift 的實現(xiàn)不一致娃圆。
CMMotionManager 為傳感器信息提供了四個不同的接口玫锋,每個接口都有相應(yīng)的屬性和方法來檢查硬件可用性和訪問度量。
- accelerometer (加速計)測量加速度讼呢,或速度隨時間的變化撩鹿。
- gyroscope(陀螺儀) 測量姿態(tài)或設(shè)備的方位。
- magnetometer(磁強計)本質(zhì)上是一個羅盤悦屏,測量地球相對于該裝置的磁場节沦。
除了這些單獨的讀數(shù)之外,CMMotionManager還提供了一個統(tǒng)一的“device motion”接口础爬,該接口使用傳感器融合算法將來自每個傳感器的讀數(shù)組合成空間中設(shè)備的統(tǒng)一視圖甫贯。
Checking for Availability (可用性檢查)
雖然現(xiàn)在大多數(shù) Apple 設(shè)備都配備了一套標(biāo)準(zhǔn)的傳感器,但在嘗試讀取運動數(shù)據(jù)之前看蚜,最好先檢查一下當(dāng)前設(shè)備的功能叫搁。
以下示例涉及加速度計,但您可以將“加速度計”替換為您感興趣的運動數(shù)據(jù)類型(例如“陀螺儀”供炎、“磁強計”或“設(shè)備運動”):
let manager = CMMotionManager()
guard manager.isAccelerometerAvailable else {
return
}
Push vs. Pull
Core Motion提供對運動數(shù)據(jù)的“pull”和“push”訪問渴逻。
要“pull”運動數(shù)據(jù),可以使用CMMotionManager的只讀屬性之一訪問當(dāng)前讀數(shù)音诫。
要接收“push”數(shù)據(jù)惨奕,您可以使用一個在指定時間間隔接收更新的閉包來開始收集所需的數(shù)據(jù)。
Starting Updates to “pull” Data
manager.startAccelerometerUpdates()
這樣調(diào)用后竭钝,加速度計數(shù)據(jù)管理器可以隨時訪問設(shè)備的當(dāng)前加速計數(shù)據(jù)梨撞。
manager.accelerometerData
也可以通過讀取相應(yīng)的“is active”屬性來檢查運動數(shù)據(jù)是否可用。
manager.isAccelerometerActive
Starting Updates to “push” Data
manager.startAccelerometerUpdates(to: .main) { (data, error) in
guard let data = data, error == nil else {
return
}
…
}
以更新間隔提供的頻率調(diào)用傳遞的閉包香罐。(實際上卧波,核心運動強制執(zhí)行最小和最大頻率,因此指定超出該范圍的值會導(dǎo)致該值標(biāo)準(zhǔn)化穴吹;您可以通過檢查隨時間推移的運動事件的時間戳來確定當(dāng)前設(shè)備的有效間隔率幽勒。)
Stopping Updates
manager.stopAccelerometerUpdates()
Accelerometer in Action (加速計工作中)
假設(shè)我們想給我們的應(yīng)用程序的啟動頁面一個有趣的效果,這樣無論手機如何傾斜港令,背景圖像都保持水平啥容。
考慮以下代碼:
if manager.isAccelerometerAvailable {
manager.accelerometerUpdateInterval = 0.01
manager.startAccelerometerUpdates(to: .main) {
[weak self] (data, error) in
guard let data = data, error == nil else {
return
}
let rotation = atan2(data.acceleration.x,
data.acceleration.y) - .pi
self?.imageView.transform =
CGAffineTransform(rotationAngle: CGFloat(rotation))
}
}
首先,我們檢查以確保我們的設(shè)備能夠提供加速計數(shù)據(jù)顷霹。接下來我們指定一個高更新頻率咪惠。最后,我們開始更新將旋轉(zhuǎn)UIImageView屬性的閉包:
每個CMAccelerMeterData對象都包含一個x淋淀、y和z值—每個值都顯示該軸的加速度(其中1G=地球上的重力)遥昧。如果你的設(shè)備是靜止的,在縱向方向上筆直地站著朵纷,它會有加速度(0炭臭,-1,0)袍辞;平躺在桌子上鞋仍,它會是(0,0搅吁,-1)威创;向右傾斜45度,會是(0.707谎懦,-0.707肚豺,0)(dat√2 tho)。
我們利用加速度計數(shù)據(jù)中的x和y分量界拦,用雙參數(shù)反正切函數(shù)(atan2)計算旋轉(zhuǎn)吸申。然后我們使用計算旋轉(zhuǎn)的方法初始化cgafinetransform。不管手機怎么轉(zhuǎn)享甸,我們的圖像都應(yīng)該是正面朝上的——這里截碴,它是在國家航空航天博物館(我小時候最喜歡的博物館)的一個假想應(yīng)用程序中:
結(jié)果并不十分令人滿意——圖像的移動是不穩(wěn)定的,在太空中移動設(shè)備對加速度計的影響和旋轉(zhuǎn)一樣大甚至更大枪萄。這些問題可以通過對多個讀數(shù)進行采樣并取平均值來緩解隐岛,但是讓我們看看當(dāng)我們涉及到陀螺儀時會發(fā)生什么。
Adding the Gyroscope(加上陀螺儀)
不是使用原始的陀螺儀數(shù)據(jù)瓷翻,而是通過調(diào)用startGyroUpdates方法聚凹,讓我們通過請求統(tǒng)一的“設(shè)備運動”數(shù)據(jù)來獲得組合的陀螺儀和加速度計數(shù)據(jù)。使用陀螺儀齐帚,核心運動將用戶的運動與重力加速度分開妒牙,并將每一項作為CMDeviceMotion對象的自己的屬性。代碼與我們的第一個示例非常相似:
if manager.isDeviceMotionAvailable {
manager.deviceMotionUpdateInterval = 0.01
manager.startDeviceMotionUpdates(to: .main) {
[weak self] (data, error) in
guard let data = data, error == nil else {
return
}
let rotation = atan2(data.gravity.x,
data.gravity.y) - .pi
self?.imageView.transform =
CGAffineTransform(rotationAngle: CGFloat(rotation))
}
}
好多了对妄!
UIClunkController
我們也可以使用這個合成的陀螺/加速度數(shù)據(jù)中的另一個非重力部分來添加新的交互方法湘今。在本例中,讓我們使用CMDeviceMotion的userAcceleration屬性在用戶用手輕觸設(shè)備左側(cè)時向后導(dǎo)航剪菱。
請記住摩瞎,X軸橫向穿過我們手中的設(shè)備拴签,負值在左側(cè)。如果我們感覺到用戶左側(cè)的加速度超過2.5gs旗们,這就是我們從堆棧中彈出視圖控制器的提示蚓哩。該實現(xiàn)與我們前面的示例僅幾行不同:
if manager.isDeviceMotionAvailable {
manager.deviceMotionUpdateInterval = 0.01
manager.startDeviceMotionUpdates(to: .main) {
[weak self] (data, error) in
guard let data = data, error == nil else {
return
}
if data.userAcceleration.x < -2.5 {
self?.navigationController?.popViewControllerAnimated(true)
}
}
}
在詳細視圖中點擊設(shè)備,我們將立即返回展品列表:
Getting an Attitude
更好的加速度數(shù)據(jù)并不是我們通過包括陀螺儀數(shù)據(jù)獲得的唯一好處:我們現(xiàn)在也知道了該設(shè)備在太空中的真實方位上渴。此數(shù)據(jù)通過CMDeviceMotion對象的姿態(tài)屬性訪問岸梨,并封裝在CMAttitude對象中。CMAttitude包含設(shè)備方向的三種不同表示:
- Euler angles(歐拉角),
- A quaternion(四元數(shù))稠氮,
- A rotation matrix(旋轉(zhuǎn)矩陣)曹阔。
每一個都與給定的參考系有關(guān)。
Finding a Frame of Reference(尋找參照系)
你可以把參考坐標(biāo)系看作是設(shè)備的靜止方向隔披,從中可以計算出姿態(tài)赃份。所有四種可能的參考框架都描述了這種設(shè)備平放在桌子上,并且對其指向的方向越來越具體锹锰。
- CMAttitudeReferenceFrameXArbitraryZVertical描述了一種設(shè)備芥炭,它用一個“任意”X軸平放(垂直Z軸)。實際上恃慧,第一次啟動設(shè)備運動更新時园蝠,X軸固定在設(shè)備的方向上。
- CMAttitudeReferenceFrameXArbitraryCorrectedZVertical本質(zhì)上是相同的痢士,但是使用磁強計來校正陀螺儀測量值隨時間的變化彪薛。
- CMAttitudeReferenceFrameXMagneticNorthZVertical描述的是一種設(shè)備,它的X軸(即設(shè)備面向您時處于縱向模式的右側(cè))指向磁北怠蹂。此設(shè)置可能需要用戶使用他們的設(shè)備執(zhí)行8字形運動來校準(zhǔn)磁強計善延。
- CMAttitudeReferenceFrameXTrueNorthZVertical與上一個相同,但它會根據(jù)磁/真北偏差進行調(diào)整城侧,因此除了磁強計外易遣,還需要位置數(shù)據(jù)。
就我們的目的而言嫌佑,默認的“任意”參考系是可以的(稍后您將看到原因)豆茫。
Euler Angles
在三種姿態(tài)表示法中,歐拉角是最容易理解的屋摇,因為它們簡單地描述了圍繞我們已經(jīng)處理過的每個軸的旋轉(zhuǎn)揩魂。
- pitch(俯仰)是圍繞X軸旋轉(zhuǎn),隨著設(shè)備向您傾斜而增加炮温,隨著設(shè)備傾斜而減小
- roll(滾動)是圍繞Y軸旋轉(zhuǎn)火脉,隨著設(shè)備向左旋轉(zhuǎn)而減小,向右旋轉(zhuǎn)時增加
- yaw(偏航)是繞(垂直)Z軸旋轉(zhuǎn),順時針減小倦挂,逆時針增加畸颅。
這些值中的每一個都遵循所謂的“右手法則”:拇指朝上做一只杯狀的手,拇指指向三個軸中的任何一個妒峦。轉(zhuǎn)向你的指尖是積極的重斑,轉(zhuǎn)向是消極的兵睛。
Keep It To Yourself(你自己留著吧)
最后肯骇,讓我們試著用這個設(shè)備的態(tài)度來實現(xiàn)一個由兩個學(xué)習(xí)伙伴使用的閃存卡應(yīng)用程序的新交互。我們不會在提示和答案之間手動切換祖很,而是在設(shè)備轉(zhuǎn)動時自動翻轉(zhuǎn)視圖笛丙,這樣測試者看到答案,而被測試者只看到提示假颇。
從參考坐標(biāo)系中找出這種轉(zhuǎn)換是很困難的胚鸯。為了知道要監(jiān)視哪個角度,我們需要考慮設(shè)備的起始方向笨鸡,然后確定設(shè)備指向哪個方向姜钳。相反,我們可以保存一個CMAttitude實例形耗,并將其用作調(diào)整后的Euler角集的“零點”哥桥,調(diào)用multiply(byInverseOf:)方法來轉(zhuǎn)換所有未來的姿態(tài)更新。
當(dāng)測試者點擊按鈕開始測試時激涤,我們首先配置交互(注意initialAttitude的設(shè)備移動的“拉動”):
// get magnitude of vector via Pythagorean theorem
func magnitude(from 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
然后拟糕,在我們現(xiàn)在熟悉的startDeviceMotionUpdates調(diào)用中,我們計算由三個Euler角描述的向量的大小送滞,并將其作為觸發(fā)器來顯示或隱藏提示視圖:
if manager.isDeviceMotionAvailable {
manager.startDeviceMotionUpdates(to: .main) {
// translate the attitude
data.attitude.multiply(byInverseOf: initialAttitude)
// calculate magnitude of the change from our initial attitude
let magnitude = magnitude(from: data.attitude) ?? 0
// show the prompt
if !showingPrompt && magnitude > showPromptTrigger {
if let promptViewController =
self?.storyboard?.instantiateViewController(
withIdentifier: "PromptViewController"
) as? PromptViewController
{
showingPrompt = true
promptViewController.modalTransitionStyle = .crossDissolve
self?.present(promptViewController,
animated: true, completion: nil)
}
}
// hide the prompt
if showingPrompt && magnitude < showAnswerTrigger {
showingPrompt = false
self?.dismiss(animated: true, completion: nil)
}
}
}
在實現(xiàn)了所有這些之后犁嗅,讓我們來看看交互褂微。隨著設(shè)備旋轉(zhuǎn)哼蛆,顯示屏?xí)詣忧袚Q視圖腮介,quizee永遠看不到答案:
Further Reading(進一步閱讀)
我在前面瀏覽了CMAttitude的四元數(shù)和旋轉(zhuǎn)矩陣組件,但它們激發(fā)起我的興趣甘改。尤其是四元數(shù)十艾,有著有趣的歷史忘嫉,如果你想得夠久,它會讓你陷入特別艱難的思考康吵。
Queueing Up(排隊)
為了保持代碼示例的可讀性访递,我們將所有的運動更新發(fā)送到主隊列拷姿。一個更好的方法是將這些更新安排在它們自己的隊列上,然后調(diào)度回main來更新UI描滔。
let queue = OperationQueue()
manager.startDeviceMotionUpdates(to: queue) {
[weak self] (data, error) in
// motion processing here
DispatchQueue.main.async {
// update UI here
}
}
記住伴挚,并不是所有由核心運動產(chǎn)生的相互作用都是好的茎芋。通過運動導(dǎo)航可能很有趣蜈出,但也可能很難發(fā)現(xiàn),很容易意外觸發(fā)偷厦,而且可能并非所有用戶都可以訪問只泼。與無目的的動畫類似卵洗,過度使用花哨的手勢會使你更難集中精力完成手頭的任務(wù)。
謹(jǐn)慎的開發(fā)者會跳過那些讓人分心的噱頭聚至,找到使用設(shè)備動作來豐富應(yīng)用程序并取悅用戶的方法扳躬。