大多数浏览器和
Developer App 均支持流媒体播放。
-
CareKit 有哪些更新
使用 CareKit 构建多功能的研究和护理 app:了解我们对健康框架的最新改进,包括其模块化体系结构的新视图、数据存储的更新以及更紧密地与 iOS 上其他的框架集成。并了解开源社区如何继续利用 CareKit ,让开发人员既能保护隐私,还能突破数字健康的界限。
资源
相关视频
WWDC20
-
下载
(你好 WWDC 2020)
大家好 欢迎来到 WWDC (Carekit 有哪些更新) 大家好 我叫 Gavi Rawson 我是一名软件工程师 我是 Apple 健康团队的成员 我开发 Carekit 框架 稍后我的同事 Erik Hornberger 会加入我们 他也开发这个框架 今天我们要为大家讲的是 我们今年对 Carekit 做的更新 那么什么是 Carekit? Carekit 是一个开源的框架 可以帮助大家构建精美的健康 app 这个框架分为 3 个部分 即 Carekit、CarekitUI 以及 CarekitStore 每一部分本身又是一个框架 可以使用 Swift Package Manager 导入 CareKitStore 提供 为健康优化的数据模型 并为持久性提供核心数据层 CareKitUI 提供绝佳的 静态视图来展示该数据 CareKit 通过同步用户界面和存储层 实现两者的紧密联合 当存储的数据变化时 视图会自动更新 去年我们用 Swift 重构了这个框架 我们确保在设计框架时使它 既对初学者易用 又能对高阶开发者提供足够的自定义钩子 今年 我们把重点放在强化框架上 并制作更多的新工具 使大家在创建健康 app 时更轻松 我们就不给各位展示无聊的事项表了 而是为 CareKit 的所有新功能 创造了一套贴纸 毕竟谁不喜欢贴纸呢? 今天演讲结束时 我们会收集所有贴纸 粘贴到我们的 MacBook 上
那么今天我们先说 CareKitUI 它能提供多种视图 将任务、图表、联系人可视化 今年我们会为框架加入更多视图 我们看看这些新视图 演示环境是我们 用 CareKit 制作的健康类应用 这里是我的个人信息流 展示了一列与我相关的卡片 这里的每一张卡片 都是我们为框架增加的新视图 第一个是 SimpleTaskView 它提醒我每天做拉伸 如果你们用过 CareKit 你们会觉得这个这个视图很眼熟 我们已经有了 SimpleTaskView 的 UIKit API 现在我们还为它加入了 SwiftUI API 我们来详细看看 Swift UI API 我们首先导入 CareKitUI 和 SwiftUI 然后编写一些样板 SwiftUI 代码 然后我们对主体增加 SimpleTaskView 我们初始化的视图 含一个标题、一条细节 以及一个判定任务是否完成的标志 但这个视图只是起点 所以我们假设你们想要自定义一下 我们来再进一步观察标题和细节的参数 它接受的是 SwiftUI 文本而不是字符串
因此你们可以使用自定义的视图修饰器 来装饰标题和细节 你们能看到我们通过把标题的字重调细 来弱化标头的强调感
我们改回默认的字重 然后看看另一种可用的自定义点
SwiftUI 鼓励大家构建小视图组件 并在之后把它们组合到一起 创建更丰富且实用的视图 简单任务视图也是用小视图组件构建的 我们有细节展示和标头 如果需要的话 你们可以选择将自定义好的标头 或者细节展示注入到视图中 以代替我们为大家提供的默认风格 我们试试给这个视图自定义一个标头
我们回来看代码 使用另一种可用的初始化器 它为我们提供一种闭包 可用来构建我们的自定义标头 CareKitUI 提供了几个小视图组件 以便于我们进行这一过程 特别地说 我们可以使用标头视图 帮助我们与其它 CareKitUI 卡片风格匹配 我们把一条标题和一条细节传递给标头 将其放在 HStack 中的 自定义强调色条边上
这样视图就有了自定义的感觉 但我们更进一步 使用另一个自定义点 (做伸展 15 分钟) 我们可以把自定义视图 附加到内容的任何一边 这样我们可以向任意方向展开卡片 我们看看怎么用代码 把内容附加到视图底部
首先我们创建一个新视图 以提供一些工作空间 然后我们把 SimpleTaskView 包装到 CardView 中 并把它放置在一个分隔线和指示文本旁边 CardView 是另一个 CareKitUI 组件 在将一张卡片包装在另一张卡片中时 只有最外层的卡片会被展示 因此我们可以把所有内容 放在单张卡片里 这样 我们就完成了自定义视图的创建 你们可以看到它和开始时已经大不相同 我们开始时创建的是简单任务视图 只包括一个标头和一个完成按钮 现在我们有了自定义的强调色条 和任务的详细指示 我们回到这个健康应用中 看看 CareKitUI 中的一些新视图
下一个新视图是 LabeledValueView 这种视图很适合展示 一个数值和与之相关的单位 这里我们展示了我的心率是每分钟62下 看起来很显眼
我们可以通过提供标题 和细节文本创建这种视图 就像前一个视图那样 我们还可以提供完成状态 如果你们想进一步自定义视图 我们在视图中提供相同的钩子 与我们在简单任务视图中的相同
CareKitUI 的下一个新增内容是 NumericProgressTaskView 它有助于展示我对一个目标的累积进展 这里大家能看到我锻炼了22分钟 还在接近我的30分钟目标 这是一个催促我完成目标的小提醒
要创建这种视图 我们再次传递要在标头展示的标题和细节 我们还提供任务指示 以及进展和目标的文本 最后我们传递一个标志 判别任务是否完成
下一个新视图是 FeaturedContentView 这个视图非常适合强调重要信息 比如需要阅读的文章 这里有一个好文章 讲的是简单又健康的食谱 我可以在家自行烹调
FeaturedContentView 的创建 和前面的视图有些不同 我们首先导入 CareKitUI 和 UIKit 接着我们定义视图 然后设置背景大图和视图底部的文本
如果点按 FeaturedContentView 我们可以展示新的 DetailView DetailView 支持在图下方的内容中 使用 HTML 和 CSS 因此大家几乎可以把任何内容加入其中
创建视图的方法和前一个相似 我们首先导入 CareKitUI 和 UIKit 然后我们定义 styledHTML 它是 HTML 和其相关联的 CSS 样式的组合
然后我们将这个视图实体化 通过传递 styledHTML 和一个标志 以决定是否在右上角 显示关闭的 X 按钮 最后我们设置图片 就像之前一样
最后一个新视图是 LinkView 非常适合用于展示 能在应用内或应用外 直接展示新视图的按钮 我这里有几个链接 能帮我安排跟理疗师的预约
创建这个视图要提供标题、指示 以及要展示的链接 我们在这里创建链接按钮 以在 app 内打开网页
这些链接选项只是我们提供的 众多选择之中的几个 我们还提供其他选项 如导航至 App Store 但如果我们的选项 不适用大家的使用情况 那么还有个选项可以提供自定义 URL 我们来到了 app 中的今日信息流底部 我们也完成了对添加到框架中的 所有新视图进行介绍 这只是新视图中的少数几个 我们其实正在为 UIKit 和 SwiftUI API 制定路线图 我们期待各位社群成员们 利用我们提供的视图 并用新视图针对新的使用情形 对框架进行优化 现在我们可以拿出 CareKitUI 视图的第一张贴纸 然后把它粘在 WW 贴纸旁边 我们刚刚看到了 如何在 CareKitUI 中创建静态视图 但 CareKit 还能再进一步 它可以把静态视图包装在同步层中 这样当存储的数据更新后 用户界面会反映其变化 既然我们在 CareKitUI 中 有了新的 SwiftUI 视图 我们就把同步的 SwiftUI 视图 加入 CareKit 我们看看如何创建这几种视图中的一种 我们首先导入 CareKit CareKitUI 和 SwiftUI
然后在主体中 我们可以创建 CareKit 的 SimpleTaskView 方法是提供 taskID、 eventQuery 和同步的 StoreManager 其中 StoreManager 保持对 CareKitStore 的引用 视图会使用 taskID 和 eventQuery 来定位存储中的任务数据 一旦定位完成 就会将其自动映射到视图中 然后 SynchronizedStoreManager 会保证视图会随任务数据变化而更新 但你们可能会想自定义 将任务数据映射到视图的方式 要做到这点 我们需要另一个初始化器 这个例子中我们提供了和之前相同的参数 但我们还要提供一个闭包 从中创建要展示的 CareKit 下层视图 每次 SwiftUI 重新计算视图的主体时 都会调用这个闭包
在闭包内部 我们可以访问一个控制器 它保持着对任务数据和视图模型的引用 视图模型是一个便于辅助我们 将下层视图实体化的结构体 我们就用这个视图模型 从 CareKitUI 中实体化 SimpleTaskView 既然我们从 CareKitUI 创建视图 我们可以访问在之前的 CareKitUI 视图中见到的所有自定义点 在这个视图模型的帮助下 我们基本创建好了 默认的 SimpleTaskView 为了增加一些乐趣 我们试着修改一下该视图 在点按它时展示一个 ResearchKit 调查
要做到这点 我们首先增加状态属性 以确定调查是否已经显示
然后我们修改展示任务为 researchKitSurveyTask
然后 我们可以修改操作 在点按视图时 将 isShowingSurvey 标志改为真
最后当标志为真时 我们弹出一个浮窗 来显示 ResearchKitSurvey 我们看到你们中有很多人 使用 CareKit 卡片 来显示 ResearchKitSurvey 所以这可以作为你们的一个好起点 好 我们的部分完成了 我们把同步视图的新贴纸加上去 再增加一个贴纸收藏 我们在 API 中深入应用了 SwiftUI 这为框架带来了许多好处 比如简化的 API 和许多自定义点 但最大的好处之一 是 SwiftUI 允许我们把 CareKit 应用到 Apple Watch 上 你们现在可以为 watchOS 生成 CareKit、CareKitUI 和 CareKitStore
在用户界面方面 我们现在支持简单和含指示的任务视图 两者都为 Apple Watch 的屏幕做了优化 我前面提到 这些只是新视图的一小部分 我们非常期待看到你们用它们更进一步 为 Apple Watch 构建新的视图
现在这个小环节讲完了 我们来加个漂亮的手表贴纸吧
我们下面来讲 CareKitStore 的一些更新 它非常适于在 app 中存储健康数据 不过虽然 CareKitStore 很有用 但现在我们的设备上已经有了一种存储 它保存了大量健康数据 这个存储就是 HealthKit 现在你们可以将 HealthKit 数据 和 CareKit 数据一同使用 以创建由 HealthKit 驱动的任务 这些任务可以存储在 CareKitStore 中 还可以基于 HealthKit 中的数据自动完成
在深入了解新的 HealthKit 架构之前 我们先看看当前 基于 CareKitStore 的架构 我们有 OCKStore 它是一种核心数据实现 该存储可以由 StoreManager 打包 通过在发现存储数据改变时 发出通知消息为各个视图提供同步
StoreManager 可以用于 在 CareKit 中创建同步视图 比如前面的例子
现在我们看看 HealthKit 怎样整合到这个应用情况中
我们创建了一个新的 HealthKit 直通式存储 它与 CareKitStore 并列 CareKitStore 将核心数据 作为其真实数据源时 HealthKit 直通存储 则将 HealthKit 作为其真实数据源 这两处存储可以用 新的 StoreCoordinator进行包装 我们和 StoreCoordinator 交互的方式 和我们与独立存储交互的方式相同 通过为 CareKit 实体 调用其创建、更新和删除方法
我们来深入研究 StoreCoordinator 看看它怎么和内部存储通信
我们要求 StoreCoordinator 取出数据时 它会从其内部所有存储中整合结果 但如果我们要求 StoreCoordinator 写入数据 它一次只会写入一个单一的存储中 这可以帮助确保 写入数据时的操作具有事务性
我们来看看部分代码 如何设置这种新的 HealthKit 整合 我们首先导入 CareKit 和 CareKitStore
然后 同时创建 CareKitStore 和 HealthKit 直通存储 记得在应用中 为它们各自分配一个独特命名
接着创建 StoreCoordinator 然后把我们刚创建的两个存储附加上去
最后用 StoreCoordinator 创建一个 StoreManager 现在这个 StoreManager 可以用来创建同步 CareKit 视图了
我们已经设置好了存储 现在我们创造一个锻炼任务 添加到其中
我们为任务创建日程 指定任务发生于每天的早上 8点 同时设定一个锻炼 30 分钟的目标值 各个视图将用它来展示锻炼目标
下面我们创建一个 HealthKit 关联 帮我们把任务 与 HealthKit 数量连接起来 我们可以使用对应 HealthKit 数据类型的数量标识符、类型以及单位
现在我们有了日程和 HealthKit 关联 我们可以创建新任务 并添加到存储
要展示任务的话 我们可以使用 CareKitUI 中 新引入的部分视图 NumericProgressTaskView 完美适合本例 因为它能展示进度和目标数值 它上面的 LabeledValueTaskView 则更适合展示没有特定目标的任务
好 这部分也讲完了 我们已经贴了好几个贴纸 不过还得腾点地方 粘上由 HealthKit 驱动的任务贴纸
除了 HealthKit 当今的医疗保健行业 还有很多其它的存储系统 其中绝大多数都把数据存储成 FHIR 格式 FHIR 的应用使数据易于交换和解析 为了让你们能在自己的 app 中 轻松和这类数据库进行交互 我们引入了 FHIR 兼容性
为了进一步了解 FHIR 我们来看看一节 以 FHIR 格式构建的 JSON 片段
这节 JSON 记载了一份用药医嘱数据 药品是泰诺 这段 JSON 是依照 某一发行版本的 FHIR 构建的 在 CareKit 中 我们支持几种发行版本的兼容性 其中包括 DSTU2 和 R4
要启用 FHIR 兼容性 我们引入了编码器 它可以在 CareKit 实体 和 FHIR 数据间映射 要完成这种映射 我们使用了一种新型开源的 Apple 框架 即 FHIRModels 要进一步了解该模型 请看这个演讲 《无痛搞定 FHIR 处理》 这是名字起得最好的 WWDC 演讲之一 当我们进行 FHIR 数据 对 CareKit 实体的映射时 通常一个单一的 FHIR 资源 会映射到一个单一的 CareKit 实体 比如一个 FHIR 病人就是一个好例子 但有些时候 FHIR 资源的颗粒度 比 CareKit 实体的要大 这种情况下 可能有少量几个 FHIR 资源 会映射到一个单一 CareKit 实体上 大家在自行进行映射工作时 要注意考虑到这一点
现在我们从高层次上理解了编码器 我们试着创建出来吧 我们首先导入 CareKitStore 和 CareKitFHIR 后者是一个新的 SPM 包 内含编码器
然后我们可以 将用于映射数据的编码器初始化 这里我们有一个编码器 可以把 R4 发行版的 JSON 映射到 CareKit 实体中
我们使用该编码器可以把 CareKit 病人数据 转换成为 FHIR 数据
而在反向转换时 我们可以读取 FHIR 数据 并映射到 CareKit 病人中 注意在这个过程中 我们首先创造 FHIR 资源数据 这能确保其二进制数据 为 R4 格式的 JSON 以确保数据可以安全地 传递给我们的 R4 编码器 但是 FHIR 数据 和 CareKit 实体之间的映射 并不总是完美的 举例来说 可能 CareKit 实体的某种属性 不能完美地由 FHIR 资源所反映 这些情况下 自行定义映射方法就很重要 以防转译后的数据缺失 这里你可以定义如何 将病人的名字映射到 FHIR 数据中
在这个闭包中 我们从 FHIRModel 框架中得到了名字和病人 我们的任务是把名字映射给对应的病人
在反向转换时 你们可以定义病人名字以何种方式 从 FHIR 数据中被映射 现在我们从 FHIRModel 框架中 获得一个病人 但这一次我们的任务是提取出名字的组成 以供 CareKit 实体使用
注意这些闭包会暴露 来自 FHIRModel 的类型 如前面提到的 这个框架完全开源 你们可以在 CareKit 的 GitHub 页面上 找到全部源代码
以上就是 FHIR 方面的更新 我们看看新的贴纸长什么样 太棒了 现在我把演讲交给我的同事 Erik 他会介绍 CareKitStore 方面 更令人激动的更新 交给你了 Erik 谢谢 Gavi 大家好 我叫 Erik 我也是 CareKit 团队的工程师 今天我很荣幸能在此介绍和演示 CareKit 中的一个让人振奋的新特色 Gavi 刚刚讲到了 CareKit 如何帮助大家 将视图与你们存储中的数据同步 而我要跟你们讲的是另一种同步 即与服务器同步 自从 CareKit 开源起 我们最经常听到的提问之一就是 怎么让 CareKit 开发的 app 与服务器同步?
我们花了不少时间和精力 考虑如何做到这一点 并且还要做好 今天我很高兴给大家介绍 一套新的 CareKit 远程同步 API 新的远程同步 API 定义了与 CareKit 通讯的协议 任何遵从这些规则的服务器 都可以作为 CareKit 开发的 app 的同步后端
当 CareKit 开发的 app 启用了远程同步 大家在本机所做的改动 比如完成了任务 就会被同步到远程服务器上
其他设备也可以和服务器上的数据交互 在此处 其他的设备 为该用户增加了一项新任务 下次我们这位病人与服务器同步的时候 存储就会接收到更新 并将更新向他展示 这几种能启用这些交互形式的 API 具有两面 一面存在于 OCKStore 的一项扩展中 可供 iOS 开发者使用 另一面则是全新的协议 用于为服务器增加 CareKit 支持 我们先从 app 开发者的角度来看 API
我们为 OCKStore 的初始化器 增加了一种新的远程形参 如果我们为该形参传入一个实参 CareKit 就为所提供的对象启用远程同步 如果你们将远程部分留空 那么 CareKit 将完全维持离线运转 就像它之前那样运转 我们还增加了一种新的同步方法 从名称上可以看出来 调用该方法会提示 CareKit 将本机存储与远程存储同步 该策略形参的默认值通常符合大家的需要 但还有其它选项允许你们完全覆盖 本机或者远程一方的数据 并以另一方的数据替代
我们来看看怎么实际应用它们 首先你们需要导入 CareKitStore 以及一个第三方包 我们稍后会解说这些第三方包 但现在我们假设已经导入好了一个 下一步就是将一个类实体化 它要符合 OCKRemoteSynchronizable 大家需要传递该实例 至 OCKStore 的初始化器 当完成这一步后 就可以像通常那样使用该存储 很多远程端支持自动同步 也就是说 CareKit 会按需 为你们调用同步方法 当然 你可以一直手动调用同步 以按自己需要发起同步 我们从 app 开发者的一面看过了 API 而它的另一面就是新的 OCKRemoteSynchronizable 协议 后端工程师和云服务商可以用它 为其服务器添加 CareKit 支持 这个协议有 5 项要求
你们需要为你们的类准备一个委托 当服务器端发生了 需要 CareKit 注意的变化时 你们有责任通知该委托
你们还需要告诉 CareKit 是否需要自动同步 如果要进行确定性单元测试 关闭自动同步会比较方便 但通常情况下 你们应该选择自动同步
你们还需要提供一种方法 以便从服务器获取变化 CareKit 会给你们一个知识向量 在其它语境下它也可能被称为向量时钟 这是一种专门的数据结构 它可以让服务器 准确得知哪些数据已存于你的设备上 而哪些还没有 你们的任务是把该向量发送给服务器 以换得一份修订记录 再将修订记录传递至修订合并器闭包 并通过调用完成代码块 在完成时指示你们
下一项要求是推送修订 CareKit 会为你们提供设备修订 其中包括了设备上自从上一次 与服务器核对之后的所有变化 你们的任务是把该修订记录传递到服务器 并通过调用完成闭包 告知 CareKit 你们已完成
最后 chooseConflictResolutionPolicy 会在设备与服务器上 作出相互冲突的修改时介入 冲突描述中包括冲突两方各自的实体拷贝 你们的任务是检查这两份拷贝 并决定保留哪一份
在实现该协议以及设置服务器端 对应的支持逻辑后 便可以创建与 CareKit 的新整合了
现在 Apple 不提供 对于专用服务器的实现 但我们让这个过程尽可能方便他人操作 我们很激动地宣布 我们已经有了一个合作伙伴
IBM 成为了第一家 增加 CareKit 支持的服务商 将其加入 IBM Cloud Hyper Protect 供应之中 而且 IBM 传承了 CareKit 精神 甚至将其工作成果开源 成为 GitHub 上 CareKit 组织的一部分 如果你们想进一步了解其 SDK 你们可以查阅他们的自助实验室 了解如何使用 好的 我们讲到了很多内容 下面是令人激动的展示环节 今天我们要进行一套演示 演示中使用了刚才介绍的新同步 API 来实现一种巧妙的同步 远程同步 API 主要设计用于 将 iOS CareKit app 和服务器同步 但我会展示如何使用同一套 API 来让 iOS app 与其伴生的 watchOS app 同步数据 我们今天要创建的 app 会提醒用户每天做拉伸 提示他们汇报任何肌肉痉挛情况 并展示一张图表 呈现二者之间的关联 我们的目标是将同样的任务 同时展示在两个设备上 且在某一台设备上完成任务时 另一台设备也自动进行更新
现在这个 iOS app 实际上是一个简化版 它简化自我们在去年的演讲上 建构的那个样例 app (对 ResearchKit 和 CareKit 刮目相看) 如果你们还没看过那个演讲 我强烈建议你回去看一遍 了解一下我们是怎么一路走到今天的 好 我们跳到 Xcode 刚刚我提过 这个 iOS app 其实已经完成了 所以我们可以集中精力 创建优秀的 watchOS 用户体验
我们首先设置好 Apple Watch app 和 iOS app 间的同步 为此我们要用到一个新类 即 OCKWatchConnectivityPeer 这个类符合远程可同步协议 它让 Apple Watch 和 iPhone 可以 相互充当彼此的远程存储
我们实例化时 需要传递这个远程存储到存储中
在今天的演示中 我们要使用 WatchConnectivity 使用 WatchConnectivity 的时候 在 iOS 和 watchOS 之间来回传递的信息 都通过 WatchConnectivity 会话委托来传送 这是一个通常由 app 开发者 所拥有和控制的类 这也就意味着 CareKit 需要你们进行配合 才能让其信息来回传递 我来给大家演示应该做什么
我们要设置并激活 WatchConnectivity 会话 这样我们设置好委托 并调用激活方法
为了实现目标 我们要定义一个小助手类 让它充当我们的委托
WatchConnectivity 会话委托 有两项必须的方法 第一个是 activationDidComplete 这看起来是我们 进行第一次同步的好位置
第二个方法 didReceiveMessage 它会在每次消息从 iPhone 传递到 Apple Watch 完成后触发 在这里我们要为 CareKit 提供一点帮助 我们每次收到消息 都要向我们的 CareKit 远程存储展示消息 并给它一个提供响应的机会
然后我们接受该响应 再代表 CareKit 把它转发回 iPhone
这一步解决之后 我们只需要创建一个新类的实例 并记住把它设为会话的委托即可
通常来说我们还需要将同样的设置 在 iOS 端再设置一遍 但因为两套设置完全相同 所以我们为了演示变了个小魔术 把设置提前做好了 我们在 watchOS 上有了远程设置 在 iOS 上也有了远程设置 那么我们的同步层就完成了 我们接下来解决视图的问题
为了保证视图 与存储中的最新数据保持一致 我们需要为其提供一个 对 SynchronizedStoreManager 的引用 我们现在写一个吧
今天我们用 SwiftUI 来制作视图 使用 SwiftUI 时 EnvironmentValues 很适合为你们视图的 StoreManager 提供引用
我们在这里定义一个新 EnvironmentKey 它提供一个 SynchronizedStoreManager 对于其默认值 我们使用 extensionDelegate 拥有的实例吧
我们还要扩展 EnvironmentValues 来定义新属性: storeManager
我们使用这个属性作为我们在 TodaysTasksView 的环境变量
现在我们有了对 storeManager 的引用 我们可以开始为视图添砖加瓦了 我们想要展示两张任务卡片 并把它们包装在一个 ScrollView 中 我们还要进一步把 ScrollView 染上红色调 以匹配我们 iOS app 的主题
在 iOS 上我们选择 使用 InstructionsTaskView 来展示这个拉伸任务 在这里我们也这样做
同样 我们使用 SimpleTaskView 展示汇报痉挛卡片
因为汇报痉挛情况 这几个字的意思已经很明确 我们在此演示一下使用次级初始化器 来隐藏细节标签 并创建干净利落的视图
视图做好了 我们的 app 也完成了 我们来生成并运行 看看怎么样
注意第一次运行该 app 时 我们不会在 Apple Watch 上 立刻看到任务 这是因为同步需要一些时间才能完成 在 watchOS 和 iOS 间的同步
同步完成后 我们就能在 Apple Watch 上看到任务了 你们看 出来了 我们继续试着在 iPhone 上 完成汇报肌肉痉挛 看看它能不能同步到 Apple Watch 上 成功了 看起来非常好 记住 CareKit 上的所有视图 都与存储同步
这也代表着稍后 我在 Apple Watch 上打勾完成 拉伸任务的时候 我们会看见所有在 iPhone 端 订阅的视图也同步更新
注意观察完成情况指示环 拉伸任务卡片 以及图表在完成任务后的变化
这真的让人产生一种莫名的满足感
这里还有些东西可以把玩一下 不过我想大家应该明白主旨了 那么我的演示就先告一段落 我们回顾一下
我们看过了 CareKit 中的新 API 我们也探讨了如何将 CareKit 支持 添加到现有的服务器或云端 然后我们演示了如何用这些新 API 将 Apple Watch 和 iPhone 同步
我们为这部分付出了不少努力 所以现在我们贴上远程同步的贴纸
下面的部分我们继续让 Gavi 介绍 他会讲解今天的最后一个主题 即社群的新动向 来吧 Gavi 谢谢 Erik 我们真的很为新的远程同步 API 而兴奋 我们也等不及想看各位 在自己的 app 中使用它们 接下来我们就像 Erik 说的那样 讲一些社群新动向 每年都有很多来自社群的成员 做出令人惊叹的作品 要和大家分享这些新动向我非常激动
Erik 刚刚谈到了我们的新远程同步 API 作为社群的成员之一 IBM 成为了该 API 的首批采用者之一 你们可以在 CareKit 的 GitHub 页面 找到他们的源代码
除了设置远程同步外 每年我们都很乐于让一些 App Store 上的优秀 app 亮相 它们很好地使用了 CareKit 和ResearchKit 举几个例子 OYM Athlete app 能帮助运动员实现营养目标 方法是通过使用我们的图表视图 为运动员提供每日饮食追踪和趋势 HEALTHConnected app 可以让你们轻松地 与医生和家人分享健康数据 它也利用了联系人视图和图表
我们还看到有 app 利用 CareKit 和 ResearchKit 快速对新冠肺炎作出反应 斯坦福的 First Responder app 正在帮助响应者评估其新冠肺炎的风险 并让他们快速得到检测 内布拉斯加大学的 1-Check COVID app 可以让调查人员更加注意该疾病
这些只是今年发行的此类 app 中的几个 但我们很愿意了解你们对框架的使用情况 我们会告诉各位怎么反馈意见 (制造工具 推进研究和护理 让 app 一个一个来) 去年我们宣布要发行一个全新的网站 现在网站已经上线了 网址是 researchandcare.org 它也是这些框架的精美着陆页 在我们的概览页面 你们可以得到 CareKit 和 ResearchKit 的特色概览 并深入了解这些框架 如何帮助各位和你们的 app
要了解更详细的 CareKit 信息 你们可以导航至 CareKit 标签 你们在此可以浏览 CareKit 的特色 往下滚屏还可以看到项目的案例研究 这些项目使用这些框架 每天都为人们带来帮助
你们还可以看看我们的调查员支持项目 它旨在使用 Apple Watch 支持研究人员进行研究
如果你们对申请该项目感兴趣 请通过网页上的电子邮件地址联系我们
最后 你们中有许多人在自己的 app 中 使用 ResearchKit 和 CareKit 我前面提过 我们愿意倾听你们的使用情况 以及你们使用这些框架的方式 所以请用这份提交表格联系我们
现在甜蜜又伤感的时刻到了 我们来贴上最后的贴纸 即社群新动向 这真是一段漫长旅程 我们谈到了很多内容 从漂亮的新用户界面 到对 CareKitStore 的增强都有提及 希望你们和我一样为这些新变化而兴奋
我想我们现在可以为今年的 新 CareKit 特色功能赢得奖章了
如果要开始着手使用这个框架 一定记得来看看我们的 GitHub 页面和新设计的网站
你们都知道 我们是开源框架 我们的进步靠各位社群成员共同推进 付出艰辛的努力 打造新特色功能 并回馈给这个项目 所以不要害怕在 GitHub 上 提交你的第一个 PR 请求 不论贡献大小都没关系 作为今天的结束语 我们团队想在此 感谢各位对我们框架的不懈支持 也感谢大家对改善世界健康做出的贡献 我们非常期待看到你们的下一个成就 谢谢
-
-
2:23 - Simple Task View
import CareKitUI import SwiftUI struct MySimpleTaskView: View { var body: some View { SimpleTaskView( title: Text("Stretches"), detail: Text("15 minutes"), isComplete: false) } }
-
2:52 - Simple Task View - View Modifiers
import CareKitUI import SwiftUI struct MySimpleTaskView: View { var body: some View { SimpleTaskView( title: Text("Stretches").fontWeight(.thin), detail: Text("15 minutes"), isComplete: false) } }
-
3:42 - Simple Task View - Custom Header
struct MySimpleTaskView: View { var body: some View { SimpleTaskView(isComplete: false) { HStack { RoundedRectangle(cornerRadius: 5) .fill(Color.accentColor) .frame(width: 5) HeaderView( title: Text("Stretches"), detail: Text("15 minutes")) } .padding() } } }
-
4:29 - Simple Task View - Appending Views
import CareKitUI import SwiftUI struct MyComposedSimpleTaskView: View { var body: some View { CardView { VStack(alignment: .leading) { MySimpleTaskView() Divider() Text("...") .font(.caption) .foregroundColor(.secondary) }.padding() } } }
-
5:24 - Labeled Value Task View
import CareKitUI import SwiftUI struct MyLabeledValueTaskView: View { var body: some View { LabeledValueTaskView( title: Text("Heart Rate"), detail: Text("Most recent measurement") state: .complete( Text("62"), Text("BPM") )) } }
-
5:57 - Numeric Progress Task View
import CareKitUI import SwiftUI struct MyNumericProgressView: View { var body: some View { NumericProgressTaskView( title: Text("Exercise Minutes"), detail: Text("Anytime"), instructions: Text("..."), progress: Text("22"), goal: Text("30"), isComplete: false) } }
-
6:28 - Featured Content View
import CareKitUI import UIKit let featureView = OCKFeaturedContentView() featureView.imageView.image = UIImage(named: "groceries") featureView.label.text = "Easy & Healthy Recipes"
-
6:58 - Detail View
import CareKitUI import UIKit let styledHTML = OCKDetailView.StyledHTML( html: html, css: css) let detailView = OCKDetailView( html: styledHTML, showsCloseButton: true) detailView.imageView.image = UIImage(named: "groceries")
-
7:41 - Link View
import CareKitUI import SwiftUI struct MyLinkView: View { var body: some View { LinkView( title: Text("Physical Therapist Appointment"), instructions: Text("..."), links: [ // ... .website( "https://www.apple.com", title: "Website") // ... ]) } }
-
8:56 - Synchronized Task View 1
// 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) } }
-
9:26 - Synchronized Task View 2
@State private var isShowingSurvey = false var body: some View { CareKit.SimpleTaskView( taskID: "researchKitSurveyTask", eventQuery: OCKEventQuery(for: Date()), storeManager: storeManager) { controller in CareKitUI.SimpleTaskView( title: Text(controller.viewModel?.title ?? ""), detail: controller.viewModel?.detail.map(Text.init), isComplete: controller.viewModel?.isComplete ?? false) { isShowingSurvey = true } } .popover(isPresented: $isShowingSurvey) { ResearchKitSurvey() } }
-
13:43 - Setting up the 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)
-
14:15 - Adding HealthKit linked tasks to the store
// 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)
-
16:57 - Encoding and decoding 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)
-
17:49 - Customizing the data mapping 1
// 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] }
-
18:10 - Customizing the data mapping 2
// 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] }
-
25:56 - Creating the remote
private lazy var remote = OCKWatchConnectivityPeer()
-
26:09 - Setting up the remote store
private lazy var store = OCKStore(name: "sample-store", remote: remote)
-
26:18 - import WatchConnectivity
import WatchConnectivity
-
26:33 - Setting up the Watch Connectivity Session
WCSession.default.delegate = sessionDelegate WCSession.default.activate()
-
26:53 - Stub out session delegate class
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!") } }
-
27:02 - Kick off first synchronization
if activationState == .activated { store.synchronize { error in print(error?.localizedDescription ?? "Successful sync!") } }
-
27:11 - Forwarding replies on CareKit's behalf
remote.reply(to: message, store: store) { reply in print("Sending reply to peer!") replyHandler(reply) }
-
27:39 - Creating the delegate
private lazy var sessionDelegate = SessionDelegate(remote: remote, store: store)
-
28:10 - Setting up the synchronized store manager
private(set) lazy var storeManager = OCKSynchronizedStoreManager(wrapping: store)
-
28:27 - Defining a new environment key
private struct StoreManagerKey: EnvironmentKey { static var defaultValue: OCKSynchronizedStoreManager { let extensionDelegate = WKExtension.shared().delegate as! ExtensionDelegate return extensionDelegate.storeManager } }
-
28:51 - Defining a store manager environment value
extension EnvironmentValues { var storeManager: OCKSynchronizedStoreManager { get { self[StoreManagerKey.self] } set { self[StoreManagerKey.self] = newValue } } }
-
28:57 - Adding an environment variable to the view
@Environment(\.storeManager) private var storeManager
-
29:04 - Setting up the scroll view
ScrollView { }.accentColor(Color(#colorLiteral(red: 0.9960784314, green: 0.3725490196, blue: 0.368627451, alpha: 1)))
-
29:22 - Displaying the stretch task card
InstructionsTaskView( taskID: "stretch", eventQuery: OCKEventQuery(for: Date()), storeManager: storeManager)
-
29:33 - Displaying the muscle cramps task card
SimpleTaskView( taskID: "cramps", eventQuery: OCKEventQuery(for: Date()), storeManager: storeManager) { controller in .init(title: Text(controller.viewModel?.title ?? ""), detail: nil, isComplete: controller.viewModel?.isComplete ?? false, action: controller.viewModel?.action ?? {}) }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。