Highlight
HealthKit 在 visionOS 2 上完整可用,iPad 应用无需改动即可运行,原生应用则可通过空间计算实现图表自适应、多窗口对比和沉浸式健康体验。
核心内容
你的 iPad 健康应用想在 Apple Vision Pro 上跑起来,但不知道 HealthKit 支持到什么程度、需要改多少代码。visionOS 2 给出了答案:HealthKit 的读写、统计、通知能力与 iPadOS 基本一致,数据通过 iCloud 跨设备同步,授权配置也通用。两种应用形态都能用——基于 iOS 17 编译的 iPad 应用自动获得 Designed for iPad 体验,零改动;使用 visionOS SDK 的原生应用则可以充分利用空间画布。
Session 用一个情绪记录应用做案例,演示了从 iPad 适配到原生 visionOS 的三步走:第一步,让图表随窗口缩放自适应,用 onGeometryChange 动态调整数据点数量;第二步,通过 openWindow 支持多窗口并排对比;第三步,用 Full Immersive Space 做沉浸式情绪记录,保存后自动退出沉浸态回到工作空间。Guest User 场景下有特殊限制——已授权应用可以继续读取数据,但不能发起新授权,也不能写入数据,HealthKit 会返回一个新增的错误类型 errorNotPermissibleForGuestUserMode。
详细内容
HealthKit 在 visionOS 上的能力
HealthKit 在 visionOS 上的行为与 iPadOS 一致:读写健康数据、聚合统计、注册前后台数据变更通知全部支持(01:52)。用户在 Settings 中管理每个应用的授权,数据通过 iCloud 在设备间同步(02:04)。两种应用形态都可以使用 HealthKit:使用 visionOS SDK 的原生应用,以及基于 iOS 17 编译的 iPad 应用(02:16)。
基础用法跟其他平台一样。先检查可用性:
import HealthKit
if HKHealthStore.isHealthDataAvailable() {
// Configure HealthKit-powered experiences
} else {
// Omit HealthKit experiences
}
关键点:
isHealthDataAvailable()是所有 HealthKit 逻辑的入口,在 visionOS 上同样适用- 不同设备和 OS 版本可用性可能不同,必须检查
- 如果要兼容早期 visionOS 版本,这个检查尤其重要(02:43)
然后请求授权:
import HealthKitUI
import SwiftUI
func healthDataAccessRequest(
store: HKHealthStore,
shareTypes: Set<HKSampleType>,
readTypes: Set<HKObjectType>? = nil,
trigger: some Equatable,
completion: @escaping (Result<Bool, any Error>) -> Void
) -> some View
关键点:
healthDataAccessRequest是 SwiftUI 视图修饰符,触发系统授权弹窗shareTypes是你想写入的数据类型,readTypes是你想读取的类型trigger控制何时弹出授权,只在应用真正需要数据时才触发- 只请求你需要的类型,只在需要的时候请求(03:03)
图表自适应与多窗口
空间画布的核心优势是窗口可以自由缩放。用 onGeometryChange 让图表数据点随窗口大小动态变化:
import SwiftUI
import HealthKit
import Charts
struct ChartView: View {
@State var chartBinCount: Int
var body: some View {
Chart { ...
// Chart body
}
.onGeometryChange(for: Int.self) { proxy in
Int(proxy.size.width / 80) // 80 points per chart point
} action: { newValue in
chartBinCount = newValue
}
}
}
关键点:
onGeometryChange是 SwiftUI 新增 API,监听视图尺寸变化- 闭包
proxy -> Int根据宽度计算数据点数量,这里每 80pt 一个数据点 action闭包拿到新值后更新@State,驱动图表重绘- 窗口拉大时数据点变多,缩小时变少,用户始终看到合理密度的图表(05:59)
多窗口对比只需一个按钮:
struct NewChartViewerButton: View {
@Environment(\.openWindow) private var openWindow
var body: some View {
Button("Open In New Window", systemImage: "plus.rectangle.on.rectangle") {
openWindow(id: "chart-viewer-window")
}
}
}
关键点:
openWindow环境动作通过窗口标识符打开新窗口- iPad 上已支持 Split View 的应用,迁移到多窗口几乎没有额外工作
- 两个图表窗口并排放在空间画布上,比 Split View 更自由(06:33)
Guest User 场景的处理
Guest User 是 visionOS 的特性,允许其他人试用设备。HealthKit 在这个场景下有三条限制:已授权应用可以继续读取数据;不能发起新的授权请求,系统会返回错误;不能写入数据,防止访客数据混入机主健康库(08:13)。
写入失败时 HealthKit 返回新增的错误类型:
let sample = HKStateOfMind(date: date, kind: .momentaryEmotion, valence: valence,
labels: [label], associations: [association])
do {
try await healthStore.save(sample)
} catch {
switch error {
case HKError.errorNotPermissibleForGuestUserMode:
// Drop data generated in a Guest User session
default:
// Existing error handling
}
}
关键点:
HKError.errorNotPermissibleForGuestUserMode是 visionOS 新增的错误类型- 收到此错误时应当丢弃 Guest 生成的数据,避免后续意外写入机主的健康库
- 原有的错误处理逻辑放在
default分支,不影响(09:00)
授权请求在 Guest 会话中也会失败,需要在 healthDataAccessRequest 的回调中处理:
.healthDataAccessRequest(store: healthStore,
shareTypes: [.stateOfMindType()],
readTypes: [.stateOfMindType()],
trigger: toggleHealthDataAuthorization) { result in
switch result {
case .success: healthDataAuthorized = true
case .failure(let error as HKError):
switch (error.code) {
case .errorNotPermissibleForGuestUserMode:
// Defer requests for a later time
default:
// Existing error handling
}
}
}
关键点:
- Guest 会话中系统不会弹出授权弹窗,调用直接失败
- Apple 建议将授权请求延迟到非 Guest 会话再发起
- 读数据不受影响——Guest 继承机主已授予的权限(09:26)
最后给用户一个提示:
struct EventView: View {
@State private var showAlert: Bool = false
@State private var saveDetails: EmojiType.SaveDetails? = nil
var body: some View {
EmojiPicker()
.alert("Unable to Save Health Data",
isPresented: $showAlert,
presenting: saveDetails,
actions: { _ in },
message: { details in
Text(details.errorString)
})
}
}
关键点:
- Guest 写入失败后弹出 alert 告知数据未保存
- 这样 Guest 可以体验应用功能,但不会污染机主数据
- alert 用
presenting参数传入错误详情,动态显示错误信息(09:58)
核心启发
1. 图表类健康数据展示用 onGeometryChange 做响应式密度
- 为什么值得做:空间画布上窗口大小由用户决定,固定数据点数量的图表要么太稀疏要么太密集,体验差。
- 怎么开始:给 Chart 视图加
.onGeometryChange,按proxy.size.width / 常数算数据点数量,更新@State驱动重绘。
2. 对比场景开放多窗口,用 openWindow(id:) 一行搞定
- 为什么值得做:健康数据经常需要对比(本周 vs 上周、心率 vs 步数),两个独立窗口比一个窗口里的 Tab 切换直观得多。
- 怎么开始:在已有 Split View 支持的 iPad 应用上加
@Environment(\.openWindow),用窗口标识符打开新窗口,参考 Apple 示例项目 “Bringing multiple windows to your SwiftUI app”。
3. 所有 HealthKit 写入路径都必须处理 errorNotPermissibleForGuestUserMode
- 为什么值得做:Guest User 写入会静默失败并返回此错误,不处理就可能导致数据丢失或意外写入机主健康库。
- 怎么开始:在
healthStore.save()的 catch 分支和healthDataAccessRequest的 failure 回调中各加一个case .errorNotPermissibleForGuestUserMode,丢弃数据并弹出 alert 提示用户。
关联 Session
- Explore wellbeing APIs in HealthKit — 本 Session 使用的 State of Mind API 的详细介绍
- Elevate your windowed app for spatial computing — 将现有窗口应用适配 visionOS 的设计指南
- Create custom environments for your immersive apps in visionOS — 为沉浸式健康体验创建自定义环境
- Bringing multiple windows to your SwiftUI app — SwiftUI 多窗口支持的完整实现方案
评论
GitHub Issues · utterances