healthkit
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHealthKit
HealthKit
Read and write health and fitness data from the Apple Health store. Covers authorization, queries, writing samples, background delivery, and workout sessions. Targets Swift 6.2 / iOS 26+.
从Apple Health存储中读取和写入健康与健身数据。内容涵盖授权、查询、写入样本、后台数据推送以及运动会话。适配Swift 6.2 / iOS 26+。
Contents
目录
Setup and Availability
配置与可用性
Project Configuration
项目配置
- Enable the HealthKit capability in Xcode (adds the entitlement)
- Add (read) and
NSHealthShareUsageDescription(write) to Info.plistNSHealthUpdateUsageDescription - For background delivery, enable the "Background Delivery" sub-capability
- 在Xcode中启用HealthKit功能(会添加权限配置)
- 在Info.plist中添加(读取权限说明)和
NSHealthShareUsageDescription(写入权限说明)NSHealthUpdateUsageDescription - 若使用后台推送,需启用“Background Delivery”子功能
Availability Check
可用性检查
Always check availability before accessing HealthKit. iPad and some devices do not support it.
swift
import HealthKit
let healthStore = HKHealthStore()
guard HKHealthStore.isHealthDataAvailable() else {
// HealthKit not available on this device (e.g., iPad)
return
}Create a single instance and reuse it throughout your app. It is thread-safe.
HKHealthStore在访问HealthKit之前,务必先检查其可用性。iPad和部分设备不支持该功能。
swift
import HealthKit
let healthStore = HKHealthStore()
guard HKHealthStore.isHealthDataAvailable() else {
// 此设备不支持HealthKit(例如iPad)
return
}创建单个实例并在整个应用中复用,它是线程安全的。
HKHealthStoreAuthorization
授权
Request only the types your app genuinely needs. App Review rejects apps that over-request.
swift
func requestAuthorization() async throws {
let typesToShare: Set<HKSampleType> = [
HKQuantityType(.stepCount),
HKQuantityType(.activeEnergyBurned)
]
let typesToRead: Set<HKObjectType> = [
HKQuantityType(.stepCount),
HKQuantityType(.heartRate),
HKQuantityType(.activeEnergyBurned),
HKCharacteristicType(.dateOfBirth)
]
try await healthStore.requestAuthorization(
toShare: typesToShare,
read: typesToRead
)
}仅请求应用实际需要的数据类型。请求过多权限的应用会被App Review拒绝。
swift
func requestAuthorization() async throws {
let typesToShare: Set<HKSampleType> = [
HKQuantityType(.stepCount),
HKQuantityType(.activeEnergyBurned)
]
let typesToRead: Set<HKObjectType> = [
HKQuantityType(.stepCount),
HKQuantityType(.heartRate),
HKQuantityType(.activeEnergyBurned),
HKCharacteristicType(.dateOfBirth)
]
try await healthStore.requestAuthorization(
toShare: typesToShare,
read: typesToRead
)
}Checking Authorization Status
检查授权状态
The app can only determine if it has not yet requested authorization. If the user denied access, HealthKit returns empty results rather than an error -- this is a privacy design.
swift
let status = healthStore.authorizationStatus(
for: HKQuantityType(.stepCount)
)
switch status {
case .notDetermined:
// Haven't requested yet -- safe to call requestAuthorization
break
case .sharingAuthorized:
// User granted write access
break
case .sharingDenied:
// User denied write access (read denial is indistinguishable from "no data")
break
@unknown default:
break
}应用只能判断是否尚未请求授权。如果用户拒绝访问,HealthKit会返回空结果而非错误——这是隐私设计的要求。
swift
let status = healthStore.authorizationStatus(
for: HKQuantityType(.stepCount)
)
switch status {
case .notDetermined:
// 尚未请求授权——可以安全调用requestAuthorization
break
case .sharingAuthorized:
// 用户授予了写入权限
break
case .sharingDenied:
// 用户拒绝了写入权限(读取权限被拒绝无法与“无数据”区分)
break
@unknown default:
break
}Reading Data: Sample Queries
读取数据:样本查询
Use (async/await) for one-shot reads. Prefer descriptors over the older callback-based .
HKSampleQueryDescriptorHKSampleQueryswift
func fetchRecentHeartRates() async throws -> [HKQuantitySample] {
let heartRateType = HKQuantityType(.heartRate)
let descriptor = HKSampleQueryDescriptor(
predicates: [.quantitySample(type: heartRateType)],
sortDescriptors: [SortDescriptor(\.endDate, order: .reverse)],
limit: 20
)
let results = try await descriptor.result(for: healthStore)
return results
}
// Extracting values from samples:
for sample in results {
let bpm = sample.quantity.doubleValue(
for: HKUnit.count().unitDivided(by: .minute())
)
print("\(bpm) bpm at \(sample.endDate)")
}使用(async/await)执行一次性读取操作。优先使用描述符而非旧的基于回调的。
HKSampleQueryDescriptorHKSampleQueryswift
func fetchRecentHeartRates() async throws -> [HKQuantitySample] {
let heartRateType = HKQuantityType(.heartRate)
let descriptor = HKSampleQueryDescriptor(
predicates: [.quantitySample(type: heartRateType)],
sortDescriptors: [SortDescriptor(\.endDate, order: .reverse)],
limit: 20
)
let results = try await descriptor.result(for: healthStore)
return results
}
// 从样本中提取值:
for sample in results {
let bpm = sample.quantity.doubleValue(
for: HKUnit.count().unitDivided(by: .minute())
)
print("\(bpm) 次/分,记录时间:\(sample.endDate)")
}Reading Data: Statistics Queries
读取数据:统计查询
Use for aggregated single-value stats (sum, average, min, max).
HKStatisticsQueryDescriptorswift
func fetchTodayStepCount() async throws -> Double? {
let calendar = Calendar.current
let startOfDay = calendar.startOfDay(for: Date())
let endOfDay = calendar.date(byAdding: .day, value: 1, to: startOfDay)!
let predicate = HKQuery.predicateForSamples(
withStart: startOfDay, end: endOfDay
)
let stepType = HKQuantityType(.stepCount)
let samplePredicate = HKSamplePredicate.quantitySample(
type: stepType, predicate: predicate
)
let query = HKStatisticsQueryDescriptor(
predicate: samplePredicate,
options: .cumulativeSum
)
let result = try await query.result(for: healthStore)
return result?.sumQuantity()?.doubleValue(for: .count())
}Options by data type:
- Cumulative types (steps, calories):
.cumulativeSum - Discrete types (heart rate, weight): ,
.discreteAverage,.discreteMin.discreteMax
使用获取聚合的单值统计数据(总和、平均值、最小值、最大值)。
HKStatisticsQueryDescriptorswift
func fetchTodayStepCount() async throws -> Double? {
let calendar = Calendar.current
let startOfDay = calendar.startOfDay(for: Date())
let endOfDay = calendar.date(byAdding: .day, value: 1, to: startOfDay)!
let predicate = HKQuery.predicateForSamples(
withStart: startOfDay, end: endOfDay
)
let stepType = HKQuantityType(.stepCount)
let samplePredicate = HKSamplePredicate.quantitySample(
type: stepType, predicate: predicate
)
let query = HKStatisticsQueryDescriptor(
predicate: samplePredicate,
options: .cumulativeSum
)
let result = try await query.result(for: healthStore)
return result?.sumQuantity()?.doubleValue(for: .count())
}按数据类型选择选项:
- 累积类型(步数、卡路里):
.cumulativeSum - 离散类型(心率、体重):,
.discreteAverage,.discreteMin.discreteMax
Reading Data: Statistics Collection Queries
读取数据:统计集合查询
Use for time-series data grouped into intervals -- ideal for charts.
HKStatisticsCollectionQueryDescriptorswift
func fetchDailySteps(forLast days: Int) async throws -> [(date: Date, steps: Double)] {
let calendar = Calendar.current
let endDate = calendar.startOfDay(
for: calendar.date(byAdding: .day, value: 1, to: Date())!
)
let startDate = calendar.date(byAdding: .day, value: -days, to: endDate)!
let predicate = HKQuery.predicateForSamples(
withStart: startDate, end: endDate
)
let stepType = HKQuantityType(.stepCount)
let samplePredicate = HKSamplePredicate.quantitySample(
type: stepType, predicate: predicate
)
let query = HKStatisticsCollectionQueryDescriptor(
predicate: samplePredicate,
options: .cumulativeSum,
anchorDate: endDate,
intervalComponents: DateComponents(day: 1)
)
let collection = try await query.result(for: healthStore)
var dailySteps: [(date: Date, steps: Double)] = []
collection.statisticsCollection.enumerateStatistics(
from: startDate, to: endDate
) { statistics, _ in
let steps = statistics.sumQuantity()?
.doubleValue(for: .count()) ?? 0
dailySteps.append((date: statistics.startDate, steps: steps))
}
return dailySteps
}使用获取按时间间隔分组的时间序列数据——非常适合用于图表展示。
HKStatisticsCollectionQueryDescriptorswift
func fetchDailySteps(forLast days: Int) async throws -> [(date: Date, steps: Double)] {
let calendar = Calendar.current
let endDate = calendar.startOfDay(
for: calendar.date(byAdding: .day, value: 1, to: Date())!
)
let startDate = calendar.date(byAdding: .day, value: -days, to: endDate)!
let predicate = HKQuery.predicateForSamples(
withStart: startDate, end: endDate
)
let stepType = HKQuantityType(.stepCount)
let samplePredicate = HKSamplePredicate.quantitySample(
type: stepType, predicate: predicate
)
let query = HKStatisticsCollectionQueryDescriptor(
predicate: samplePredicate,
options: .cumulativeSum,
anchorDate: endDate,
intervalComponents: DateComponents(day: 1)
)
let collection = try await query.result(for: healthStore)
var dailySteps: [(date: Date, steps: Double)] = []
collection.statisticsCollection.enumerateStatistics(
from: startDate, to: endDate
) { statistics, _ in
let steps = statistics.sumQuantity()?
.doubleValue(for: .count()) ?? 0
dailySteps.append((date: statistics.startDate, steps: steps))
}
return dailySteps
}Long-Running Collection Query
长期运行的集合查询
Use (plural) to get an that emits updates as new data arrives:
results(for:)AsyncSequenceswift
let updateStream = query.results(for: healthStore)
Task {
for try await result in updateStream {
// result.statisticsCollection contains updated data
}
}使用(复数形式)获取,当有新数据到达时会发出更新:
results(for:)AsyncSequenceswift
let updateStream = query.results(for: healthStore)
Task {
for try await result in updateStream {
// result.statisticsCollection包含更新后的数据
}
}Writing Data
写入数据
Create objects and save them to the store.
HKQuantitySampleswift
func saveSteps(count: Double, start: Date, end: Date) async throws {
let stepType = HKQuantityType(.stepCount)
let quantity = HKQuantity(unit: .count(), doubleValue: count)
let sample = HKQuantitySample(
type: stepType,
quantity: quantity,
start: start,
end: end
)
try await healthStore.save(sample)
}
Your app can only delete samples it created. Samples from other apps or Apple Watch are read-only.
创建对象并保存到存储中。
HKQuantitySampleswift
func saveSteps(count: Double, start: Date, end: Date) async throws {
let stepType = HKQuantityType(.stepCount)
let quantity = HKQuantity(unit: .count(), doubleValue: count)
let sample = HKQuantitySample(
type: stepType,
quantity: quantity,
start: start,
end: end
)
try await healthStore.save(sample)
}
应用只能删除自己创建的样本。来自其他应用或Apple Watch的样本为只读。
Background Delivery
后台数据推送
Register for background updates so your app is launched when new data arrives. Requires the background delivery entitlement.
swift
func enableStepCountBackgroundDelivery() async throws {
let stepType = HKQuantityType(.stepCount)
try await healthStore.enableBackgroundDelivery(
for: stepType,
frequency: .hourly
)
}Pair with an to handle notifications. Always call the completion handler:
HKObserverQueryswift
let observerQuery = HKObserverQuery(
sampleType: HKQuantityType(.stepCount),
predicate: nil
) { query, completionHandler, error in
defer { completionHandler() } // Must call to signal done
guard error == nil else { return }
// Fetch new data, update UI, etc.
}
healthStore.execute(observerQuery)Frequencies: , , ,
.immediate.hourly.daily.weeklyCall once (e.g., at app launch). The system persists the registration.
enableBackgroundDelivery注册后台更新,以便新数据到达时启动应用。需要后台推送权限。
swift
func enableStepCountBackgroundDelivery() async throws {
let stepType = HKQuantityType(.stepCount)
try await healthStore.enableBackgroundDelivery(
for: stepType,
frequency: .hourly
)
}搭配使用来处理通知。必须调用完成处理程序:
HKObserverQueryswift
let observerQuery = HKObserverQuery(
sampleType: HKQuantityType(.stepCount),
predicate: nil
) { query, completionHandler, error in
defer { completionHandler() } // 必须调用以告知系统处理完成
guard error == nil else { return }
// 获取新数据、更新UI等
}
healthStore.execute(observerQuery)推送频率: , , ,
.immediate.hourly.daily.weekly只需调用一次(例如在应用启动时),系统会保留该注册信息。
enableBackgroundDeliveryWorkout Sessions
运动会话
Use and to track live workouts. Available on watchOS 2+ and iOS 17+.
HKWorkoutSessionHKLiveWorkoutBuilderswift
func startWorkout() async throws {
let configuration = HKWorkoutConfiguration()
configuration.activityType = .running
configuration.locationType = .outdoor
let session = try HKWorkoutSession(
healthStore: healthStore,
configuration: configuration
)
session.delegate = self
let builder = session.associatedWorkoutBuilder()
builder.dataSource = HKLiveWorkoutDataSource(
healthStore: healthStore,
workoutConfiguration: configuration
)
session.startActivity(with: Date())
try await builder.beginCollection(at: Date())
}
func endWorkout(
session: HKWorkoutSession,
builder: HKLiveWorkoutBuilder
) async throws {
session.end()
try await builder.endCollection(at: Date())
try await builder.finishWorkout()
}For full workout lifecycle management including pause/resume, delegate handling, and multi-device mirroring, see .
references/healthkit-patterns.md使用和跟踪实时运动。支持watchOS 2+和iOS 17+。
HKWorkoutSessionHKLiveWorkoutBuilderswift
func startWorkout() async throws {
let configuration = HKWorkoutConfiguration()
configuration.activityType = .running
configuration.locationType = .outdoor
let session = try HKWorkoutSession(
healthStore: healthStore,
configuration: configuration
)
session.delegate = self
let builder = session.associatedWorkoutBuilder()
builder.dataSource = HKLiveWorkoutDataSource(
healthStore: healthStore,
workoutConfiguration: configuration
)
session.startActivity(with: Date())
try await builder.beginCollection(at: Date())
}
func endWorkout(
session: HKWorkoutSession,
builder: HKLiveWorkoutBuilder
) async throws {
session.end()
try await builder.endCollection(at: Date())
try await builder.finishWorkout()
}关于完整的运动生命周期管理(包括暂停/恢复、委托处理和多设备镜像),请参阅。
references/healthkit-patterns.mdCommon Data Types
常见数据类型
HKQuantityTypeIdentifier
HKQuantityTypeIdentifier
| Identifier | Category | Unit |
|---|---|---|
| Fitness | |
| Fitness | |
| Fitness | |
| Fitness | |
| Vitals | |
| Vitals | |
| Vitals | |
| Body | |
| Body | |
| Body | |
| Body | |
| Lab | |
| 标识符 | 分类 | 单位 |
|---|---|---|
| 健身 | |
| 健身 | |
| 健身 | |
| 健身 | |
| 生命体征 | |
| 生命体征 | |
| 生命体征 | |
| 身体指标 | |
| 身体指标 | |
| 身体指标 | |
| 身体指标 | |
| 实验室指标 | |
HKCategoryTypeIdentifier
HKCategoryTypeIdentifier
Common category types: , ,
.sleepAnalysis.mindfulSession.appleStandHour常见分类类型:, ,
.sleepAnalysis.mindfulSession.appleStandHourHKCharacteristicType
HKCharacteristicType
Read-only user characteristics: , , ,
.dateOfBirth.biologicalSex.bloodType.fitzpatrickSkinType只读用户特征:, , ,
.dateOfBirth.biologicalSex.bloodType.fitzpatrickSkinTypeHKUnit Reference
HKUnit 参考
swift
// Basic units
HKUnit.count() // Steps, counts
HKUnit.meter() // Distance
HKUnit.mile() // Distance (imperial)
HKUnit.kilocalorie() // Energy
HKUnit.joule(with: .kilo) // Energy (SI)
HKUnit.gramUnit(with: .kilo) // Mass (kg)
HKUnit.pound() // Mass (imperial)
HKUnit.percent() // Percentage
// Compound units
HKUnit.count().unitDivided(by: .minute()) // Heart rate (bpm)
HKUnit.meter().unitDivided(by: .second()) // Speed (m/s)
// Prefixed units
HKUnit.gramUnit(with: .milli) // Milligrams
HKUnit.literUnit(with: .deci) // Decilitersswift
// 基础单位
HKUnit.count() // 步数、计数
HKUnit.meter() // 距离
HKUnit.mile() // 距离(英制)
HKUnit.kilocalorie() // 能量
HKUnit.joule(with: .kilo) // 能量(国际单位制)
HKUnit.gramUnit(with: .kilo) // 质量(千克)
HKUnit.pound() // 质量(英制)
HKUnit.percent() // 百分比
// 复合单位
HKUnit.count().unitDivided(by: .minute()) // 心率(次/分)
HKUnit.meter().unitDivided(by: .second()) // 速度(米/秒)
// 带前缀的单位
HKUnit.gramUnit(with: .milli) // 毫克
HKUnit.literUnit(with: .deci) // 分升Common Mistakes
常见错误
1. Over-requesting data types
1. 请求过多数据类型
DON'T -- request everything:
swift
// App Review will reject this
let allTypes: Set<HKObjectType> = [
HKQuantityType(.stepCount),
HKQuantityType(.heartRate),
HKQuantityType(.bloodGlucose),
HKQuantityType(.bodyMass),
HKQuantityType(.oxygenSaturation),
// ...20 more types the app never uses
]DO -- request only what you use:
swift
let neededTypes: Set<HKObjectType> = [
HKQuantityType(.stepCount),
HKQuantityType(.activeEnergyBurned)
]错误示例——请求所有类型:
swift
// 会被App Review拒绝
let allTypes: Set<HKObjectType> = [
HKQuantityType(.stepCount),
HKQuantityType(.heartRate),
HKQuantityType(.bloodGlucose),
HKQuantityType(.bodyMass),
HKQuantityType(.oxygenSaturation),
// ...还有20个应用从未使用的类型
]正确做法——仅请求需要的类型:
swift
let neededTypes: Set<HKObjectType> = [
HKQuantityType(.stepCount),
HKQuantityType(.activeEnergyBurned)
]2. Not handling authorization denial
2. 未处理授权被拒绝的情况
DON'T -- assume data will be returned:
swift
func getSteps() async throws -> Double {
let result = try await query.result(for: healthStore)
return result!.sumQuantity()!.doubleValue(for: .count()) // Crashes if denied
}DO -- handle nil gracefully:
swift
func getSteps() async throws -> Double {
let result = try await query.result(for: healthStore)
return result?.sumQuantity()?.doubleValue(for: .count()) ?? 0
}错误示例——假设一定会返回数据:
swift
func getSteps() async throws -> Double {
let result = try await query.result(for: healthStore)
return result!.sumQuantity()!.doubleValue(for: .count()) // 授权被拒绝时会崩溃
}正确做法——优雅处理nil值:
swift
func getSteps() async throws -> Double {
let result = try await query.result(for: healthStore)
return result?.sumQuantity()?.doubleValue(for: .count()) ?? 0
}3. Assuming HealthKit is always available
3. 假设HealthKit始终可用
DON'T -- skip the check:
swift
let store = HKHealthStore() // Crashes on iPad
try await store.requestAuthorization(toShare: types, read: types)DO -- guard availability:
swift
guard HKHealthStore.isHealthDataAvailable() else {
showUnsupportedDeviceMessage()
return
}错误示例——跳过可用性检查:
swift
let store = HKHealthStore() // 在iPad上会崩溃
try await store.requestAuthorization(toShare: types, read: types)正确做法——先检查可用性:
swift
guard HKHealthStore.isHealthDataAvailable() else {
showUnsupportedDeviceMessage()
return
}4. Running heavy queries on the main thread
4. 在主线程运行重型查询
DON'T -- use old callback-based queries on main thread. DO -- use async descriptors:
swift
// Bad: HKSampleQuery with callback on main thread
// Good: async descriptor
func loadAllData() async throws -> [HKQuantitySample] {
let descriptor = HKSampleQueryDescriptor(
predicates: [.quantitySample(type: stepType)],
sortDescriptors: [SortDescriptor(\.endDate, order: .reverse)],
limit: 100
)
return try await descriptor.result(for: healthStore)
}错误示例——在主线程使用旧的基于回调的查询。正确做法——使用异步描述符:
swift
// 错误:在主线程使用HKSampleQuery回调
// 正确:使用异步描述符
func loadAllData() async throws -> [HKQuantitySample] {
let descriptor = HKSampleQueryDescriptor(
predicates: [.quantitySample(type: stepType)],
sortDescriptors: [SortDescriptor(\.endDate, order: .reverse)],
limit: 100
)
return try await descriptor.result(for: healthStore)
}5. Forgetting to call completionHandler in observer queries
5. 在观察者查询中忘记调用completionHandler
DON'T -- skip the completion handler:
swift
let query = HKObserverQuery(sampleType: type, predicate: nil) { _, handler, _ in
processNewData()
// Forgot to call handler() -- system won't schedule next delivery
}DO -- always call it:
swift
let query = HKObserverQuery(sampleType: type, predicate: nil) { _, handler, _ in
defer { handler() }
processNewData()
}错误示例——跳过完成处理程序:
swift
let query = HKObserverQuery(sampleType: type, predicate: nil) { _, handler, _ in
processNewData()
// 忘记调用handler()——系统不会安排下一次推送
}正确做法——始终调用:
swift
let query = HKObserverQuery(sampleType: type, predicate: nil) { _, handler, _ in
defer { handler() }
processNewData()
}6. Using wrong statistics options for the data type
6. 为数据类型使用错误的统计选项
DON'T -- use cumulative sum on discrete types:
swift
// Heart rate is discrete, not cumulative -- this returns nil
let query = HKStatisticsQueryDescriptor(
predicate: heartRatePredicate,
options: .cumulativeSum
)DO -- match options to data type:
swift
// Use discrete options for discrete types
let query = HKStatisticsQueryDescriptor(
predicate: heartRatePredicate,
options: .discreteAverage
)错误示例——对离散类型使用累积总和:
swift
// 心率是离散类型,不是累积类型——此查询会返回nil
let query = HKStatisticsQueryDescriptor(
predicate: heartRatePredicate,
options: .cumulativeSum
)正确做法——为数据类型匹配对应的选项:
swift
// 对离散类型使用离散选项
let query = HKStatisticsQueryDescriptor(
predicate: heartRatePredicate,
options: .discreteAverage
)Review Checklist
审核检查清单
- checked before any HealthKit access
HKHealthStore.isHealthDataAvailable() - Only necessary data types requested in authorization
- includes
Info.plistand/orNSHealthShareUsageDescriptionNSHealthUpdateUsageDescription - HealthKit capability enabled in Xcode project
- Authorization denial handled gracefully (nil results, not crashes)
- Single instance reused (not created per query)
HKHealthStore - Async query descriptors used instead of callback-based queries
- Heavy queries not blocking main thread
- Statistics options match data type (cumulative vs. discrete)
- Background delivery paired with and
HKObserverQuerycalledcompletionHandler - Background delivery entitlement enabled if using
enableBackgroundDelivery - Workout sessions properly ended and builder finalized
- Write operations only for sample types the app created
- 在访问任何HealthKit功能前,已检查
HKHealthStore.isHealthDataAvailable() - 授权时仅请求必要的数据类型
- Info.plist中包含和/或
NSHealthShareUsageDescriptionNSHealthUpdateUsageDescription - Xcode项目中已启用HealthKit功能
- 已优雅处理授权被拒绝的情况(返回nil结果而非崩溃)
- 复用单个实例(而非每次查询都创建新实例)
HKHealthStore - 使用异步查询描述符而非基于回调的查询
- 重型查询未阻塞主线程
- 统计选项与数据类型匹配(累积型 vs 离散型)
- 后台推送已搭配使用,且已调用
HKObserverQuerycompletionHandler - 若使用,已启用后台推送权限
enableBackgroundDelivery - 运动会话已正确结束,且builder已完成最终处理
- 仅对应用创建的样本类型执行写入操作
References
参考资料
- Extended patterns (workouts, anchored queries, SwiftUI integration):
references/healthkit-patterns.md - HealthKit framework
- HKHealthStore
- HKSampleQueryDescriptor
- HKStatisticsQueryDescriptor
- HKStatisticsCollectionQueryDescriptor
- HKWorkoutSession
- HKLiveWorkoutBuilder
- Setting up HealthKit
- Authorizing access to health data
- 扩展模式(运动、锚定查询、SwiftUI集成):
references/healthkit-patterns.md - HealthKit框架
- HKHealthStore
- HKSampleQueryDescriptor
- HKStatisticsQueryDescriptor
- HKStatisticsCollectionQueryDescriptor
- HKWorkoutSession
- HKLiveWorkoutBuilder
- 设置HealthKit
- 授权访问健康数据