WWDC Quick Look 💓 By SwiftGGTeam
What's new in Core Motion

What's new in Core Motion

观看原视频

Highlight

Core Motion 在 iOS 17 和 watchOS 10 中新增三项能力:AirPods 头部追踪数据支持 macOS、Apple Watch Ultra 水下深度与温度监测、以及 HealthKit 运动期间的高频传感器数据流(800Hz 加速度计 / 200Hz 设备运动)。

核心内容

运动数据是 Apple 生态的隐形基础设施。计步、碰撞检测、空间音频——这些体验都建立在加速度计、陀螺仪、气压计等传感器之上。Core Motion 作为统一入口,让开发者也能使用这些数据。

AirPods 头部追踪扩展到 macOS

02:22)空间音频的动态头部追踪依赖与 iPhone 和 Apple Watch 相同的运动算法。CMHeadphoneMotionManager 从 iOS 14 起开放这些数据,让 App 能追踪用户头部的姿态、加速度和旋转。

今年这个能力扩展到 macOS 14。

import CoreMotion

let headphoneManager = CMHeadphoneMotionManager()
headphoneManager.delegate = self

// 检查设备是否支持
if headphoneManager.isDeviceMotionAvailable {
    headphoneManager.startDeviceMotionUpdates(to: .main) { motion, error in
        guard let motion = motion else { return }

        // 头部姿态(俯仰、偏航、翻滚)
        let attitude = motion.attitude

        // 用户加速度
        let acceleration = motion.userAcceleration

        // 旋转速率
        let rotation = motion.rotationRate

        // 数据来源:左耳还是右耳
        let location = motion.sensorLocation // .left or .right
    }
}

关键点:

  • 支持 AirPods Pro 等支持空间音频动态头部追踪的耳机
  • sensorLocation 区分数据来自左耳还是右耳
  • 开启自动入耳检测后,取出单耳会自动切换到另一耳继续传输
  • 需要 Info.plist 中配置 Motion Usage Description 权限描述

连接状态监听:

extension ViewController: CMHeadphoneMotionManagerDelegate {
    func headphoneMotionManagerDidConnect(_ manager: CMHeadphoneMotionManager) {
        // 耳机已连接,可以开始接收数据
    }

    func headphoneMotionManagerDidDisconnect(_ manager: CMHeadphoneMotionManager) {
        // 耳机断开,停止相关功能
    }
}

关键点:

  • delegate 方法在连接状态变化时触发
  • 自动入耳检测开启时,取出/放入耳机也会触发 disconnect/connect
  • 头戴式耳机的佩戴检测同样支持

Apple Watch Ultra 水下数据监测

07:22CMWaterSubmersionManager 利用 Apple Watch Ultra 的气压计,在浮潜、游泳等水上活动中追踪水深和水温。

import CoreMotion

let submersionManager = CMWaterSubmersionManager()
submersionManager.delegate = self

Delegate 实现:

extension WorkoutManager: CMWaterSubmersionManagerDelegate {
    // 浸没状态变化(入水/出水)
    func waterSubmersionManager(
        _ manager: CMWaterSubmersionManager,
        didUpdate event: CMWaterSubmersionEvent
    ) {
        switch event.state {
        case .notSubmerged:
            // 在水面上
        case .submergedShallow:
            // 水下 1 米以内
        case .submergedDeep:
            // 水下超过 1 米
        case .approachingMaxDepth:
            // 接近 6 米最大深度
        case .pastMaxDepth:
            // 超过 6 米
        case .sensorDepthError:
            // 传感器超出量程
        }
    }

    // 深度、压力、水温测量数据
    func waterSubmersionManager(
        _ manager: CMWaterSubmersionManager,
        didUpdate measurement: CMWaterSubmersionMeasurement
    ) {
        let depth = measurement.depth?.value      // 水深(米)
        let pressure = measurement.pressure?.value // 水压
        let surfacePressure = measurement.surfacePressure?.value // 水面气压
    }

    // 水温更新
    func waterSubmersionManager(
        _ manager: CMWaterSubmersionManager,
        didUpdate temperature: CMWaterTemperature
    ) {
        let waterTemp = temperature.temperature.value // 水温
        let uncertainty = temperature.uncertainty     // 不确定度
    }
}

关键点:

  • 仅 Apple Watch Ultra 支持,需要 watchOS 9+
  • 需要添加 “Shallow Depth and Pressure” capability
  • 水温数据仅在浸没状态下可用,刚入水时不确定度较高,随时间收敛
  • 最大监测深度 6 米,超出后进入 pastMaxDepth 状态
  • 深度分区:notSubmergedsubmergedShallow(小于 1m)→ submergedDeep(大于 1m)

高频传感器数据流 CMBatchedSensorManager

11:35)Apple Watch Series 8 和 Ultra 支持新的高频传感器数据流。与现有的 CMMotionManager(最高 100Hz,逐样本实时推送)不同,CMBatchedSensorManager 每秒批量传输一次数据,频率提升到 800Hz 加速度计和 200Hz 设备运动。

两种数据获取方式的对比:

特性CMMotionManagerCMBatchedSensorManager
加速度计频率最高 100Hz800Hz
设备运动频率最高 100Hz200Hz
传输方式逐样本实时推送每秒批量传输
延迟低(亚秒级)较高(秒级)
使用场景UI 反馈、实时控制运动分析、事后计算
前提条件需要活跃的 HealthKit Workout

13:18)演讲者以棒球挥棒为例演示了高频数据的价值。挥棒全程约 0.3 秒,100Hz 采样只能获得 30 个数据点,而 800Hz 能获得 240 个——足够分辨击球瞬间的细微振动差异。

详细内容

高频数据流的使用流程

16:17)使用 CMBatchedSensorManager 需要活跃的 HealthKit Workout Session:

import CoreMotion
import HealthKit

let batchedManager = CMBatchedSensorManager()

// 1. 确认设备支持
guard batchedManager.isAccelerometerSupported else { return }

// 2. 启动 HealthKit Workout
let workoutConfiguration = HKWorkoutConfiguration()
workoutConfiguration.activityType = .baseball
// ... 配置并启动 workout session

// 3. 使用 Swift async 接收批量数据
Task {
    do {
        for try await batch in batchedManager.accelerometerUpdates() {
            processAccelerometerBatch(batch)
        }
    } catch {
        // 处理授权错误或平台不支持
    }
}

关键点:

  • isAccelerometerSupportedisDeviceMotionSupported 检查设备支持
  • 仅 Apple Watch Series 8 和 Ultra 支持
  • 必须在活跃的 HealthKit Workout 中才能获取数据
  • 使用 Swift async/await 接口逐批处理数据
  • 循环中需要检查 workout 是否结束,避免无限循环

挥棒分析的算法实现

17:08)演讲者展示了如何用高频数据计算”击球接触时间”(time to contact):

步骤 1:检测击球瞬间

func detectImpact(from batch: [CMAccelerometerData]) -> TimeInterval? {
    // z 轴垂直于表冠方向,击球时会产生明显振动
    let filteredZ = batch.map { highPassFilter($0.acceleration.z) }

    // 找到滤波后信号的峰值
    guard let maxIndex = filteredZ.enumerated().max(by: { $0.element < $1.element })?.offset else {
        return nil
    }

    // 从原始数据获取对应时间戳
    return batch[maxIndex].timestamp
}

关键点:

  • z 轴方向垂直于 Apple Watch 表冠
  • 使用高通滤波器隔离高频冲击信号
  • 峰值对应击球瞬间

步骤 2:检测挥棒起始

func detectSwingStart(impactTime: TimeInterval, motionBuffer: [CMDeviceMotion]) -> TimeInterval? {
    // 从 impactTime 向前遍历,寻找旋转速率沿重力方向的分量
    // 从超过阈值变为低于阈值的转折点

    for sample in motionBuffer.reversed() {
        // 只检查 impactTime 之前的样本
        guard sample.timestamp < impactTime else { continue }

        // 挥棒持续时间不应超过合理范围(如 500ms)
        guard impactTime - sample.timestamp < 0.5 else { break }

        // 计算旋转速率沿重力方向的分量
        let rotationAlongGravity =
            sample.rotationRate.x * sample.gravity.x +
            sample.rotationRate.y * sample.gravity.y +
            sample.rotationRate.z * sample.gravity.z

        // 寻找从超过阈值到低于阈值的转变点
        if abs(rotationAlongGravity) < swingThreshold {
            return sample.timestamp
        }
    }

    return nil
}

关键点:

  • 从 impactTime 向前遍历设备运动数据
  • 旋转速率沿重力方向的分量反映手腕绕身体的旋转
  • 挥棒期间该分量显著非零,静止时接近零
  • 设置合理的挥棒持续时间上限过滤噪声

步骤 3:计算接触时间

func computeTimeToContact(impactTime: TimeInterval, swingStartTime: TimeInterval?) -> TimeInterval? {
    guard let startTime = swingStartTime else { return nil }

    // 验证挥棒期间累计旋转角度在合理范围
    let accumulatedRotation = computeAccumulatedRotation(from: startTime, to: impactTime)
    guard accumulatedRotation > minRotationThreshold && accumulatedRotation < maxRotationThreshold else {
        return nil
    }

    return impactTime - startTime
}

关键点:

  • 接触时间 = 击球瞬间时间戳 - 挥棒起始时间戳
  • 通过累计旋转角度验证检测到的挥棒是否有效
  • 过滤误检(如挥手、调整姿势等非挥棒动作)

击中和未击中的信号差异

20:45)演讲者对比了击中球和未击中球的加速度计信号。两种情况下挥棒动作本身相似,但击球瞬间的振动模式明显不同——击中时产生尖锐的冲击峰值,未击中时振动更平缓。800Hz 数据流能捕捉这种差异,100Hz 则难以区分。

核心启发

1. 头部追踪驱动的空间音频游戏

  • 做什么:用 AirPods 的头部姿态数据控制游戏视角或菜单选择
  • 为什么值得做:用户无需手持设备,转头即可操作,适合健身和 VR 类体验
  • 怎么开始CMHeadphoneMotionManager 获取 attitude 数据,映射到游戏相机的欧拉角

2. 水上运动记录 App

  • 做什么:为浮潜、自由潜水爱好者记录深度曲线、水温变化、水下时间
  • 为什么值得做:Apple Watch Ultra 的 CMWaterSubmersionManager 提供原生水深和水温 API,无需外接传感器
  • 怎么开始:配置 “Shallow Depth and Pressure” capability,实现 delegate 接收深度和温度更新,叠加到 HealthKit Workout 中

3. 运动姿势分析工具

  • 做什么:用 800Hz 加速度计和 200Hz 设备运动数据分析高尔夫挥杆、网球发球等动作
  • 为什么值得做:高频数据能捕捉 100Hz 无法分辨的细微动作差异,如击球瞬间的杆头速度、手腕翻转角度
  • 怎么开始:启动 HealthKit Workout,使用 CMBatchedSensorManager 的 async 接口批量获取数据,设计针对特定运动的检测算法

4. 健身动作计数与质量评估

  • 做什么:自动识别俯卧撑、深蹲等动作的完成次数,并评估动作质量
  • 为什么值得做:头部追踪数据可以判断身体姿态,高频加速度计能检测动作的节奏和幅度
  • 怎么开始:AirPods 提供头部姿态参考,CMBatchedSensorManager 提供手腕运动细节,两者结合构建全身运动模型

关联 Session

评论

GitHub Issues · utterances