CareKit学习笔记1

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对象来管理

健康计划存储、UI、联系人之间的关系
代码

创建健康计划存储的实例:

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,一定要获取权限啊。
下个帖子我们讲一下洞察和联系哦

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容