core-motion

core-motion

热门

访问 Core Motion 加速度计、陀螺仪、磁力计、设备运动、计步器、活动识别、海拔高度、耳机运动、批量高频锻炼运动以及水下浸没/深度数据。适用于读取设备传感器、计步、检测步行/跑步/驾驶/骑行、跟踪海拔高度、构建运动交互、处理 AirPods 头部跟踪或实现 watchOS 潜水/深度功能。

936Star
47Fork
更新于 2026/7/15
SKILL.md
readonly只读
name
core-motion
description

访问 Core Motion 加速度计、陀螺仪、磁力计、设备运动、计步器、活动识别、海拔高度、耳机运动、批量高频锻炼运动以及水下浸没/深度数据。适用于读取设备传感器、计步、检测步行/跑步/驾驶/骑行、跟踪海拔高度、构建运动交互、处理 AirPods 头部跟踪或实现 watchOS 潜水/深度功能。

CoreMotion

使用 Core Motion 读取设备运动、计步器/活动、海拔高度、耳机、批量锻炼和浸没传感器。范围:Swift 6.3,iOS 26+。

目录

设置

Info.plist

在 Info.plist 中添加 NSMotionUsageDescription,并提供一个面向用户的字符串,说明您的应用为何需要运动数据。缺少此键会导致应用在首次访问时崩溃。

<key>NSMotionUsageDescription</key>
<string>此应用使用运动数据来跟踪您的活动。</string>

授权

当 API 暴露了 authorizationStatus()authorizationStatus 属性时(CMPedometerCMMotionActivityManagerCMAltimeter、耳机运动、批量传感器和浸没),使用相应管理器的该属性。原始的 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,并读取其报告的 accelerometerDataFrequencydeviceMotionDataFrequency,而不是分配这些只读属性。

常见错误

不要:创建多个 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()
  • [ ] 根据实际需要选择姿态参考帧(不默认使用真北)

参考资料