[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)。对于其他的数量,你需要自己来换算单位和本地化数据