AnpherosAnpheros PlatformDevelopers

Try it free in the sandbox · first FHIR call in 5 minutesFree · 30 synthetic patients · no card

Apple Health (HealthKit) 转 FHIR

使用 Swift 读取 Apple Watch 和 Apple Health 数据(通过 HealthKit),按本地日期,并写入 FHIR R4 记录:权限、后台同步、Apple 的规则。

Apple Watch 数据存储在配对 iPhone 上的 Apple Health 中,读取它的唯一方法是使用 HealthKit,从该 iPhone 上的应用程序读取,并获得用户的许可。 要将其放入 FHIR 记录,请使用 HKStatisticsCollectionQuery 读取每日统计数据,将值发送到您的后端,并将每个指标作为具有 LOINC 代码和 UCUM 单位的 Observation 写入。本页展示了 Swift 代码(已针对 iOS 17 SDK 进行类型检查)、Apple 设置的权限和规则,以及这些值如何成为 FHIR R4 Observations。

Apple Health 中的内容

Apple Health 收集 iPhone 和 Apple Watch 测量的数据 — 心率、静息心率、心率变异性 (SDNN)、血氧、呼吸频率、步数、能量、睡眠阶段、心律不齐通知、ECG 记录 — 以及其他应用程序写入的数据:Garmin Connect、Oura、Withings Health Mate、智能秤、血压计、血糖仪。因此,读取 Apple Health 可以涵盖比 Apple Watch 更多的数据。

没有 HealthKit 服务器或 Web API。数据保留在设备上(以及用户的加密 iCloud 备份中);您的应用程序在本地读取它。

权限

  1. 在 Xcode 中开启 HealthKit 功能,并添加 NSHealthShareUsageDescription 到 Info.plist(如果您写入数据,则额外添加 NSHealthUpdateUsageDescription)。
  2. 请求您需要的类型。用户可以选择允许某些类型而拒绝其他类型,HealthKit 不会告知您的应用读取权限是否被拒绝:被拒绝的类型只会返回空数据。请设计“无数据”的界面,而不是错误界面。
  3. 在用户连接时请求权限,并附带解释原因的句子,而不是在首次启动时请求。

在 Swift 中读取每日值

import Foundation
import HealthKit

/// One day of Apple Health data, ready to send to your backend.
struct DailyValue: Codable {
    let metric: String      // "heart_rate", "steps"
    let day: String         // "2026-10-01", the user's local day
    let value: Double
    let min: Double?
    let max: Double?
}

let store = HKHealthStore()
let heartRate = HKQuantityType(.heartRate)
let steps = HKQuantityType(.stepCount)

func requestAccess() async throws {
    // Read-only: the app never writes to Apple Health.
    try await store.requestAuthorization(toShare: [], read: [heartRate, steps])
}

/// Daily statistics for the last `days` days, one value per local calendar day.
func dailyValues(_ type: HKQuantityType, metric: String, unit: HKUnit,
                 options: HKStatisticsOptions, days: Int) async throws -> [DailyValue] {
    let calendar = Calendar.current
    let end = calendar.startOfDay(for: Date()).addingTimeInterval(86_400)
    let start = calendar.date(byAdding: .day, value: -days, to: end)!
    let query = HKStatisticsCollectionQueryDescriptor(
        predicate: HKSamplePredicate.quantitySample(type: type,
                                                    predicate: HKQuery.predicateForSamples(withStart: start, end: end)),
        options: options,
        anchorDate: start,
        intervalComponents: DateComponents(day: 1))
    let collection = try await query.result(for: store)
    let fmt = DateFormatter()
    fmt.dateFormat = "yyyy-MM-dd"
    var out: [DailyValue] = []
    collection.enumerateStatistics(from: start, to: end) { stats, _ in
        let day = fmt.string(from: stats.startDate)
        if options.contains(.cumulativeSum), let sum = stats.sumQuantity() {
            out.append(DailyValue(metric: metric, day: day, value: sum.doubleValue(for: unit), min: nil, max: nil))
        } else if let avg = stats.averageQuantity() {
            out.append(DailyValue(metric: metric, day: day, value: avg.doubleValue(for: unit),
                                  min: stats.minimumQuantity()?.doubleValue(for: unit),
                                  max: stats.maximumQuantity()?.doubleValue(for: unit)))
        }
    }
    return out
}

func collect() async throws -> [DailyValue] {
    try await requestAccess()
    let bpm = HKUnit.count().unitDivided(by: .minute())
    let hr = try await dailyValues(heartRate, metric: "heart_rate", unit: bpm,
                                   options: [.discreteAverage, .discreteMin, .discreteMax], days: 30)
    let st = try await dailyValues(steps, metric: "steps", unit: .count(), options: .cumulativeSum, days: 30)
    return hr + st   // POST these to your backend, which writes them to the FHIR record
}

为何使用统计查询而非读取样本:HKStatisticsCollectionQuery 返回的每日总计和平均值是 Apple Health 已经跨来源去重的结果,因此 iPhone 和 Watch 计算的步数不会重复累加。直接读取原始样本并自行求和是导致步数统计膨胀的最常见原因。

这些天是用户的本地日历日(Calendar.current)。在将其写入 FHIR 记录时,请保持此格式,其中 Observation 的 effectivePeriod 是带有其时区偏移量的本地日期。

保持记录最新

从 Swift 值到 FHIR 观测

将 DailyValue 数组发送到您的后端,并为每个人、每项指标和每一天写入一个具有稳定标识符的 Observation,当同一天再次到来时进行更新。映射表(HealthKit 类型 → LOINC → UCUM)、一个经过验证的 JSON 示例以及执行此 upsert 操作并在平台上运行的 TypeScript 代码,都可以在 Wearable data in FHIR 中找到。此处两个指标的代码是:

HealthKit LOINC UCUM 每日值
heartRate 8867-4 Heart rate /min 平均值,包含最小值和最大值作为组件
stepCount 41950-7 Number of steps in 24 hour {steps} 总计

Apple 的健康数据规则

Apple Health Records

除了健身数据之外,Apple Health 还可以存储从参与的医疗系统(主要在美国)下载的临床记录。HealthKit 将它们暴露为 HKClinicalRecord 对象,这些对象携带原始的 FHIR 资源。读取它们需要单独的权限和授权。这与可穿戴设备数据的集成不同,Anpheros Daily 目前不读取它们。

Anpheros 的定位

Anpheros 平台存储结果:即用户的 FHIR R4 记录,其中每个可穿戴设备值都作为一个 Observation 记录,标记为设备数据、其他应用的同意、来源信息和访问日志。它本身不读取 Apple Health — 这部分由您的 iOS 应用完成。Anpheros 自有的应用 Anpheros Daily 会在 iPhone 上读取 Apple Health(仅限 Plus 和 Max 套餐),并将每日摘要写入记录中,使用上述代码。

常见问题解答

我可以从服务器读取 Apple Watch 数据吗?

不可以。Apple Watch 数据会同步到配对的 iPhone 上的 Apple Health,而 HealthKit 只能由该设备上的应用程序使用。应用程序读取数据并将其发送到您的服务器。

有 Apple Health 的 Web API 或 REST API 吗?

没有。Apple 不提供 Apple Health 的 Web 或 REST API:HealthKit 仅在 iPhone(或 Apple Watch)上的应用程序内运行。要在服务器上使用数据,您的应用程序需要获得用户许可后读取数据,然后将其发送,例如作为 FHIR Observations 发送到 Anpheros。

我需要 API 密钥或为 Apple Health API 付费吗?

无需密钥,也无需付费:HealthKit 是 iOS 的一部分。您需要在应用程序中启用 HealthKit 功能,为每种数据类型提供使用说明,并获得用户的许可;App Store 审核会检查您如何使用数据。

我如何知道用户是否拒绝访问 HealthKit 类型?

对于读取访问,您无法知道:HealthKit 会隐藏此信息以保护隐私,而拒绝的类型将不返回任何数据。将“无数据”视为正常状态,并提供一种在 Health 应用中查看权限的方式。

为什么我的步数计数比 Health 应用中的多?

您可能正在汇总来自多个来源(iPhone 和 Watch)的原始样本。请使用 HKStatisticsCollectionQuery 和 .cumulativeSum,它们会应用 Apple Health 自带的去重功能。

Apple Watch 心率变异性的 LOINC 代码是什么?

Apple Health 将 HRV 存储为 SDNN,这与 LOINC 80404-7 匹配。Health Connect 存储 RMSSD,这是一个不同的统计量,因此不要将两者混淆在一个代码下。

相关资源

Try it free in the sandbox · first FHIR call in 5 minutesFree · 30 synthetic patients · no card

Product updates by email

New features, guides and the end of the beta. One email a month at most.

Send me occasional Anpheros Platform updates by email (new features, API changes, guides). I'll confirm by email and can unsubscribe in one click from any email. Privacy