初探HealthKit 获取步数

[iOS] HealthKit 获取步数

前言

HealthKit 是苹果在 iOS 8.0 之后推出的健康框架,HealthKit框架提供了一个结构,应用可以使用它来分享健康和健身数据。

设计思路

HealthKit 是用来在应用间以一种有意义的方式共享数据。为了达到这点,框架限制只能使用预先定义好的数据类型和单位。

框架还大量使用了子类化,在相似的类间创建层级关系。通常这些类间都有一些细微但是重要的差别。还有不少很相关的类,需要正确地区别开才能一起工作。例如 HKObject 和 HKObjectType抽象类有很多平行层级的子类。当使用 object 和 object type 时,必须确保使用匹配的子类。

HKObject

HealthKit中所有的对象都是HKObject的子类。
大部分 HKObject 对象子类都是不可变的。每个对象都有下列属性:

- UUID。每个对象的唯一标示符。
- Source Revision。数据的来源。来源可以是直接把数据存进HealthKit的设备,或者是应用。当一个对象保存进HealthKit中时,HealthKit会自动设置其来源。只有从HealthKit中获取的数据source属性才可用。
-  Metadata。一个包含关于该对象额外信息的字典。元数据包含预定义和自定义的键。预定义的键用来帮助在应用间共享数据。自定义的键用来扩展HealthKit对象类型,为对象添加针对应用的数据。
- Device. 在此 sampler 生成的数据存储硬件设备。
HKSample

HealthKit对象主要分为2类:特征和样本。特征对象代表一些基本不变的数据。包括用户的生日、血型和生理性别。你的应用不能保存特征数据。用户必须通过健康应用来输入或者修改这些数据。所有的样本对象都是HKSample的子类。它们都有下列属性:

- Type。      // 样本类型。例如,这可能包括一个睡眠分析样本、一个身高样本或者一个计步样本。

- Start date。// 样本的开始时间。

- End date。  // 样本的结束时间。如果样本代表时间中的某一刻,结束时间和开始时间相同。如果样本代表一段时间内收集的数据,结束时间应该晚于开始时间。
HKObjectType 含有一些样本类型
  • HKCategorySample 类别样本。这种样本代表一些可以被分为有限种类的数据。在iOS8.0中,只有一种类别样本,睡眠分析。更多信息,参见 HKCategorySample Class Reference

  • HKQuantitySample 数量样本。这种样本代表一些可以存储为数值的数据。数量样本是HealthKit中最常见的数据类型。这些包括用户的身高和体重,还有一些其他数据,例如行走的步数,用户的体温和脉搏率。更多信息,参见HKQuantitySample Class Reference

  • Correlation 符合数据。这种样本代表复合数据,包含一个或多个样本。在iOS8.0中,HealthKit使用 correlation来代表食物和血压。在创建书屋或者血压数据时,你应该使用 correlation。更多信息,参见 HKCorrelation Class Reference

  • Workout。Workout 代表某些物理活动,像跑步、游泳,甚至游戏。Workout 通常有类型、时长、距离、和消耗能量这些属性。你还可以为一个 workout 关联许多详细的样本。不像 correlation,这些样本是不包含在 workout 里的。但是,它们可以通过 workout 获取到。更多信息,HKWorkout Class Reference

设置HealthKit

  • 要在Xcode中打开HealthKit功能。
设置权限

打开权限
 // 打开之后,在 Bundle 里会多一个 .entitlements 文件。
  • 调用 isHealthDataAvailable 方法来查看HealthKit在该设备上是否可用。

     if ([HKHealthStore isHealthDataAvailable]) {
     // add code to use HealthKit here...
     }
    
  • 为你的应用实例化一个 HKHealthStore 对象。每个应用只需要一个HealthKit存储实例。这个存储实例就是你和HealthKit数据库交互的主要接口。

     self.healthStore = [[HKHealthStore alloc] init];
    
  • 使用 requestAuthorizationToShareTypes:readTypes:completion:方法来请求获取HealthKit数据的权限。对每种类型的数据,你都必须请求许可来共享和读取。

      // 设置共享数据类型
      HKQuantityType *sampleType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
      NSSet *shareSet = [NSSet setWithObjects:sampleType, nil]; 
      
      // 设置写入数据类型
      HKQuantityType *read = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
      NSSet *readSet = [NSSet setWithObjects:read, nil];
      
      [self.healthStore requestAuthorizationToShareTypes:shareSet readTypes:readSet completion:^(BOOL success, NSError * _Nullable error) {
          if (success) {
              NSLog(@" 请求权限成功");
          } else{
              NSLog(@"error %@", error);
          }
      }];
    

读取HealthKit数据

有三种主要的方式来访问数据从HealthKit存储:

  • 直接方法调用。HealthKit 提供了直接读取特征数据的方法。这些方法只能用于读取特征数据。HKHealthStore
  • 查询
    • 样本查询(Sample query )。这是使用最多的查询。使用样本查询来读取任何类型的样本数据。当你想要对结果进行排序或者限制返回的样本总数时,样本查询就特别有用。HKSampleQuery

    • 固定对象查询。用这种查询来搜索添加进存储的项。当锚定查询第一次执行时,会返回存储中所有匹配的样本。在接下来的执行中,只会返回上一次执行之后添加的项目。通常,锚定对象查询会和观察者查询一起使用。观察者查询告诉你某些项目发生了变化,而锚定对象查询来决定有哪些(如果有的话)项目被添加进了存储。HKAnchoredObjectQuery

    • 统计查询。使用这种查询来在一系列匹配的样本中执行统计运算。你可以使用统计查询来计算样本的总和、最小值、最大值或平均值。HKStatisticsQuery

    • 统计集合查询。使用这种查询来在一系列长度固定的时间间隔中执行多次统计查询。通常使用这种查询来生成图表。查询提供了一些简单的方法来计算某些值,例如,每天消耗的总热量或者每5分钟行走的步数。统计集合查询是长时间运行的。查询可以返回当前的统计集合,也可以监测HealthKit存储,并对更新做出响应。HKStatisticsCollectionQuery

    • Correlation 查询。使用这种查询来在 correlation 查找数据。这种查询可以为 correlation 中每个样本类型包含独立的谓词。如果你只是想匹配 correlation 类型,那么请使用样本查询。 HKCorrelation

    • 来源查询。使用这种查询来查找 HealthKit 存储中的匹配数据的来源(应用和设备)。来源查询会列出储存的特定样本类型的所有来源。HKSourceQuery

    • 活动汇总查询。 使用这个可以查询用户活动的总结信息。每个活动汇总对象包含给定天数的用户活动总结,你可以查询一天或者多天。更多信息见,HKActivitySummaryQuery

  • 长时间运行的查询。 这些查询队列继续运行在一个匿名的后台线程中,并且无论何时改变了 HealthKit 数据,都能更新其应用。此外,观察者查询能注册到后台中。因而能使得在更新的时候,HealthKit 能在后台唤醒你的 app。
    • 观察者查询。这是一个长时间运行的查询,它会检测HealthKit存储,并在匹配到的样本发生变化时通知你。如果当存储发生变化时你想得到通知,就使用观察者查询。你可以让这些查询在后台执行。HKObserverQuery

      每种数据调用的查询的方法不一样,类型也要相应的匹配。
      NSDate *startDate, *endDate;
      endDate = [NSDate date];
      NSCalendar *calendar = [NSCalendar currentCalendar];
      NSDate *now = [NSDate date];
      startDate = [calendar startOfDayForDate:now];
      endDate = [calendar dateByAddingUnit:NSCalendarUnitDay value:1 toDate:startDate options:0];
      // NSLog(@"startDate %@, endDate %@", startDate, now);

       HKSampleType *sampleType = [HKSampleType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
      
       //  创建query, 设置 start 和 end 谓词 Create a predicate to set start/end date bounds of the query
       NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:endDate options:HKQueryOptionStrictStartDate];
      
       // Create a sort descriptor for sorting by start date
       NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:HKSampleSortIdentifierStartDate ascending:YES];
       __weak UILabel *numberLabel = self.showView.existStpNumberLabel;
        HKSampleQuery *sampleQuery = [[HKSampleQuery alloc] initWithSampleType:sampleType predicate:predicate limit:HKObjectQueryNoLimit sortDescriptors:@[sortDescriptor] resultsHandler:^(HKSampleQuery * _Nonnull query, NSArray<__kindof HKSample *> * _Nullable results, NSError * _Nullable error) {
      
        if(!error && results){
        
            CGFloat sum = 0.0f;
            for(HKQuantitySample *sample in results)
            {
           
           //
           NSLog(@"%@", sample);
           HKUnit *unit = [HKUnit unitFromString:@"count"];
           CGFloat number = [sample.quantity doubleValueForUnit:unit];
           sum += number;
           
         }
          dispatch_async(dispatch_get_main_queue(), ^{
              numberLabel.text = @(sum).description;
          });
       }
       }];
       [self.healthStore executeQuery:sampleQuery];
      

向 HealthKit Store 中添加样本

你需要创建一个新的样本对象,并将它添加到实例化的 Health Store 中去。样本类型都是相似的类型,只是在主体上可能有一些变化。

  • 在 HealthKit Constants Reference 中找到正确的类型标识符。HealthKit

  • 使用类型标识符创建一个匹配的 HKObjectType 子类。有一些便捷的方法,参见 HKObjectType Class Reference

  • 使用对象类型,创建一个匹配的 HKSample 子类。

  • 使用 saveObject:withCompletion: 方法将对象保存到HealthKit 存储中。

    对于数量样本,你必须创建一个 HKQuantity 类的实例。数量的单位必须和类型标识符文档中描述的可用单位相关。例如,HKQuantityTypeIdentifierHeight 文档中说明它使用长度单位,因此,你的数量必须使用厘米、米、英尺、英寸或者其他长度单位。HKQuantitySample

    对于类别样本,它的值必须和类型标识符文档中描述的枚举值相关。例如, HKCategoryTypeIdentifierSleepAnalysis 文档中说明它使用 HKCategoryValueSleepAnalysis 枚举值。因此你在创建样本时必须从这个枚举中传递一个值。HKCategorySample

    对于correlation,你必须先创建correlation包含的所有样本。correlation的类型标识符描述了它可以包含的类型和对象的数量。不要把被包含的对象存进HealthKit。它们是以correlation的一部分存储的。 HKCorrelation

    workout和其他类型的样本有些不同。首先,创建 HKWorkoutType 实例并不需要指定类型标识符。所有的workout都是用同样的类型标识符。第二,对于每个workout你都需要提供一个 HKWorkoutActivityType 值。这个值定义了workout中执行的活动的类型。最后,当workout保存到HealthKit后,你可以给workout关联额外的样本。这些样本提供了workout的详细信息。HKWorkout

    性别,年龄,
    NSError *error;
    HKBiologicalSexObject *bioSex = [healthStore biologicalSexWithError:&error];
    switch (bioSex.biologicalSex) {
    case HKBiologicalSexNotSet:
    // undefined
    break;
    case HKBiologicalSexFemale:
    // ...
    break;
    case HKBiologicalSexMale:
    // ...
    break;
    }
    
    体重
    // Some weight in gram
    double weightInGram = 83400.f;
    // Create an instance of HKQuantityType and
    // HKQuantity to specify the data type and value
    // you want to update
    NSDate          *now = [NSDate date];
    HKQuantityType  *hkQuantityType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMass];
    HKQuantity      *hkQuantity = [HKQuantity quantityWithUnit:[HKUnit gramUnit] doubleValue:weightInGram];
    
    // Create the concrete sample
    HKQuantitySample *weightSample = [HKQuantitySample quantitySampleWithType:hkQuantityType
    quantity:hkQuantity
    startDate:now
    endDate:now];
    
    // Update the weight in the health store
    [healthStore saveObject:weightSample withCompletion:^(BOOL success, NSError *error) {
    // ..
    }];
    
    // 获取步数
    NSDate *eDate = [NSDate date];
    NSDate *sDate = [NSDate dateWithTimeInterval:-300 sinceDate:eDate];
    HKQuantity *stepQuantityConsumed = [HKQuantity quantityWithUnit:[HKUnit countUnit] doubleValue:self.showView.addStepNumberTextField.text.doubleValue];
    HKQuantityType *stepConsumedType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
    
    NSString *strName = [[UIDevice currentDevice] name];
    NSString *strModel = [[UIDevice currentDevice] model];
    NSString *strSysVersion = [[UIDevice currentDevice] systemVersion];
    NSString *localeIdentifier = [[NSLocale currentLocale] localeIdentifier]; 
    
    HKDevice *device = [[HKDevice alloc] initWithName:strName manufacturer:@"Apple" model:strModel hardwareVersion:strModel firmwareVersion:strModel softwareVersion:strSysVersion localIdentifier:localeIdentifier UDIDeviceIdentifier:localeIdentifier];
    HKQuantitySample *stepConsumedSample = [HKQuantitySample quantitySampleWithType:stepConsumedType quantity:stepQuantityConsumed startDate:sDate endDate:eDate device:device metadata:nil];
    
    [self.healthStore saveObject:stepConsumedSample withCompletion:^(BOOL success, NSError * _Nullable error) {
    dispatch_async(dispatch_get_main_queue(), ^{
       
       if (success) {
           [self.view endEditing:YES];
           UIAlertView *doneAlertView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"添加成功" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
           [doneAlertView show];
           //刷新数据  重新获取步数
           [self getTodyStepNumberForHealthKit];
       }else {
           NSLog(@"The error was: %@.", error);
           UIAlertView *doneAlertView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"添加失败" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
           [doneAlertView show];
           return ;
       }
     });
    }];
    

单位换算

  • HealthKit使用 HKUnit 和 HKQuantity 类来支持单位。HKUnit 提供了单一单位的表示。它支持大部分的公制和英制单位,当然还包括基本单位和符合单位。基本单位代表单一的度量,例如米、磅或者秒。复合单位使用数学运算连接一个或多个基本单位,例如m/s或者lb/ft2。
  • HKUnit 提供了便捷方法来创建HealthKit支持的所有基本单位。它还提供了构建复合单位需要的数学运算。最后,你还可以通过直接使用恰当的格式化的单位字符串来创建复合单位。

  • HKQuantity 类存储了给定单位的值。之后你可以用任何兼容的单位来取值。这样,你的应用就可以很轻松的完成单位换算。
    在某些场合,你可以使用格式化器来本地化数量。iOS8提供了提供了新的格式化器来处理长度(NSLengthFormatter)、质量(NSMassFormatter)和能量(NSEnergyFormatter)。对于其他的数量,你需要自己来换算单位和本地化数据

demo

demo链接

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

推荐阅读更多精彩内容