HealthKit的使用
一、项目中关联HealthKit框架
1.在Capabilities选项中打开HealthyKit选项
首先填写好你项目的Bundle Identifier并且选好Team(这两个东西最好事先设置好,以免之后又得重新关联),然后在项目物理文件结构中点选对应的项目,在TARGETS中选择你自身的项目,再在右侧选择Capabilities选项,选择开启HeathyKit选项
图中有4个选项,但是你的可能缺少其中的选项,这个时候可以根据其中缺少的去配置
3.确认你的App ID中HealthyKit选项是可用状态
4.配置plist文件
二、项目中使用HealthKit
1、HealthKit所支持的系统和设备
因为HealthKit框架是在iOS8系统出来之时一同推出的,所以该框架目前只支持iOS8及以上系统,目前支持的设备有iPhone、iWatch,要记得iPad是不支持的哦,如果你的代码同时支持iPhone和iPad设备,那么记得判断下设备还有系统版本号,以免出现不必要的奔溃现象。在项目中导入后,你也可以使用以下代码判断该设备的系统能否使用健康数据:
要想获取健康数据中的步数,则需要通过用户许可才行。具体可以使用以下代码进行授权:
HKHealthStore *healthStore = [[HKHealthStore alloc] init]; NSSet *readObjectTypes = [NSSet setWithObjects:[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount], nil]; [healthStore requestAuthorizationToShareTypes:nil readTypes:readObjectTypes completion:^(BOOL success, NSError *error) { if (success == YES) { //授权成功 } else { //授权失败 } }];
这里调用了requestAuthorizationToShareTypes: readTypes: completion:
方法,用于对应用授权需要获取和分享的健康数据:
1、第一个参数传入一个
NSSet
类型数据,用于告知用户,我的app可能会在你的健康数据库中修改这些选项数据(显然目前我们不需要,传nil)
2、第二个参数也是传入NSSet
类型数据,告知用户,我的app可能会从你的数据库中读取以下几项数据
3、第三个是授权许可回调,BOOL值success
用于区分用户是否允许应用向数据库存取数据
3、获取健康步数
授权完成之后,我们接下来就可以调用API来获取数据库数据了。
1、第一段通过传入一个枚举值HKQuantityTypeIdentifierStepCount来创建一个样品类的实例,用于告知,我接下来要获取的数据是步数>2、第二段代码通过创建一个NSPredicate类的实例,用于获取在某个时间段的数据,这里startDate和endDate传入nil,表示获取全部数据,第三个参数传入一个Option,里面有三个值,这个参数我试验了下不同的值代入,发现返回的结果都是一样的,要是有谁知道这个值是做什么用的麻烦告知我一声~
3、第三段代码创建了一个NSSortDescriptor类实例,用于对查询的结果排序
4、第四段代码通过调用HKSampleQuery类的实例方法获取所需数据
5、最后一行代码用于执行数据查询操作
通过这段代码获取的数据,打印出来会发现,它获取的是比较详尽的数据,精确到每一小段时间从开始时间到结束时间内所获取的步数。
4、数据采集
有时候需求并不需要了解这么详尽的数据,只希望获取每小时、每天或者每月的步数,那么我们就需要用到另一个新类HKStatisticsCollectionQuery
进行数据的分段采集
HKQuantityType *quantityType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]; NSDateComponents *dateComponents = [[NSDateComponents alloc] init]; dateComponents.day = 1; HKStatisticsCollectionQuery *collectionQuery = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:quantityType quantitySamplePredicate:nil options: HKStatisticsOptionCumulativeSum | HKStatisticsOptionSeparateBySource anchorDate:[NSDate dateWithTimeIntervalSince1970:0] intervalComponents:dateComponents]; collectionQuery.initialResultsHandler = ^(HKStatisticsCollectionQuery *query, HKStatisticsCollection * __nullable result, NSError * __nullable error) { for (HKStatistics *statistic in result.statistics) { NSLog(@"n%@ 至 %@", statistic.startDate, statistic.endDate); for (HKSource *source in statistic.sources) { if ([source.name isEqualToString:[UIDevice currentDevice].name]) { NSLog(@"%@ -- %f",source, [[statistic sumQuantityForSource:source] doubleValueForUnit:[HKUnit countUnit]]); } } } }; [healthStore executeQuery:collectionQuery];
1、第一段代码所做的和之前的一样,定义需要获取的数据为步数
2、第二段代码创建一个NSDateComponents类实例,设置我要获取的步数时间间隔,这里设置为按天统计,这里也可以设置按小时或者按月统计
3、第三段代码创建查询统计对象collectionQuery
,通过传入四个参数进行初始化:
1、第一个参数同上面一样,设置需要查询的类型
2、第二个参数传入一个NSPredicate实例,目前这里传nil
3、第三个参数是关键,传入一个Option可选值,告诉查询统计对象我需要获取的是啥,这里传入HKStatisticsOptionCumulativeSum | HKStatisticsOptionSeparateBySource
值,获取时间段的步数和以及将数据根据不同的数据来源进行分段
4、第四个参数传入一个锚点,类似于数组的索引值,查询将会从改锚点开始查询,这里可以根据不同的锚点值,获取日/周/月/年数据
4、第四段代码是将collectionQuery
对象的block属性initialResultsHandler
进行赋值,该block会在数据查询成功之后进行回调,从中可以获得我们想要的数据
5、最后一行执行该查询统计操作执行这段代码,通过打印的日志可以看到当前设备中存储的按日间隔存储的步行数总和了。
三、HealthKit工具类
EBHealthKitUtils.h
#import <Foundation/Foundation.h> #import <HealthKit/HealthKit.h> @interface EBHealthKitUtils : NSObject +(id)shareInstance; - (void)getPermissions:(void(^)(BOOL success))Handle; /*! * @author lei * * @brief 获取当天实时步数 * * @param handler 回调 */ - (void)getRealTimeStepCountCompletionHandler:(void(^)(double value, NSError *error))handler; /*! * @author lei * * @brief 获取当天所爬楼层 * * @param handler 回调 */ - (void)getRealTimeFloorCountCompletionHandler:(void(^)(double value, NSError *error))handler; /*! * @author lei * * @brief 获取一定时间段步数 * * @param predicate 时间段 * @param handler 回调 */ - (void)getStepCount:(NSPredicate *)predicate completionHandler:(void(^)(double value, NSError *error))handler; /*! * @author lei * * @brief 获取卡路里 * * @param predicate 时间段 * @param quantityType 样本类型 * @param handler 回调 */ - (void)getKilocalorieUnit:(NSPredicate *)predicate quantityType:(HKQuantityType*)quantityType completionHandler:(void(^)(double value, NSError *error))handler; /*! * @author lei * * @brief 获取当天某种类型的运动卡路里 * * @param predicate 时间段 * @param quantityType 样本类型 * @param handler 回调 */ - (void)getKilocalorieUnitWithQuantityType:(HKQuantityType*)quantityType completionHandler:(void(^)(double value, NSError *error))handler; /*! * @author lei * * @brief 当天时间段 * * @return */ + (NSPredicate *)predicateForSamplesToday; /*! * @author lei * * @brief 当天距离 * * @return */ - (void)getDistanceWalkingRunningCompletionHandler:(void(^)(double value, NSError *error))handler; @end
EBHealthKitUtils.m
#import "EBHealthKitUtils.h" #import <UIKit/UIDevice.h> #import <PPModuleCommonLib/LMBPPConstants.h> #import "ElectronicBalancePch.pch" #import "ElectronicBalanceApiDefine.h" #import "HKHealthStore+AAPLExtensions.h" @interface EBHealthKitUtils () @property (nonatomic, strong) HKHealthStore *healthStore; @end #define HKVersion [[[UIDevice currentDevice] systemVersion] doubleValue] #define CustomHealthErrorDomain @"com.apple.healthkit" @implementation EBHealthKitUtils +(id)shareInstance { static id manager ; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ manager = [[[self class] alloc] init]; }); return manager; } - (void)getPermissions:(void(^)(BOOL success))Handle { if(HKVersion >= 8.0) { if ([HKHealthStore isHealthDataAvailable]) { if(self.healthStore == nil) self.healthStore = [[HKHealthStore alloc] init]; /* 组装需要读写的数据类型 */ NSSet *writeDataTypes = [self dataTypesToWrite]; NSSet *readDataTypes = [self dataTypesRead]; /* 注册需要读写的数据类型,也可以在“健康”APP中重新修改 */ [self.healthStore requestAuthorizationToShareTypes:writeDataTypes readTypes:readDataTypes completion:^(BOOL success, NSError *error) { if (!success) { NSLog(@"%@\n\n%@",error, [error userInfo]); return ; } else { Handle(YES); } }]; } } } - (NSSet *)dataTypesToWrite { return [NSSet set]; } - (NSSet *)dataTypesRead { HKQuantityType *activeEnergyType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned]; // 注释掉暂不使用的内容 /* HKQuantityType *heightType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeight]; HKQuantityType *weightType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMass]; HKQuantityType *temperatureType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyTemperature]; HKCharacteristicType *birthdayType = [HKObjectType characteristicTypeForIdentifier:HKCharacteristicTypeIdentifierDateOfBirth]; HKCharacteristicType *sexType = [HKObjectType characteristicTypeForIdentifier:HKCharacteristicTypeIdentifierBiologicalSex]; */ HKQuantityType *stepCountType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]; HKQuantityType *floorCountType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierFlightsClimbed]; HKQuantityType *WalkingRunningType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceWalkingRunning]; // return [NSSet setWithObjects:heightType, temperatureType,birthdayType,sexType,weightType,stepCountType, activeEnergyType,nil]; return [NSSet setWithObjects:stepCountType,floorCountType,activeEnergyType,WalkingRunningType, nil]; } - (void)getDistanceWalkingRunningCompletionHandler:(void(^)(double value, NSError *error))handler { if(HKVersion < 8.0) { NSDictionary *userInfo = [NSDictionary dictionaryWithObject:@"iOS 系统低于8.0" forKey:NSLocalizedDescriptionKey]; NSError *aError = [NSError errorWithDomain:CustomHealthErrorDomain code:0 userInfo:userInfo]; handler(0,aError); } else { [self getPermissions:^(BOOL success) { if(success) { HKSampleType *sampleType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceWalkingRunning]; HKObserverQuery *query = [[HKObserverQuery alloc] initWithSampleType:sampleType predicate:nil updateHandler:^(HKObserverQuery *query, HKObserverQueryCompletionHandler completionHandler, NSError *error) { if (error) { // Perform Proper Error Handling Here... NSLog(@"*** An error occured while setting up the stepCount observer. %@ ***", error.localizedDescription); handler(0,error); // abort(); } HKQuantityType *stepType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceWalkingRunning]; [self.healthStore aapl_mostRecentQuantitySampleOfType:stepType predicate:[EBHealthKitUtils predicateForSamplesToday] completion:^(NSArray *results, NSError *error) { if(error) { handler(0,error); } else { double meter = 0; for(HKQuantitySample *quantitySample in results) { HKQuantity *quantity = quantitySample.quantity; HKUnit *meterUnit = [HKUnit meterUnit]; double value = [quantity doubleValueForUnit:meterUnit]; meter += value; } handler(meter, error); } }]; }]; [self.healthStore executeQuery:query]; } }]; } } - (void)getRealTimeStepCountCompletionHandler:(void(^)(double value, NSError *error))handler { if(HKVersion < 8.0) { NSDictionary *userInfo = [NSDictionary dictionaryWithObject:@"iOS 系统低于8.0" forKey:NSLocalizedDescriptionKey]; NSError *aError = [NSError errorWithDomain:CustomHealthErrorDomain code:0 userInfo:userInfo]; handler(0,aError); } else { [self getPermissions:^(BOOL success) { if(success) { HKSampleType *sampleType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]; HKObserverQuery *query = [[HKObserverQuery alloc] initWithSampleType:sampleType predicate:nil updateHandler:^(HKObserverQuery *query, HKObserverQueryCompletionHandler completionHandler, NSError *error) { if (error) { // Perform Proper Error Handling Here... NSLog(@"*** An error occured while setting up the stepCount observer. %@ ***", error.localizedDescription); handler(0,error); // abort(); } [self getStepCount:[EBHealthKitUtils predicateForSamplesToday] completionHandler:^(double value, NSError *error) { handler(value,error); }]; }]; [self.healthStore executeQuery:query]; } }]; } } - (void)getStepCount:(NSPredicate *)predicate completionHandler:(void(^)(double value, NSError *error))handler { if(HKVersion < 8.0) { NSDictionary *userInfo = [NSDictionary dictionaryWithObject:@"iOS 系统低于8.0" forKey:NSLocalizedDescriptionKey]; NSError *aError = [NSError errorWithDomain:CustomHealthErrorDomain code:0 userInfo:userInfo]; handler(0,aError); } else { [self getPermissions:^(BOOL success) { if(success) { HKQuantityType *stepType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]; [self.healthStore aapl_mostRecentQuantitySampleOfType:stepType predicate:predicate completion:^(NSArray *results, NSError *error) { if(error) { handler(0,error); } else { NSInteger totleSteps = 0; for(HKQuantitySample *quantitySample in results) { HKQuantity *quantity = quantitySample.quantity; HKUnit *heightUnit = [HKUnit countUnit]; double usersHeight = [quantity doubleValueForUnit:heightUnit]; totleSteps += usersHeight; } NSLog(@"当天行走步数 = %ld",(long)totleSteps); if(results.count == 0) { if(results.count == 0){ totleSteps = [[kUserDefaults objectForKey:kEBStepCountsKey]integerValue]; } } dispatch_async(dispatch_get_main_queue(), ^{ [kUserDefaults setObject:[NSString stringWithFormat:@"%zd",totleSteps] forKey:kEBStepCountsKey]; [kUserDefaults synchronize]; handler(totleSteps,error); }); } }]; } }]; } } + (NSPredicate *)predicateForSamplesToday { if(HKVersion >= 8.0) { NSCalendar *calendar = [NSCalendar currentCalendar]; NSDate *now = [NSDate date]; NSDateComponents *components = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:now]; [components setHour:0]; [components setMinute:0]; [components setSecond: 0]; NSDate *startDate = [calendar dateFromComponents:components]; NSDate *endDate = [calendar dateByAddingUnit:NSCalendarUnitDay value:1 toDate:startDate options:0]; NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:endDate options:HKQueryOptionNone]; return predicate; } else return nil; } - (void)getKilocalorieUnit:(NSPredicate *)predicate quantityType:(HKQuantityType*)quantityType completionHandler:(void(^)(double value, NSError *error))handler { if(HKVersion < 8.0) { NSDictionary *userInfo = [NSDictionary dictionaryWithObject:@"iOS 系统低于8.0" forKey:NSLocalizedDescriptionKey]; NSError *aError = [NSError errorWithDomain:CustomHealthErrorDomain code:0 userInfo:userInfo]; handler(0,aError); } else { [self getPermissions:^(BOOL success) { if(success) { HKStatisticsQuery *query = [[HKStatisticsQuery alloc] initWithQuantityType:quantityType quantitySamplePredicate:predicate options:HKStatisticsOptionCumulativeSum completionHandler:^(HKStatisticsQuery *query, HKStatistics *result, NSError *error) { NSLog(@"health = %@",result); HKQuantity *sum = [result sumQuantity]; double value = [sum doubleValueForUnit:[HKUnit kilocalorieUnit]]; NSLog(@"%@卡路里 ---> %.2lf",sum,value); if(handler) { handler(value,error); } }]; [self.healthStore executeQuery:query]; } }]; } } - (void)getKilocalorieUnitWithQuantityType:(HKQuantityType*)quantityType completionHandler:(void(^)(double value, NSError *error))handler { if(HKVersion < 8.0) { NSDictionary *userInfo = [NSDictionary dictionaryWithObject:@"iOS 系统低于8.0" forKey:NSLocalizedDescriptionKey]; NSError *aError = [NSError errorWithDomain:CustomHealthErrorDomain code:0 userInfo:userInfo]; handler(0,aError); } else { [self getPermissions:^(BOOL success) { if(success) { HKStatisticsQuery *query = [[HKStatisticsQuery alloc] initWithQuantityType:quantityType quantitySamplePredicate:[EBHealthKitUtils predicateForSamplesToday] options:HKStatisticsOptionCumulativeSum completionHandler:^(HKStatisticsQuery *query, HKStatistics *result, NSError *error) { NSLog(@"health = %@",result); HKQuantity *sum = [result sumQuantity]; double value = [sum doubleValueForUnit:[HKUnit kilocalorieUnit]]; NSLog(@"%@卡路里 ---> %.2lf",sum,value); if(handler) { handler(value,error); } }]; [self.healthStore executeQuery:query]; } }]; } } - (void)getRealTimeFloorCountCompletionHandler:(void(^)(double value, NSError *error))handler { if(HKVersion < 8.0) { NSDictionary *userInfo = [NSDictionary dictionaryWithObject:@"iOS 系统低于8.0" forKey:NSLocalizedDescriptionKey]; NSError *aError = [NSError errorWithDomain:CustomHealthErrorDomain code:0 userInfo:userInfo]; handler(0,aError); } else { [self getPermissions:^(BOOL success) { if(success) { HKSampleType *sampleType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierFlightsClimbed]; HKObserverQuery *query = [[HKObserverQuery alloc] initWithSampleType:sampleType predicate:nil updateHandler:^(HKObserverQuery *query, HKObserverQueryCompletionHandler completionHandler, NSError *error) { if (error) { // Perform Proper Error Handling Here... NSLog(@"*** An error occured while setting up the stepCount observer. %@ ***", error.localizedDescription); handler(0,error); // abort(); } [self getFloorCount:[EBHealthKitUtils predicateForSamplesToday] completionHandler:^(double value, NSError *error) { handler(value,error); }]; }]; [self.healthStore executeQuery:query]; } }]; } } - (void)getFloorCount:(NSPredicate *)predicate completionHandler:(void(^)(double value, NSError *error))handler { if(HKVersion < 8.0) { NSDictionary *userInfo = [NSDictionary dictionaryWithObject:@"iOS 系统低于8.0" forKey:NSLocalizedDescriptionKey]; NSError *aError = [NSError errorWithDomain:CustomHealthErrorDomain code:0 userInfo:userInfo]; handler(0,aError); } else { [self getPermissions:^(BOOL success) { if(success) { HKQuantityType *stepType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierFlightsClimbed]; [self.healthStore aapl_mostRecentQuantitySampleOfType:stepType predicate:predicate completion:^(NSArray *results, NSError *error) { if(error) { handler(0,error); } else { NSInteger totleSteps = 0; for(HKQuantitySample *quantitySample in results) { HKQuantity *quantity = quantitySample.quantity; HKUnit *heightUnit = [HKUnit countUnit]; double usersHeight = [quantity doubleValueForUnit:heightUnit]; totleSteps += usersHeight; } NSLog(@"当天所爬的楼层 = %ld",(long)totleSteps); if(results.count == 0){ totleSteps = [[kUserDefaults objectForKey:kEBFloorCountsKey]integerValue]; } dispatch_async(dispatch_get_main_queue(), ^{ [kUserDefaults setObject:[NSString stringWithFormat:@"%zd",totleSteps] forKey:kEBFloorCountsKey]; [kUserDefaults synchronize]; handler(totleSteps,error); }); } }]; } }]; } } @end