CareKit™ 是一个开源软件框架,用于创建帮助人们更好地了解和管理自身健康的应用程序。该框架提供可以直接使用,或经过扩展和定制以满足更具体用例的模块。它由三个 SPM 包组成,每个包都可以单独导入。
CareKit: 这是构建您的应用程序的最佳起点。CareKit 提供了将 CareKitUI 和 CareKitStore 结合在一起的视图控制器。这些视图控制器利用 Combine 提供存储和视图之间的同步。
CareKitUI: 提供整个框架中使用的视图。这些视图是 UIView 的开放且可扩展的子类。视图中的属性是公开的,允许完全控制内容。
CareKitStore: 提供 Core Data 解决方案来存储患者数据。它还提供了使用自定义存储的能力,例如第三方数据库或 API。
主要的 CareKit 框架代码库支持 iOS,需要 Xcode 12.0 或更高版本。 CareKit 框架的 Base SDK 版本为 13.0。
您可以使用 Swift Package Manager 安装 CareKit。创建一个新的 Xcode 项目,并导航到 File > Swift Packages > Add Package Dependency
。输入 URL https://github.com/carekit-apple/CareKit
并点击 Next
。选择 main
分支,并在下一个屏幕上,根据需要勾选相应的包。
要将本地化字符串添加到您的项目,请将字符串文件添加到您的项目:英文字符串
下载项目源代码并根据需要拖入 CareKit.xcodeproj、CareKitUI.xcodeproj 和 CareKitStore.xcodeproj。然后,通过将每个框架添加到目标的“Embedded Binaries”部分,将其嵌入到您的应用程序中,如下图所示。
随附的目录应用程序演示了 CareKit 中提供的不同模块:OCKCatalog
随附的示例应用程序演示了一个完全构建的 CareKit 应用程序:OCKSample
CareKit 是一个总括性的包,它提供视图控制器将 CareKitUI 和 CareKitStore 结合在一起。导入 CareKit 时,CareKitUI 和 CareKitStore 会在底层导入。
CareKit 提供全屏视图控制器,以方便使用。这些视图控制器查询并显示来自存储的数据,并与数据保持同步。
OCKDailyTasksPageViewController
: 显示每天的任务,并带有一个日历来翻页浏览日期。
OCKContactsListViewController
: 显示存储中的联系人列表。
对于 CareKitUI 中的每个卡片,CareKit 中都有一个对应的视图控制器。这些视图控制器是独立的模块,您可以使用标准视图控制器容器将其放置在任何位置。每个卡片的视图控制器都提供视图和存储之间的同步。以下代码创建了一个同步视图控制器。
// Create a store to hold your data.
let store = OCKStore(named: "my-store", type: .onDisk)
// Create a view controller that queries for and displays data. The view will update automatically whenever the data in the store changes.
let viewController = OCKSimpleTaskViewController(taskID: "doxylamine", eventQuery: OCKEventQuery(for: Date()), store: store)
所有同步视图控制器都有一个视图同步器。视图同步器定义如何实例化要显示的视图,以及当存储中的数据发生更改时如何更新视图。您可以自定义视图同步器并将它们注入到视图控制器中以执行自定义行为。
// Define a custom view synchronizer.
class CustomSimpleTaskViewSynchronizer: OCKSimpleTaskViewSynchronizer {
override func makeView() -> OCKSimpleTaskView {
let view = super.makeView()
// Customize the view when it's instantiated here.
return view
}
override func updateView(_ view: OCKSimpleTaskView, context: OCKSynchronizationContext<OCKTaskEvents>) {
super.updateView(view, context: context)
// Update the view when the data changes in the store here.
}
}
// Instantiate the view controller with the custom classes, then fetch and observe data in the store.
var query = OCKEventQuery(for: Date())
query.taskIDs = ["Doxylamine"]
let viewController = OCKSimpleTaskViewController(query: query, store: store, viewSynchronizer: CustomSimpleTaskViewSynchronizer())
CareKit 支持创建可以与同步视图控制器配对的自定义视图。这允许自定义视图和存储中的数据之间的同步。
// Define a view synchronizer for the custom view.
class TaskButtonViewSynchronizer: ViewSynchronizing {
// Instantiate the custom view.
func makeView() -> UIButton {
return UIButton(frame: CGRect(x: 0, y: 0, width: 200, height: 60))
}
// Update the custom view when the data in the store changes.
func updateView(
_ view: UIButton,
context: OCKSynchronizationContext<OCKAnyEvent?>
) {
let event = context.viewModel
view.titleLabel?.text = event?.task.title
view.isSelected = event?.outcome != nil
}
}
var query = OCKEventQuery(for: Date())
query.taskIDs = ["Doxylamine"]
let events = store
.anyEvents(matching: query)
.map { $0.first }
let viewController = SynchronizedViewController(
initialViewModel: nil,
viewModels: events,
viewSynchronizer: TaskButtonViewSynchronizer()
)
CareKitUI 提供卡片来表示任务、图表和联系人。每种卡片类别都有多种提供的样式。
您以类似的模式构建所有卡片。这使得识别和自定义每个卡片的属性变得容易。卡片顶部包含一个 headerView
,用于显示标签和图标。卡片的内容位于垂直 contentStackView
内部。这允许轻松地将自定义视图放置到卡片中,而不会破坏现有的约束。
有关从头开始创建卡片的信息,请参阅 OCKCardable
协议。遵循此协议使自定义卡片可以匹配整个框架中使用的样式。
以下是可用的任务卡片样式
此示例实例化并自定义说明任务卡片
let taskView = OCKInstructionsTaskView()
taskView.headerView.titleLabel.text = "Doxylamine"
taskView.headerView.detailLabel.text = "7:30 AM to 8:30 AM"
taskView.instructionsLabel.text = "Take the tablet with a full glass of water."
taskView.completionButton.isSelected = false
taskView.completionButton.label.text = "Mark as Completed"
以下是可用的图表卡片样式
此示例实例化并自定义条形图
let chartView = OCKCartesianChartView(type: .bar)
chartView.headerView.titleLabel.text = "Doxylamine"
chartView.graphView.dataSeries = [
OCKDataSeries(values: [0, 1, 1, 2, 3, 3, 2], title: "Doxylamine")
]
以下是可用的联系人卡片样式
此示例实例化并自定义简单的联系人卡片
let contactView = OCKSimpleContactView()
contactView.headerView.titleLabel.text = "Lexi Torres"
contactView.headerView.detailLabel.text = "Family Practice"
要提供跨框架的自定义样式或品牌,请参阅 OCKStylable
协议。所有可样式化的视图都从注入的常量列表中派生其外观。您可以自定义此常量列表,以便快速轻松地进行样式设置。
这是一个自定义视图及其所有子视图中的分隔符颜色的示例
// Define your custom separator color.
struct CustomColors: OCKColorStyler {
var separator: UIColor { .black }
}
// Define a custom struct to hold your custom color.
struct CustomStyle: OCKStyler {
var color: OCKColorStyler { CustomColors() }
}
// Apply the custom style to your view.
let view = OCKSimpleTaskView()
view.customStyle = CustomStyle()
请注意,CareKitUI 中的每个视图默认都使用 OCKStyle
进行样式设置。在视图上设置自定义样式会将自定义样式传播到尚未设置自定义样式的任何子视图。您可以在此图中可视化样式传播规则,该图演示了三个单独的视图层次结构
有关使用 OCKStylable
设置 SwiftUI 视图样式的信息,请参阅 CareKitUI 中的 SwiftUI。
CareKitStore 包定义了 CareKit 用于与数据存储通信的 OCKStoreProtocol
,以及利用 CoreData 的具体实现,称为 OCKStore
。它还包含 CareKit 依赖的大多数核心结构和数据类型的定义,例如 OCKAnyTask
、OCKTaskQuery
和 OCKSchedule
。
OCKStore
类是一个附加专用、版本化的存储,与 CareKit 打包在一起。它在 CoreData 之上实现,并提供快速、安全的设备上存储。 OCKStore
旨在与 CareKit 的同步视图控制器集成,但也可以单独使用。
import CareKitStore
let store = OCKStore(named: "my-store", type: .onDisk)
let breakfastSchedule = OCKSchedule.dailyAtTime(hour: 8, minutes: 0, start: Date(), end: nil, text: "Breakfast")
let task = OCKTask(id: "doxylamine", title: "Doxylamine", carePlanID: nil, schedule: breakfastSchedule)
let storedTask = try await store.addTask(task)
OCKStore
最重要的特性是它是一个具有时间概念的版本化存储。当使用日期范围查询存储时,返回的结果是指定间隔期间存储的状态。如果未提供日期间隔,则查询将返回实体的所有版本。
// On January 1st
let task = OCKTask(id: "doxylamine", title: "Take 1 tablet of Doxylamine", carePlanID: nil, schedule: breakfastSchedule)
let addedTask = try await store.addTask(task)
// On January 10th
let task = OCKTask(id: "doxylamine", title: "Take 2 tablets of Doxylamine", carePlanID: nil, schedule: breakfastSchedule)
let updatedTask = try await store.updateTask(task)
// On some future date.
let earlyQuery = OCKTaskQuery(dateInterval: /* Jan 1st - 5th */)
let earlyTasks = try await store.fetchTasks(query: earlyQuery)
let laterQuery = OCKTaskQuery(dateInterval: /* Jan 12th - 17th */)
let laterTasks = try await store.fetchTasks(query: laterQuery)
// Queries return the newest version of the task during the query interval.
let midQuery = OCKTaskQuery(dateInterval: /* Jan 5th - 15th */)
let midTasks = try await store.fetchTasks(query: laterQuery)
// Queries with no date interval return all versions of the task.
let allQuery = OCKTaskQuery()
let allTasks = try await store.fetchTasks(query: allQuery)
此图形可视化了在 CareKit 中查询版本化对象时如何检索结果。请注意,日期范围内的查询如何返回在该日期范围内有效的对象版本。
CareKitStore 在此图中定义了六个高级实体
Patient: 患者代表应用程序的用户。
Care Plan: 一个患者可以有零个或多个护理计划。护理计划组织与特定治疗相关的联系人和任务。例如,患者可能有一个针对心脏病的护理计划,另一个针对肥胖症的护理计划。
Contact: 护理计划有零个或多个相关联系人。联系人可能包括医生、护士、保险提供商或家人。
Task: 护理计划有零个或多个任务。任务表示患者执行的某些活动。示例包括服用药物、锻炼、写日记或与医生联系。
Schedule: 每个任务都必须有一个时间表。时间表定义任务的发生时间,并且可以选择指定目标或目标值,例如服用多少药物。
Outcome: 任务的每次发生都可能有一个相关的结果。缺少结果表明该任务的发生没有取得任何进展。
Outcome Value: 每个结果都有零个或多个与之关联的值。一个值可能代表服用了多少药物,或者多个结果值可能代表调查的答案。
重要的是要注意,任务、联系人和护理计划可以没有父实体而存在。许多 CareKit 应用程序针对明确定义的用例,因此通常可以简单地创建任务和联系人,而无需定义患者或护理计划。
CareKit 中提供的调度工具允许对任务进行非常精确和可定制的调度。您可以通过组合一个或多个 OCKScheduleElements
来创建 OCKSchedule
的实例。每个元素定义一个重复的间隔。
存在静态便捷方法来帮助处理常见的用例。
let breakfastSchedule = OCKSchedule.dailyAtTime(hour: 8, minutes: 0, start: Date(), end: nil, text: "Breakfast")
let everySaturdayAtNoon = OCKSchedule.weeklyAtTime(weekday: 7, hours: 12, minutes: 0, start: Date(), end: nil)
您可以通过组合计划元素或其他计划来创建高度精确、复杂的计划。
// Combine elements to create a complex schedule.
let elementA = OCKScheduleElement(start: today, end: nextWeek, interval: DateComponents(hour: 36))
let elementB = OCKScheduleElement(start: lastWeek, end: nil, interval: DateComponents(day: 2))
let complexSchedule = OCKSchedule(composing: [elementA, elementB])
// Combine two schedules into a composed schedule.
let dailySchedule = OCKSchedule.dailyAtTime(hour: 8, minutes: 0, start: tomorrow, end: nextYear, text: nil)
let crazySchedule = OCKSchedule(composing: [dailySchedule, complexSchedule])
计划还有许多其他有用的属性可以设置,包括目标值、持续时间和文本描述。
let element = OCKScheduleElement(
start: today, // The date and time this schedule begins.
end: nextYear, // The date and time this schedule ends.
interval: DateComponents(day: 3), // Occurs every 3 days.
text: "Before bed", // Show "Before bed" instead of clock time.
targetValues: [OCKOutcomeValue(10, units: "mL")], // Specifies what counts as "complete".
duration: Duration = .hours(2) // The window of time to complete the task.
)
text
: 默认情况下,CareKit视图控制器会提示用户使用时钟时间来执行任务,例如“晚上8:00”。如果您提供 text
属性,CareKit 将使用该文本来提示用户,例如上述代码中的“睡前”。
duration
: 如果您提供持续时间,CareKit 会提示用户在某个时间段内执行计划的任务,例如“晚上8:00 - 晚上10:00”。 如果您不想指定任何特定时间,您也可以将持续时间设置为 .allDay
。
targetValues
: CareKit 使用目标值来确定用户是否完成了特定任务。 有关更多信息,请参阅 OCKAdherenceAggregator
。
CareKit 提供的 OCKStore
类是一个快速、安全的设备端存储,可以满足大多数用例。 但它可能无法完全满足所有开发者的需求,因此 CareKit 还允许您编写自己的存储。 例如,您可以编写一个 Web 服务器的包装器,甚至是一个简单的 JSON 文件。 您可以使用任何符合 OCKStoreProtocol
的类来代替默认存储。
编写 CareKit 存储适配器需要定义存储中存在的实体,并为每个实体实现异步的创建、读取、更新和删除方法。 只要这些类型符合特定协议,存储就可以自由定义自己的类型。 例如,如果您正在编写一个可以保存任务的存储,您可以这样做。
import CareKitStore
struct MyTask: OCKAnyTask & Equatable & Identifiable {
// MARK: OCKAnyTask
let id: String
let title: String
let schedule: String
/* ... */
// MARK: Custom Properties
let difficulty: DifficultyRating
/* ... */
}
struct MyTaskQuery: OCKAnyTaskQuery {
// MARK: OCKAnyTaskQuery
let ids: [String]
let carePlanIDs: [String]
/* ... */
// MARK: Custom Properties
let difficult: DifficultyRating?
}
class MyStore: OCKStoreProtocol {
typealias Task = MyTask
typealias TaskQuery = MyTaskQuery
/* ... */
// MARK: Task CRUD Methods
func fetchTasks(query: TaskQuery, callbackQueue: DispatchQueue, completion: @escaping OCKResultClosure<[Task]>) { /* ... */ }
func addTasks(_ tasks: [Task], callbackQueue: DispatchQueue, completion: OCKResultClosure<[Task]>?) { /* ... */ }
func updateTasks(_ tasks: [Task], callbackQueue: DispatchQueue, completion: OCKResultClosure<[Task]>?) { /* ... */ }
func deleteTasks(_ tasks: [Task], callbackQueue: DispatchQueue, completion: OCKResultClosure<[Task]>?) { /* ... */ }
/* ... */
}
使用您提供的四个基本 CRUD 方法,CareKit 能够使用协议扩展来为您的存储注入额外的功能。 例如,一个为任务实现四个 CRUD 方法的存储会自动收到以下方法。
func fetchTask(withID id: String, callbackQueue: DispatchQueue, completion: @escaping OCKResultClosure<Task>)
func addTask(_ task: Task, callbackQueue: DispatchQueue, completion: OCKResultClosure<Task>?)
func updateTask(_ task: Task, callbackQueue: DispatchQueue, completion: OCKResultClosure<Task>?)
func deleteTask(_ task: Task, callbackQueue: DispatchQueue, completion: OCKResultClosure<Task>?)
提供的方法采用简单的实现方式。 您可以自由地提供自己的实现,利用底层数据存储的功能来实现更高的性能或效率。
如果您正在考虑实现自己的存储,请仔细阅读协议说明和文档。
GitHub 是我们 CareKit 的主要论坛。 欢迎您提出关于问题、疑问或想法的 issue。
此项目根据 BSD 许可条款提供。 请参阅 LICENSE 文件。