CareKit

CareKit

License Swift Versions OS's Xcode 14.0+ SPM

CareKit™ 是一个开源软件框架,用于创建帮助人们更好地了解和管理自身健康的应用程序。该框架提供了您可以直接使用,或者扩展和定制用于更有针对性的用例的模块。它由三个 SPM 包组成,每个包都可以单独导入。

目录

要求

主要的 CareKit 框架代码库支持 iOS,需要 Xcode 12.0 或更高版本。CareKit 框架的基本 SDK 版本为 13.0。

入门指南

选项一:使用 Swift Package Manager 安装

您可以使用 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”部分来将其嵌入到您的应用程序中,如下图所示。

embedded-framework

OCKCatalog 应用程序

包含的目录应用程序演示了 CareKit 中可用的不同模块: OCKCatalog

ockcatalog

OCKSampleApp

包含的示例应用程序演示了一个完全构建的 CareKit 应用程序: OCKSample

ocksample

CareKit

CareKit 是一个总括性的包,它提供了将 CareKitUI 和 CareKitStore 结合在一起的视图控制器。当导入 CareKit 时,CareKitUI 和 CareKitStore 会在底层导入。

列表视图控制器

CareKit 为了方便起见,提供了全屏视图控制器。这些视图控制器查询并显示来自存储的数据,并与数据保持同步。

同步视图控制器

对于 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

CareKitUI 提供了卡片来表示任务、图表和联系人。每个类别的卡片都有多种提供的样式。

您以类似的模式构建所有卡片。这使得识别和自定义每个卡片的属性变得容易。卡片顶部包含一个 headerView,用于显示标签和图标。卡片的内容位于垂直 contentStackView 内。这允许轻松地将自定义视图放入卡片中,而不会破坏现有的约束。

要从头开始创建卡片,请参阅 OCKCardable 协议。遵循此协议可以使自定义卡片与框架中使用的样式相匹配。

任务

以下是可用的任务卡片样式

Task

此示例实例化并自定义了说明任务卡

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"

图表

以下是可用的图表卡片样式

Chart

此示例实例化并自定义了条形图

let chartView = OCKCartesianChartView(type: .bar)

chartView.headerView.titleLabel.text = "Doxylamine"

chartView.graphView.dataSeries = [
    OCKDataSeries(values: [0, 1, 1, 2, 3, 3, 2], title: "Doxylamine")
]

联系人

以下是可用的联系人卡片样式

Contact

此示例实例化并自定义了简单联系人卡

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 进行样式设置。在视图上设置自定义样式会将自定义样式传播到尚未设置自定义样式的任何子视图。您可以在此图中可视化样式传播规则,该图演示了三个单独的视图层次结构

Styling

有关使用 OCKStylable 样式化 SwiftUI 视图的信息,请参阅 CareKitUI 中的 SwiftUI

CareKitStore

CareKitStore 包定义了 OCKStoreProtocol,CareKit 使用它与数据存储通信,以及一个利用 CoreData 的具体实现,称为 OCKStore。它还包含 CareKit 依赖的大多数核心结构和数据类型的定义,例如 OCKAnyTaskOCKTaskQueryOCKSchedule

存储

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 中查询版本化对象时如何检索结果。请注意,日期范围上的查询如何返回在该日期范围内有效的对象版本。
3d608700-5193-11ea-8ec0-452688468c72

模式

CareKitStore 在此图中定义了六个高级实体

Schema

重要的是要注意,任务、联系人和护理计划可以存在而没有父实体。许多 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.
)

自定义存储和类型

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 文件。