访问 Core Motion 加速度计、陀螺仪、磁力计、设备运动、计步器、活动识别、海拔高度、耳机运动、批量高频锻炼运动以及水下浸没/深度数据。适用于读取设备传感器、计步、检测步行/跑步/驾驶/骑行、跟踪海拔高度、构建运动交互、处理 AirPods 头部跟踪或实现 watchOS 潜水/深度功能。
CoreMotion
使用 Core Motion 读取设备运动、计步器/活动、海拔高度、耳机、批量锻炼和浸没传感器。范围:Swift 6.3,iOS 26+。
目录
- 设置
- CMMotionManager:传感器数据
- 处理后的设备运动
- CMPedometer:步数和距离数据
- CMMotionActivityManager:活动识别
- CMAltimeter:海拔高度数据
- 更新间隔和电池
- 常见错误
- 审查清单
- 参考资料
设置
Info.plist
在 Info.plist 中添加 NSMotionUsageDescription,并提供一个面向用户的字符串,说明您的应用为何需要运动数据。缺少此键会导致应用在首次访问时崩溃。
<key>NSMotionUsageDescription</key>
<string>此应用使用运动数据来跟踪您的活动。</string>
授权
当 API 暴露了 authorizationStatus() 或 authorizationStatus 属性时(CMPedometer、CMMotionActivityManager、CMAltimeter、耳机运动、批量传感器和浸没),使用相应管理器的该属性。原始的 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:传感器数据
每个应用只创建一个 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()
// 在游戏循环/显示链接中:
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 实例
保留一个应用级别的 CMMotionManager;竞争实例会降低更新率。
不要:跳过传感器可用性检查
在启动每个传感器流之前立即应用相应的 is...Available 门控。
不要:忘记停止更新
每个 start 都应在对应的生命周期或任务取消路径中配对相应的 stop。
不要:使用不必要的高更新率
选择满足交互的最低速率,并使用更新间隔和电池表作为起点。
不要:假设所有 CMMotionActivity 属性互斥
// 错误——只检查一个属性
if activity.walking { handleWalking() }
// 正确——多个属性可能同时为真;检查置信度
if activity.walking && activity.confidence == .high {
handleWalking()
} else if activity.automotive && activity.confidence != .low {
handleDriving()
}
审查清单
- [ ]
NSMotionUsageDescription存在于 Info.plist 中,并附有清晰说明 - [ ] 单个
CMMotionManager实例在应用中共享 - [ ] 在启动更新前检查传感器可用性(
isAccelerometerAvailable等) - [ ] 在计步器/活动 API 前检查授权状态
- [ ] 更新间隔设置为可接受的最低频率
- [ ] 所有
start*Updates调用在生命周期对应部分有匹配的stop*Updates - [ ] 处理程序分派到适当的队列(不阻塞主线程进行繁重处理)
- [ ] 在根据活动类型采取行动前检查
CMMotionActivity.confidence - [ ] 在更新处理程序中检查错误参数
- [ ] 设备运动代码片段在请求特定姿态帧前调用
CMMotionManager.availableAttitudeReferenceFrames() - [ ] 根据实际需要选择姿态参考帧(不默认使用真北)






