WWDC Quick Look 💓 By SwiftGGTeam
What's new in CareKit

What's new in CareKit

观看原视频

Highlight

CareKit 2020 更新让这个开源健康管理框架获得 SwiftUI 任务卡、watchOS 支持、HealthKit 驱动任务、FHIR 映射和远程同步 API,开发者可以把护理计划、健康数据、临床记录和 iPhone/Apple Watch 状态同步放进同一套应用结构。

核心内容

做健康管理 App 时,界面和数据很容易分裂。用户看到的是每日拉伸、用药、问卷、症状记录和图表;开发者背后要维护任务日程、完成状态、HealthKit 数据、临床系统记录,还要处理 iPhone 与 Apple Watch 的同步。CareKit 的目标,是把这些护理场景拆成可复用的 UI、数据模型和同步层。

这场 session 先给出 CareKit 的模块边界:CareKitStore 提供健康领域的数据模型和 Core Data 持久层,CareKitUI 提供展示任务、图表和联系人的静态视图,CareKit 把 UI 和 store 连接起来,让 store 中的数据变化自动反映到界面上。(00:31

2020 年的变化,是把这些边界继续向外扩展。UI 层新增 SwiftUI 版本的任务卡和信息卡;同步视图可以把 store 中的任务自动映射成界面;CareKit、CareKitUI 和 CareKitStore 都能构建到 watchOS;store 层新增 HealthKit passthrough、FHIR coder 和远程同步协议。结果是,开发者不必把健康数据、护理任务和跨设备状态分别做成三套孤立流程。

Session 的演示很具体:同一个护理 App 里,用户在 iPhone 上记录肌肉抽筋,在 Apple Watch 上完成拉伸任务,iPhone 的任务卡、adherence ring 和图表会同步更新。CareKit 在这里承担的是应用结构,而不只是几张默认卡片。

详细内容

CareKitUI 新增 SwiftUI 任务卡

02:23SimpleTaskView 已经有 UIKit API,2020 年新增 SwiftUI API。最小用法只需要 title、detail 和完成状态。

import CareKitUI
import SwiftUI

struct MySimpleTaskView: View {

    var body: some View {
        SimpleTaskView(
            title: Text("Stretches"),
            detail: Text("15 minutes"),
            isComplete: false)
    }
}

关键点:

  • CareKitUI 提供的是静态视图,适合展示已经准备好的任务数据。
  • titledetail 接收 Text,所以可以继续使用 SwiftUI 的 view modifier。
  • isComplete 控制任务卡的完成状态,适合预览、静态 feed 或自定义数据源。

03:42)这张卡不是封闭组件。开发者可以注入自定义 header,也可以把内容附加到任意方向。演讲中用 HeaderView 和一条 accent bar 改造了默认头部,又用 CardView 包住任务卡、分割线和说明文字,把一张简单完成卡变成带说明的护理任务。

05:08)同一批 UI 更新还包括 LabeledValueTaskViewNumericProgressTaskViewOCKFeaturedContentViewOCKDetailViewLinkView。这些视图覆盖心率数值、运动目标进度、健康内容推荐、HTML 详情页和站内外链接入口,都是护理 App feed 中常见的卡片形态。

同步视图把 store 数据映射到 SwiftUI

08:56)静态视图解决展示问题,同步视图解决数据变化问题。CareKit.SimpleTaskView 接收 taskIDOCKEventQueryOCKSynchronizedStoreManager,再从 store 中定位当天任务并自动更新 UI。

// Synchronized Task View

import CareKit
import CareKitUI
import SwiftUI

struct MySynchronizedTaskView: View {

    let storeManager: OCKSynchronizedStoreManager

    var body: some View {
        CareKit.SimpleTaskView(
            taskID: "stretch",
            eventQuery: OCKEventQuery(for: Date()),
            storeManager: storeManager)

    }
}

关键点:

  • taskID 是 store 中任务的稳定标识,视图靠它找到护理任务。
  • OCKEventQuery(for: Date()) 把查询范围收敛到某一天的 event。
  • OCKSynchronizedStoreManager 监听 store 变化,任务完成状态改变后会驱动界面刷新。

09:26)如果默认映射不够,初始化器还提供 closure。closure 中可以访问 controller、task data 和 view model,再自己创建 CareKitUI.SimpleTaskView。演讲用这个入口把一个 CareKit 任务改造成 ResearchKit survey 的入口,用户点任务卡时弹出 ResearchKitSurvey()

HealthKit 驱动任务进入 CareKit store

12:52)CareKit 以前主要依赖 OCKStore。2020 年新增 OCKHealthKitPassthroughStore,它以 HealthKit 作为 source of truth,再和 Core Data 版本的 OCKStore 一起挂到 OCKStoreCoordinator 上。读数据时,coordinator 会聚合内部 stores;写数据时,它一次只写入一个 store,以保持事务边界清晰。

// Setting up the Store

import CareKit
import CareKitStore

let coreDataStore = OCKStore(name: "core-data-store")
let healthKitPassthroughStore = OCKHealthKitPassthroughStore(name: "hk-passthrough—store")

let coordinator = OCKStoreCoordinator()
coordinator.attach(store: coreDataStore)
coordinator.attach(eventStore: healthKitPassthroughStore)

let storeManager = OCKSynchronizedStoreManager(wrapping: coordinator)

关键点:

  • OCKStore 仍然负责 CareKit 自己的数据模型和持久化。
  • OCKHealthKitPassthroughStore 让 HealthKit 数据以 event store 的身份参与 CareKit 查询。
  • OCKStoreCoordinator 把两个 store 包在一起,外部使用方式接近单一 store。
  • 最后仍然用 OCKSynchronizedStoreManager 提供给同步视图。

14:15)HealthKit 驱动任务的例子是每天早上 8 点的 30 分钟运动目标。任务通过 OCKHealthKitLinkage 连接到 .appleExerciseTime,类型是 cumulative,单位是 minute。HealthKit 中的运动分钟数达到目标后,CareKit 任务可以自动完成。

// Adding HealthKit Linked Tasks to the Store

let schedule = OCKSchedule.dailyAtTime(
    hour: 8,
    minutes: 0,
    start: Date(),
    end: nil,
    text: nil,
    duration: .allDay,
    targetValues: [OCKOutcomeValue(30.0, units: "Minutes")])

let link = OCKHealthKitLinkage(
    quantityIdentifier: .appleExerciseTime,
    quantityType: .cumulative,
    unit: .minute())

let steps = OCKHealthKitTask(
    id: "exerciseMinutes",
    title: "Exercise Minutes",
    carePlanUUID: nil,
    schedule: schedule,
    healthKitLinkage: link)

storeManager.store.addAnyTask(steps)

关键点:

  • OCKSchedule.dailyAtTime 定义护理任务的发生时间和目标值。
  • targetValues 中的 OCKOutcomeValue(30.0, units: "Minutes") 会被任务视图用来展示目标。
  • OCKHealthKitLinkage 把 CareKit task 绑定到 HealthKit quantity。
  • OCKHealthKitTask 进入 store 后,可以用 NumericProgressTaskView 展示进度和目标。

FHIR coder 负责临床数据映射

15:23)健康行业里很多系统使用 FHIR 交换数据。CareKit 2020 增加 FHIR 兼容性,并通过新的开源框架 FHIRModels 映射 FHIR release。演讲明确提到 CareKit 支持 DSTU2 和 R4,并提供 coder 在 CareKit entity 与 FHIR data 之间转换。

// Encoding and Decoding FHIR Data

import CareKitStore
import CareKitFHIR

let coder = OCKR4PatientCoder()

// CareKit entity to FHIR data
let patient = OCKPatient(...)
let json = try! coder.encode(patient)

// FHIR data to CareKit entity
let data: Data = //...
let resourceData = OCKFHIRResourceData<R4, JSON>(data: data)
let patient: OCKPatient = try! coder.decode(resourceData)

关键点:

  • CareKitFHIR 是包含 coder 的 Swift Package Manager 包。
  • OCKR4PatientCoder 负责 R4 release 中 Patient 资源与 OCKPatient 的转换。
  • OCKFHIRResourceData<R4, JSON> 在类型层标记 FHIR release 和编码格式,避免把错误版本的数据交给 coder。
  • 这段能力适合把临床系统或 HealthKit clinical records 中的 FHIR 数据带入护理 App。

17:49)FHIR 和 CareKit 的模型不总是一一对应。Session 用 patient name 举例:如果默认映射会丢字段,开发者可以提供 closure,自行决定 CareKit name 如何写入 FHIR HumanName

// Customizing the Data Mapping

// Encoding process
coder.setFHIRName = { name, fhirPatient in // ModelsR4.Patient

    let humanName = HumanName()
    humanName.family = name.familyName.map { FHIRPrimitive(FHIRString($0)) }
    humanName.given = name.givenName.map { [FHIRPrimitive(FHIRString($0))] }

    fhirPatient.name = [humanName]
}

关键点:

  • setFHIRName 暴露 CareKit name 和 FHIRModels 的 patient 对象。
  • HumanName.familyHumanName.given 需要按 FHIRModels 的 primitive 类型封装。
  • 自定义映射的目的,是防止临床数据在 CareKit 与 FHIR 往返时丢失语义。

远程同步覆盖服务器和 Apple Watch

19:16)CareKit 新增远程同步 API,解决的问题是“如何把 CareKit App 中的数据同步到服务器”。App 侧的入口在 OCKStore:初始化时传入 remote,即可启用远程同步;调用 synchronize 会触发本地 store 与 remote store 同步。服务端或云提供商要实现 OCKRemoteSynchronizable,处理 delegate、自动同步开关、fetch changes、push revisions 和冲突解决策略。

25:54)演讲随后用同一套 remote sync API 做 iPhone 与 Apple Watch 同步。关键类是 OCKWatchConnectivityPeer。它遵守远程同步协议,让 iPhone 和 Apple Watch 可以把彼此当成 remote store。

private lazy var remote = OCKWatchConnectivityPeer()

关键点:

  • OCKWatchConnectivityPeer 是 CareKit 提供的 WatchConnectivity remote。
  • watchOS 端把它传给 OCKStore,store 就具备与配对 iPhone 同步的入口。
  • 同步协议的抽象没有绑定服务器,所以同一组概念也能用于设备间同步。

26:33)WatchConnectivity 的 session delegate 仍由开发者拥有。CareKit 需要开发者在收到消息时把 message 转交给 remote,再把 CareKit 生成的 reply 发回 iPhone。

class SessionDelegate: NSObject, WCSessionDelegate {

    let remote: OCKWatchConnectivityPeer
    let store: OCKStore

    init(remote: OCKWatchConnectivityPeer, store: OCKStore) {
        self.remote = remote
        self.store = store
    }

    func session(
        _ session: WCSession,
        activationDidCompleteWith activationState: WCSessionActivationState,
        error: Error?) {

        print("New session state: \(activationState)")
    }

    func session(
        _ session: WCSession,
        didReceiveMessage message: [String: Any],
        replyHandler: @escaping ([String: Any]) -> Void) {

        print("Received message from peer!")
    }
}

关键点:

  • activationDidCompleteWith 是首次同步的合适位置,演讲后续在这里调用 store.synchronize
  • didReceiveMessage 是 CareKit 消息进入 watchOS app 的入口。
  • 真实转发逻辑使用 remote.reply(to:store:) 生成 reply,再调用 WatchConnectivity 的 replyHandler
  • 这段配合完成后,Apple Watch 上完成拉伸任务,iPhone 上订阅同一 store 的视图会同步更新。

核心启发

  1. 做一个术后康复任务 feed

    • 做什么:把每日拉伸、冰敷、服药和疼痛记录做成 CareKit 任务卡。
    • 为什么值得做SimpleTaskViewInstructionsTaskViewNumericProgressTaskView 已经覆盖任务说明、完成按钮和目标进度。
    • 怎么开始:先用 OCKStore 存任务和 outcome,再用 OCKSynchronizedStoreManager 驱动当天的 SwiftUI 任务列表。
  2. 做一个 HealthKit 自动完成的运动目标

    • 做什么:让用户设置每天 30 分钟运动目标,达到 HealthKit 的 exercise minutes 后自动完成 CareKit 任务。
    • 为什么值得做:session 中的 OCKHealthKitTask 正是这个模式,HealthKit 数据成为任务完成依据。
    • 怎么开始:创建 OCKHealthKitPassthroughStore,通过 OCKHealthKitLinkage(quantityIdentifier: .appleExerciseTime, quantityType: .cumulative, unit: .minute()) 绑定任务。
  3. 做一个问卷触发型护理流程

    • 做什么:用户点 CareKit 任务卡后,打开 ResearchKit 问卷记录症状或副作用。
    • 为什么值得做:同步视图的 closure 可以自定义 task data 到 UI 的映射,演讲示例直接弹出 ResearchKitSurvey()
    • 怎么开始:用 CareKit.SimpleTaskView 的自定义 initializer 读取 controller.viewModel,在 action 中切换 @State 并呈现 survey。
  4. 做一个 iPhone 与 Apple Watch 同步的护理任务

    • 做什么:iPhone 展示完整护理计划和图表,Apple Watch 只展示当天最重要的两张任务卡。
    • 为什么值得做:CareKit 2020 支持 watchOS,并提供 OCKWatchConnectivityPeer 复用远程同步 API。
    • 怎么开始:在 watchOS extension 中创建 OCKStore(name:remote:),实现 WCSessionDelegate,把收到的 message 交给 remote.reply(to:store:)
  5. 做一个临床记录导入后的护理计划

    • 做什么:把医疗机构返回的 FHIR patient 或 medication order 转成 CareKit entity,用于生成后续护理任务。
    • 为什么值得做:CareKitFHIR 和 FHIRModels 让 CareKit 能处理 DSTU2、R4 等 FHIR release。
    • 怎么开始:根据数据版本选择 coder,例如 OCKR4PatientCoder,必要时用 setFHIRName 这类 closure 修正字段映射。

关联 Session

  • What’s new in ResearchKit — ResearchKit 的 onboarding、survey 和 active task 更新,补足 CareKit 中呈现 ResearchKit survey 的数据采集侧。
  • What’s new in HealthKit — HealthKit 在 iOS 14 和 watchOS 7 中新增 ECG、症状与 mobility 数据,解释 CareKit 接入健康数据的来源。
  • Synchronize health data with HealthKit — 深入讲解 HealthKit 数据版本、锚定查询和跨设备同步,补充 CareKit 远程同步场景。
  • Handling FHIR without getting burned — 介绍 FHIRModels 如何处理 HealthKit 或临床系统中的 FHIR 数据,是理解 CareKit FHIR coder 的前置内容。

评论

GitHub Issues · utterances