存取 Core Motion 加速度計、陀螺儀、磁力計、裝置動作、計步器、活動辨識、高度、耳機動作、批次高頻運動動作以及水下浸沒/深度資料。適用於讀取裝置感測器、計算步數、偵測走路/跑步/開車/騎車、追蹤高度、建立動作互動、處理 AirPods 頭部追蹤,或實作 watchOS 潛水/深度功能。
CoreMotion
使用 Core Motion 讀取裝置動作、計步器/活動、高度、耳機、批次運動以及浸沒感測器。範圍:Swift 6.3、iOS 26+。
目錄
- 設定
- CMMotionManager:感測器資料
- 處理後的裝置動作
- CMPedometer:步數與距離資料
- CMMotionActivityManager:活動辨識
- CMAltimeter:高度資料
- 更新間隔與電池
- 常見錯誤
- 審查清單
- 參考資料
設定
Info.plist
在 Info.plist 中加入 NSMotionUsageDescription,並提供使用者可理解的說明文字,解釋為何你的 App 需要動作資料。缺少此金鑰會導致首次存取時 App 崩潰。
<key>NSMotionUsageDescription</key>
<string>此 App 使用動作資料來追蹤你的活動。</string>
授權
當 API 提供授權狀態時(CMPedometer、CMMotionActivityManager、CMAltimeter、耳機動作、批次感測器以及浸沒),使用對應管理器的 authorizationStatus() 或 authorizationStatus 屬性。原始的 CMMotionManager 加速度計/陀螺儀/裝置動作串流沒有明確的授權請求 API;但仍需提供使用說明字串,並處理 start/update 回呼中的錯誤。
import CoreMotion
let status = CMMotionActivityManager.authorizationStatus()
switch status {
case .notDetermined:
// 首次使用時會提示
break
case .authorized:
break
case .restricted, .denied:
// 引導使用者前往設定
break
@unknown default:
break
}
CMMotionManager:感測器資料
每個 App 只建立一個 CMMotionManager。多個實例會降低感測器更新率。
import CoreMotion
let motionManager = CMMotionManager()
加速度計更新
guard motionManager.isAccelerometerAvailable else { return }
motionManager.accelerometerUpdateInterval = 1.0 / 60.0 // 60 Hz
motionManager.startAccelerometerUpdates(to: .main) { data, error in
guard let acceleration = data?.acceleration else { return }
print("x: \(acceleration.x), y: \(acceleration.y), z: \(acceleration.z)")
}
// 完成時:
motionManager.stopAccelerometerUpdates()
陀螺儀更新
guard motionManager.isGyroAvailable else { return }
motionManager.gyroUpdateInterval = 1.0 / 60.0
motionManager.startGyroUpdates(to: .main) { data, error in
guard let rotationRate = data?.rotationRate else { return }
print("x: \(rotationRate.x), y: \(rotationRate.y), z: \(rotationRate.z)")
}
motionManager.stopGyroUpdates()
輪詢模式(遊戲)
對於遊戲,啟動更新但不提供處理常式,並在每一幀輪詢最新樣本:
motionManager.startAccelerometerUpdates()
// 在你的遊戲迴圈 / display link 中:
if let data = motionManager.accelerometerData {
let tilt = data.acceleration.x
// 根據傾斜移動玩家
}
處理後的裝置動作
裝置動作融合加速度計、陀螺儀和磁力計,產生單一的 CMDeviceMotion 物件,包含姿態、使用者加速度(已移除重力)、旋轉速率和校正後的磁場。
在提供裝置動作指引時,請在程式碼片段中顯示執行階段的幀檢查,而不是硬編碼校正、磁北或真北幀。當偏好的幀不可用時,回退到 .xArbitraryZVertical。
guard motionManager.isDeviceMotionAvailable else { return }
let availableFrames = CMMotionManager.availableAttitudeReferenceFrames()
let frame: CMAttitudeReferenceFrame = availableFrames.contains(.xArbitraryCorrectedZVertical)
? .xArbitraryCorrectedZVertical
: .xArbitraryZVertical
motionManager.deviceMotionUpdateInterval = 1.0 / 60.0
motionManager.startDeviceMotionUpdates(
using: frame,
to: .main
) { motion, error in
guard let motion else { return }
let attitude = motion.attitude // roll, pitch, yaw
let userAccel = motion.userAcceleration
let gravity = motion.gravity
let heading = motion.heading // 相對於當前幀的角度
print("Pitch: \(attitude.pitch), Roll: \(attitude.roll)")
}
motionManager.stopDeviceMotionUpdates()
姿態參考幀
對於簡單的傾斜控制,使用 .xArbitraryZVertical 或 .xArbitraryCorrectedZVertical;它們避免了磁力計/定位的依賴。在請求校正、磁北或真北幀之前,請呼叫 CMMotionManager.availableAttitudeReferenceFrames() 並回退到可用的幀。
| 幀 | 使用情境 |
|---|---|
.xArbitraryZVertical |
預設。Z 軸垂直,X 軸在啟動時任意。大多數遊戲。 |
.xArbitraryCorrectedZVertical |
同上,但隨時間校正陀螺儀漂移。 |
.xMagneticNorthZVertical |
X 軸指向磁北。需要磁力計。 |
.xTrueNorthZVertical |
X 軸指向真北。需要磁力計 + 定位。 |
使用前檢查可用幀:
let available = CMMotionManager.availableAttitudeReferenceFrames()
if available.contains(.xTrueNorthZVertical) {
// 可以安全使用真北
}
CMPedometer:步數與距離資料
CMPedometer 提供步數、距離、配速、步頻和樓層數。
let pedometer = CMPedometer()
guard CMPedometer.isStepCountingAvailable() else { return }
// 歷史查詢
pedometer.queryPedometerData(
from: Calendar.current.startOfDay(for: Date()),
to: Date()
) { data, error in
guard let data else { return }
print("今日步數:\(data.numberOfSteps)")
print("距離:\(data.distance?.doubleValue ?? 0) 公尺")
print("上樓層數:\(data.floorsAscended?.intValue ?? 0)")
}
// 即時更新
pedometer.startUpdates(from: Date()) { data, error in
guard let data else { return }
print("步數:\(data.numberOfSteps)")
}
// 完成時停止
pedometer.stopUpdates()
可用性檢查
| 方法 | 檢查內容 |
|---|---|
isStepCountingAvailable() |
計步器硬體 |
isDistanceAvailable() |
距離估算 |
isFloorCountingAvailable() |
氣壓高度計(樓層) |
isPaceAvailable() |
配速資料 |
isCadenceAvailable() |
步頻資料 |
CMMotionActivityManager:活動辨識
偵測使用者是否靜止、走路、跑步、騎車或乘坐車輛。
let activityManager = CMMotionActivityManager()
guard CMMotionActivityManager.isActivityAvailable() else { return }
// 即時活動更新
activityManager.startActivityUpdates(to: .main) { activity in
guard let activity else { return }
if activity.walking {
print("走路中(信心度:\(activity.confidence.rawValue))")
} else if activity.running {
print("跑步中")
} else if activity.automotive {
print("在車輛中")
} else if activity.cycling {
print("騎車中")
} else if activity.stationary {
print("靜止")
}
}
activityManager.stopActivityUpdates()
歷史活動查詢
let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date())!
activityManager.queryActivityStarting(
from: yesterday,
to: Date(),
to: .main
) { activities, error in
guard let activities else { return }
for activity in activities {
print("\(activity.startDate): walking=\(activity.walking)")
}
}
CMAltimeter:高度資料
高度計存取由 NSMotionUsageDescription 涵蓋;透過不可用的資料和更新處理常式中的錯誤來處理被拒絕的動作存取。
let altimeter = CMAltimeter()
guard CMAltimeter.isRelativeAltitudeAvailable() else { return }
altimeter.startRelativeAltitudeUpdates(to: .main) { data, error in
guard let data else { return }
print("相對高度:\(data.relativeAltitude) 公尺")
print("氣壓:\(data.pressure) kPa")
}
altimeter.stopRelativeAltitudeUpdates()
絕對高度是相對於海平面的高度,而非 GPS 高度。首先檢查可用性。絕對高度僅在支援的硬體上可用,例如 iPhone 12 或後續機型,以及 Apple Watch Series 6、Apple Watch SE 或後續機型。
guard CMAltimeter.isAbsoluteAltitudeAvailable() else { return }
altimeter.startAbsoluteAltitudeUpdates(to: .main) { data, error in
guard let data else { return }
print("高度:\(data.altitude)m,精確度:\(data.accuracy)m")
}
altimeter.stopAbsoluteAltitudeUpdates()
更新間隔與電池
| 間隔 | Hz | 使用情境 | 電池影響 |
|---|---|---|---|
1.0 / 10.0 |
10 | UI 方向 | 低 |
1.0 / 30.0 |
30 | 休閒遊戲 | 中等 |
1.0 / 60.0 |
60 | 動作遊戲 | 高 |
1.0 / 100.0 |
100 | 最大速率(iPhone) | 非常高 |
使用滿足需求的最低頻率。不要假設所有裝置都有固定的最大取樣率。對於高頻運動動作,請在支援的情況下使用 CMBatchedSensorManager,並讀取其報告的 accelerometerDataFrequency 或 deviceMotionDataFrequency,而不是賦值給這些唯讀屬性。
常見錯誤
不要:建立多個 CMMotionManager 實例
保留一個 App 層級的 CMMotionManager;競爭的實例會降低更新率。
不要:跳過感測器可用性檢查
在啟動每個感測器串流之前,立即套用對應的 is...Available 檢查。
不要:忘記停止更新
每個 start 都必須在對應的生命週期或任務取消路徑中配對對應的 stop。
不要:使用不必要的高更新率
選擇滿足互動需求的最低速率,並以更新間隔與電池表格作為起點。
不要:假設所有 CMMotionActivity 屬性互斥
// 錯誤——只檢查一個屬性
if activity.walking { handleWalking() }
// 正確——多個屬性可能同時為 true;檢查信心度
if activity.walking && activity.confidence == .high {
handleWalking()
} else if activity.automotive && activity.confidence != .low {
handleDriving()
}
審查清單
- [ ]
NSMotionUsageDescription存在於 Info.plist 中,並附有清楚說明 - [ ] 單一
CMMotionManager實例在 App 中共享 - [ ] 在啟動更新前檢查感測器可用性(
isAccelerometerAvailable等) - [ ] 在計步器/活動 API 前檢查授權狀態
- [ ] 更新間隔設定為可接受的最低頻率
- [ ] 所有
start*Updates呼叫在生命週期中都有對應的stop*Updates - [ ] 處理常式分派到適當的佇列(不要在主佇列上進行大量處理)
- [ ] 在根據活動類型採取行動前檢查
CMMotionActivity.confidence - [ ] 在更新處理常式中檢查錯誤參數
- [ ] 裝置動作程式碼片段在請求特定姿態幀前呼叫
CMMotionManager.availableAttitudeReferenceFrames() - [ ] 根據實際需求選擇姿態參考幀(不要不必要地預設為真北)






