CareKit为我们提供了一套医疗相关的控件和逻辑,与ResearchKit和HealthKit一起使用,可以方便和专业的创建医疗类应用。
版权
CareKit相关的资料很少,官方的GitHub也不怎么更新目前找到了一个很好的学习案例,是Swift写的,很值得参考:
CareKit Tutorial for iOS: Part 1
CareKit Tutorial for iOS: Part 2
这个人的教程很详细,而且很认真,不过有几个小的错误,自己需要修改一下
所以我镜像了他的工程,并且把他Step-by-Step的内容都添加上了,大家可以参考我的GitHub镜像,不过所有的代码权利,都是原作者所有jefframes
同时,我将这个项目转换成了OC版本,并且把他用的单例模式都简化了。他的代码抽象的非常好,非常适合正式开发,
但是因为去耦合的原因,代码看起来有些分散,所以我没有抽象代码,但是对学习CareKit,代码更连贯
请参考 ObjC版CareKitDemo
下面是根据这位仁兄的帖子做的笔记,并且代码已经转换成OC的了哦。
1. CareKit解析
从功能上,CareKit分为4个主要的UI模块:
-
Care Card 健康卡
:定义和记录对健康有益的活动。比如减肥应用,每天跟踪锻炼的时长 -
Symptom And Measurement Tracker 症状和测量跟踪器
:定义和跟踪症状或健康征兆。比如,减肥应用每天记录你的体重 -
Insights 洞察
:允许你用图表展示收集的数据,并且展现书面警告或者发现。减肥应用会列出体重和锻炼的图表来展现两者的关系 -
Connect 联系
:允许你和健康提供者,或者朋友沟通。比如减肥应用可以用来比较应该需要多努力,可以把数据转换成PDF然后发给医生。
Care Plan Store 健康计划存储
概念
CareKit应用的核心是Care Plan Store健康计划存储,用来管理和提供一个到数据库的接口,用来持久化健康计划。这个数据可以与任何已连接的健康卡、症状和测量跟踪器相连。当数据被输入到这些控制器的视图后,健康计划存储会自动收集。
这个存储主要掌握两类数据:
-Activities活动
:代表用户用来管理他们健康和跟踪状况的活动。这些活动经常被集中为健康计划
-Events事件
:用来表示一个规划的活动的发生。它由活动本身,规划数据,完成状态和结果(如果适用)组成
活动进一步细分为:
-Intervention Activities干预活动
:是用户用来治疗的一部分活动,它们出现在健康卡中
-Assessment Activities估值活动
:是用户在APP中评估他们健康的活动(也就是测量健康数值),会出现在症状和测量跟踪器中
Connect联系模块,并不在上述的存储中,而是使用OCKContact
对象来管理
代码
创建健康计划存储的实例:
OCKCarePlanStore* store = [[OCKCarePlanStore alloc] initWithPersistenceDirectoryURL:storeURL];
//storeURL是用来存储健康计划的地址,是一个文件夹名称
2. Care Card健康卡
如果你要定制一个锻炼计划,那就使用健康卡,创建健康卡使用如下代码
OCKCareCardViewController* viewController = [[OCKCareCardViewController alloc] initWithCarePlanStore:store];
//store就是刚创建的那个
//你还可以定制自己的图案,设置maskImage,smallMaskImage,maskImageTintColor,不定制就用系统默认的
这个页面显示出来都是100%,因为你还没有添加干预活动
准备健康计划数据,添加干预活动
现在可以在健康计划数据中添加一些活动,插入在 创建存储数据实例 和 创建健康卡ViewController之间
//有氧活动
OCKCarePlanActivity* cardioActivity = [[OCKCarePlanActivity alloc] initWithIdentifier:@"Cardio"
groupIdentifier:nil
type:OCKCarePlanActivityTypeIntervention
title:@"Cardio"
text:@"60 Min"
tintColor:[UIColor orangeColor]
instructions:@"Jog at a moderate pace for an hour. If there isn't an actual one, imagine a horde of zombies behind you."
imageURL:nil
schedule:[OCKCareSchedule dailyScheduleWithStartDate:[self firstDateOfCurrentWeek] occurrencesPerDay:2]
resultResettable:YES
userInfo:nil];
//舒展活动
OCKCarePlanActivity* limberUpActivity = [[OCKCarePlanActivity alloc] initWithIdentifier:@"Limber Up"
groupIdentifier:nil
type:OCKCarePlanActivityTypeIntervention
title:@"Limber Up"
text:@"Stretch Regularly"
tintColor:[UIColor orangeColor]
instructions:@"Stretch and warm up muscles in your arms, legs and back before any expected burst of activity. This is especially important if, for example, you're heading down a hill to inspect a Hostess truck."
imageURL:nil
schedule:[OCKCareSchedule dailyScheduleWithStartDate:[self firstDateOfCurrentWeek] occurrencesPerDay:6]
resultResettable:YES
userInfo:nil];
//目标训练
OCKCarePlanActivity* targetPracticeActivity = [[OCKCarePlanActivity alloc] initWithIdentifier:@"Target Practice"
groupIdentifier:nil
type:OCKCarePlanActivityTypeIntervention
title:@"Target Practice"
text:nil
tintColor:[UIColor orangeColor]
instructions:@"Gather some objects that frustrated you before the apocalypse, like printers and construction barriers. Keep your eyes sharp and your arm steady, and blow as many holes as you can in them for at least five minutes."
imageURL:nil
schedule:[OCKCareSchedule dailyScheduleWithStartDate:[self firstDateOfCurrentWeek] occurrencesPerDay:2]
resultResettable:YES
userInfo:nil];
NSArray* activitiesArray = @[cardioActivity, limberUpActivity, targetPracticeActivity];
//添加活动
for (OCKCarePlanActivity* activityToAdd in activitiesArray) {
//先判断有没有同名的活动,没有再添加
[store activityForIdentifier:activityToAdd.identifier completion:^(BOOL success, OCKCarePlanActivity * _Nullable activity, NSError * _Nullable error) {
if (success) {
if (!activity) {
//没有同名活动则添加
[store addActivity:activityToAdd completion:^(BOOL success, NSError * _Nullable error) {
if (success) {
//添加成功
NSLog(@"success");
}
}];
}
}
if (error) {
NSLog(@"error:%@",error);
}
}];
}
这里有一个方法,是获得本周的第一天的
- (NSDateComponents*)firstDateOfCurrentWeek{
NSDate *beginningOfWeek = nil;
NSCalendar* gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
gregorian.locale = [NSLocale currentLocale];
[gregorian rangeOfUnit:NSCalendarUnitWeekOfYear startDate:&beginningOfWeek interval:nil forDate:[NSDate date]];
NSDateComponents* dateComponents = [gregorian components:NSCalendarUnitEra|NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:beginningOfWeek];
return dateComponents;
}
3. 症状和测量跟踪器
这个页面是用来记录测量数据的(估值数据)
创建方法如下:
OCKSymptomTrackerViewController* viewController = [[OCKSymptomTrackerViewController alloc] initWithCarePlanStore:_carePlanStore];
viewController.progressRingTintColor = [UIColor purpleColor];
打开这个页面发现这个页面是进度100%,并且没有测量选项,这是因为我们没有添加估值活动
Assessment Activities估值活动
外国人比较喜欢搞怪,而且还编了一个僵尸来袭的故事,故事部分我就不阐述了,还是说主要内容吧,我们需要再添加两个估值活动,分别是脉搏和体温
//估值活动
OCKCarePlanActivity* pulseActivity = [OCKCarePlanActivity assessmentWithIdentifier:@"Pulse"
groupIdentifier:nil
title:@"Pulse"
text:@"Do you have one?"
tintColor:[UIColor greenColor]
resultResettable:YES
schedule:[OCKCareSchedule dailyScheduleWithStartDate:[self firstDateOfCurrentWeek] occurrencesPerDay:1]
userInfo:@{@"ORKTask":[self makePulseAssessmentTask]}];
OCKCarePlanActivity* temperatureActivity = [OCKCarePlanActivity assessmentWithIdentifier:@"Temperature"
groupIdentifier:nil
title:@"Temperature"
text:@"Oral"
tintColor:[UIColor yellowColor]
resultResettable:YES
schedule:[OCKCareSchedule dailyScheduleWithStartDate:[self firstDateOfCurrentWeek] occurrencesPerDay:1]
userInfo:@{@"ORKTask":[self makeTemperatureAssessmentTask]}];
这里userInfo需要填一个测量任务,生成方法如下:
- (ORKOrderedTask*)makePulseAssessmentTask{
HKQuantityType* quantityType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
HKUnit* unit = [HKUnit unitFromString:@"count/min"];
ORKHealthKitQuantityTypeAnswerFormat* answerFormat = [[ORKHealthKitQuantityTypeAnswerFormat alloc] initWithQuantityType:quantityType unit:unit style:ORKNumericAnswerStyleInteger];
NSString* title = @"Measure the number of beats per minute.";
NSString* text = @"Place two fingers on your wrist and count how many beats you feel in 15 seconds. Multiply this number by 4. If the result is 0, you are a zombie.";
ORKQuestionStep* queationStep = [ORKQuestionStep questionStepWithIdentifier:@"PulseStep" title:title text:text answer:answerFormat];
queationStep.optional = NO;
return [[ORKOrderedTask alloc] initWithIdentifier:@"PulseTask" steps:@[queationStep]];
}
- (ORKOrderedTask*)makeTemperatureAssessmentTask{
HKQuantityType* quantityType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyTemperature];
HKUnit* unit = [HKUnit unitFromString:@"degC"];
ORKHealthKitQuantityTypeAnswerFormat* answerFormat = [[ORKHealthKitQuantityTypeAnswerFormat alloc] initWithQuantityType:quantityType unit:unit style:ORKNumericAnswerStyleDecimal];
NSString* title = @"Take temperature orally.";
NSString* text = @"Temperatures should be in the range of 35.0-37.0°C";
ORKQuestionStep* queationStep = [ORKQuestionStep questionStepWithIdentifier:@"TemperatureStep" title:title text:text answer:answerFormat];
queationStep.optional = NO;
return [[ORKOrderedTask alloc] initWithIdentifier:@"TemperatureTask" steps:@[queationStep]];
}
创建估值活动的时候,有两个参数是之前干预活动没有用到的:
- resultResettable:决定这个测量活动,能不能重测,如果设置成NO,一旦数据键入,就不能再改了
- userInfo中的ORKTask:这是一个来自ResearchKit的类,它继承了很多标准化的健康测量任务,它还有配套的ViewController,这样我们应用不需要写心跳测量页面,使用ResearchKit给我们提供的就好了。这个数据,会在稍后用到
下面,扩充一下activitiesArray的内容,这样测量跟踪页面就可以显示出这两个活动了
NSArray* activitiesArray = @[cardioActivity, limberUpActivity, targetPracticeActivity,pulseActivity,temperatureActivity];
但是现在点击某一个活动,没有任何效果,需要进一步写代码
处理输入
在健康卡中,干预活动是一个二进制的输入,开关某个事件来表示是否发生了。但是估值活动更多元化,它可以输入各种数据,并且需要各种的UI,所以就需要提供一个新的ViewController来处理估值的输入。
ResearchKit提供了一系列的选择,来处理健康数据的输入,CareKit被设计的可以与他们协作。
输入被集中在ORKTaskViewController对象中,并且需要一个配套的ORKTask对象。
我们刚才创建了ORKTask对象,现在正好用一用
首先让入口的ViewController遵守<OCKSymptomTrackerViewControllerDelegate>协议,因为当点击了估值活动的某一行,会有代理方法的回调。当然,把OCKSymptomTrackerViewController的实例的delegate也得设置为self。然后实现代理方法:
- (void)symptomTrackerViewController:(OCKSymptomTrackerViewController *)viewController didSelectRowWithAssessmentEvent:(OCKCarePlanEvent *)assessmentEvent{
NSDictionary* userInfo = assessmentEvent.activity.userInfo;
if (userInfo ) {
ORKOrderedTask* task = userInfo[@"ORKTask"];
if (task) {
ORKTaskViewController* viewController = [[ORKTaskViewController alloc] initWithTask:task taskRunUUID:nil];
[self presentViewController:viewController animated:YES completion:nil];
}
}
}
如果这一步报错,你需要在info.plist文件中添加NSHealthShareUsageDescription权限。因为弹出的测量界面,会读取你的健康数值
这时候可以跑一下程序,可以弹出测量页面了,但是不能关闭,这是因为我们还需要实现另一个代理方法,来存储数据
ORKTaskViewController需要一个代理(ORKTaskViewControllerDelegate)回调来处理它的结果,所以我们来实现一下
//ORKTaskViewControllerDelegate
//记得设置代理
- (void)taskViewController:(ORKTaskViewController *)taskViewController didFinishWithReason:(ORKTaskViewControllerFinishReason)reason error:(NSError *)error{
if (reason == ORKTaskViewControllerFinishReasonCompleted) {
//待处理
}
[taskViewController dismissViewControllerAnimated:YES completion:nil];
}
获取结果的代码如下,好复杂。。。关于ResearchKit还是新建个帖子继续学习吧,就不深入探讨了,总之就是提取出值,然后存储到健康计划数据中
//ORKTaskViewControllerDelegate
- (void)taskViewController:(ORKTaskViewController *)taskViewController didFinishWithReason:(ORKTaskViewControllerFinishReason)reason error:(NSError *)error{
if (reason == ORKTaskViewControllerFinishReasonCompleted) {
OCKCarePlanEvent* event = _symptomTrackerViewController.lastSelectedAssessmentEvent;
if(event){
ORKStepResult* firstResult = (ORKStepResult*)taskViewController.result.firstResult;
ORKResult* stepResult = firstResult.results.firstObject;
if([stepResult isKindOfClass:ORKNumericQuestionResult.class]){
ORKNumericQuestionResult* numbericResult = (ORKNumericQuestionResult*) stepResult;
NSNumber* answer = numbericResult.numericAnswer;
OCKCarePlanEventResult* eventResult = [[OCKCarePlanEventResult alloc] initWithValueString:answer.stringValue unitString:numbericResult.unit userInfo:nil];
[_carePlanStore updateEvent:event withResult:eventResult state:OCKCarePlanEventStateCompleted completion:^(BOOL success, OCKCarePlanEvent * _Nullable event, NSError * _Nullable error) {
if(!success){
NSLog(@"Error:%@",error.localizedDescription);
}
}];
}
}
}
[taskViewController dismissViewControllerAnimated:YES completion:nil];
}
总结
这个APP依赖了HealthKit,一定要获取权限啊。
下个帖子我们讲一下洞察和联系哦