- 关于iOS10的报错
Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: 'NSHealthUpdateUsageDescription must be set in the app's Info.plist
in order to request write authorization.'
处理方法:在Info.plist文件中添加关键字NSHealthShareUsageDescription
和NSHealthUpdateUsageDescription
,写入和读取都需要。iOS10对于涉及用户隐私的很多数据获取都添加了类似的设置,可根据提示在plist文件中添加相应的权限字段。
在苹果的 iOS8 系统中自带了健康应用,可以记录我们在一天当中的运动数据,比如走了多少步,一天走了或跑了多少公里等。还可以接入硬件设备以写入运动数据,HealthKit框架为苹果为采集各种健康数据源提供的接口,可以用来采集和整合各种健康数据的来源,如运动手环,AppleWatch,以及iPhone设备提供的健康数据。这里简单介绍下如何获取苹果健康的数据。
实现流程:
一、在工程中添加HealthKit库。
二、创建模型(或在其他模型中添加#import),添加HKHealthStore属性,HKHealthStore是使用HealthKit中关键的一个类。
三、创建调用方法,在方法实现中主要要做的有3步:
1、判断设备是否支持HealthKit框架
2、请求苹果健康的认证
3、获取苹果健康的数据
*// 方法代码
- (void)getIphoneHealthData{
self.healthSteps = [NSMutableArray array];
self.healthDistances = [NSMutableArray array];
self.healthCalories = [NSMutableArray array];
NSSet *getData;
// 1.判断设备是否支持HealthKit框架
if ([HKHealthStore isHealthDataAvailable]) {
getData = [self getData];
} else {
NSLog(@"---------不支持 HealthKit 框架");
}
store = [[HKHealthStore alloc] init];
// 2.请求苹果健康的认证
[store requestAuthorizationToShareTypes:nil readTypes:getData completion:^(BOOL success, NSError * _Nullable error) {
if (!success) {
NSLog(@"--------请求苹果健康认证失败");
return ;
}
dispatch_async(dispatch_get_main_queue(), ^{
// 3.获取苹果健康数据
[self getHealthStepData];
[self getHealthDistanceData];
});
}];
}
在第一步判断设备支持HealthKit框架后,设置好要获取的数据类型,笔者此处获取的是步数,距离数据。
*// getData方法
- (NSSet *)getData{
HKQuantityType *step = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
HKQuantityType *distance = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceWalkingRunning];
return [NSSet setWithObjects:step,distance, nil];
}
第二步请求苹果健康的认证,此处用到HKHealthStore属性,实例化属性后调用认证方法,注意方法中的参数ShareTypes 和 readTypes,一个是认证写入类型,一个是认证读取类型,认证成功后开始最重要的第三步,获取数据,此处为异步执行。
在获取数据的方法中时间的设置是比较麻烦的,不过都是一样的;HKQuantityType实例对象用于设置要获取的数据类型,NSPredicate实例对象用于设置获取数据的时间段,HKStatisticsCollectionQuery实例对象用于获取数据,在获取的数据结果中需要把每一条数据遍历出来,可以做一些需要的格式的处理。
*// 代码
- (void)getHealthStepData{
HKHealthStore *healthStore = [[HKHealthStore alloc]init];
NSCalendar *calendar = [NSCalendar currentCalendar];
// 设置时间支持单位
NSDateComponents *anchorComponents =
[calendar components:NSCalendarUnitDay | NSCalendarUnitMonth |
NSCalendarUnitYear | NSCalendarUnitWeekday fromDate:[NSDate date]];
NSDate *anchorDate = [calendar dateFromComponents:anchorComponents];
// 获取数据的截止时间 今天
NSDate *endDate = [NSDate date];
// 获取数据的起始时间 此处取从今日往前推100天的数据
NSDate *startDate = [NSDate dateWithTimeIntervalSinceNow:-100*24*60*60];
// 数据类型
HKQuantityType *type = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
// Your interval: sum by hour
NSDateComponents *intervalComponents = [[NSDateComponents alloc] init];
intervalComponents.day = 1;
// Example predicate 用于获取设置时间段内的数据
NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:endDate options:HKQueryOptionStrictStartDate];
HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:type quantitySamplePredicate:predicate options:HKStatisticsOptionCumulativeSum anchorDate:anchorDate intervalComponents:intervalComponents];
query.initialResultsHandler = ^(HKStatisticsCollectionQuery *query, HKStatisticsCollection *result, NSError *error) {
for (HKStatistics *sample in [result statistics]) {
// NSLog(@"--------------%@ 至 %@ : %@", sample.startDate, sample.endDate, sample.sumQuantity);
NSDate *date = sample.endDate;
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd"];
NSString *dateTime = [formatter stringFromDate:date];
double totalStep = [sample.sumQuantity doubleValueForUnit:[HKUnit countUnit]];
NSString *value = [NSString stringWithFormat:@"%f",totalStep];
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
dateTime,@"dateTime",
value,@"value",nil];
[self.healthSteps addObject:dic];
// NSLog(@"gaizaoDateStyle:%@ Dic = %@",self.healthSteps,dic);
}
self.healthCalories = self.healthSteps;
NSDictionary *healthSteps = [NSDictionary dictionaryWithObjectsAndKeys:
self.healthSteps,@"healthSteps",
self.healthCalories,@"healthCalories",nil];
NSLog(@"改造数据格式:%@",healthSteps);
};
[healthStore executeQuery:query];
}
由于苹果健康应用中,用户可以自己手动添加数据,所以为了获取数据的准确性,像微信运动会把这些数据过滤掉,那么如何过滤呢?
在获取的数据结果中,每一条结果都有数据来源属性,打印HKStatistics实例对象的sources属性就可以看到数据的所有来源(因为此处获取的是一天的数据,一天内的数据来源都会被打印出来),如果有手动添加的数据,可以在打印结果中看到com.apple.Health数据源,我们要过滤的就是它。
接下来就是用获取的数据总数把手动添加的数据减掉,结果就是我们想要获取的准确的数据。
*// 粘贴代码
double totalStep = [sample.sumQuantity doubleValueForUnit:[HKUnit countUnit]];
NSString *appleHealth = @"com.apple.Health";
double editStep = 0.0;
for (HKSource *source in sample.sources) {
if ([source.bundleIdentifier isEqualToString:appleHealth]) {
// 获取用户自己添加的数据 并减去,防止用户手动刷数据
HKSource *healthSource = source;
editStep = [[sample sumQuantityForSource:healthSource] doubleValueForUnit:[HKUnit countUnit]];
}
}
NSInteger step = (NSInteger)totalStep - (NSInteger)editStep;
NSString *value = [NSString stringWithFormat:@"%ld",step];
最后是获取距离的方法,和前边类似。
*// 代码
- (void)getHealthDistanceData{
HKHealthStore *healthStore = [[HKHealthStore alloc]init];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *anchorComponents =
[calendar components:NSCalendarUnitDay | NSCalendarUnitMonth |
NSCalendarUnitYear | NSCalendarUnitWeekday fromDate:[NSDate date]];
NSDate *anchorDate = [calendar dateFromComponents:anchorComponents];
NSDate *endDate = [NSDate date];
NSDate *startDate = [NSDate dateWithTimeIntervalSinceNow:-100*24*60*60];
HKQuantityType *type = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceWalkingRunning];
// Your interval: sum by hour
NSDateComponents *intervalComponents = [[NSDateComponents alloc] init];
intervalComponents.day = 1;
// Example predicate
NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:endDate options:HKQueryOptionStrictStartDate];
HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:type quantitySamplePredicate:predicate options:HKStatisticsOptionCumulativeSum anchorDate:anchorDate intervalComponents:intervalComponents];
query.initialResultsHandler = ^(HKStatisticsCollectionQuery *query, HKStatisticsCollection *result, NSError *error) {
for (HKStatistics *sample in [result statistics]) {
// NSLog(@"+++++++++++++++%@ 至 %@ : %@", sample.startDate, sample.endDate, sample.sumQuantity);
NSDate *date = sample.endDate;
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd"];
NSString *dateTime = [formatter stringFromDate:date];
double totalDistance = [sample.sumQuantity doubleValueForUnit:[HKUnit meterUnit]];
NSString *appleHealth = @"com.apple.Health";
// double floor = [sample.sumQuantity doubleValueForUnit:[HKUnit yardUnit]];
double editDistance = 0.0;
for (HKSource *source in sample.sources) {
if ([source.bundleIdentifier isEqualToString:appleHealth]) {
// 获取用户自己添加的数据 并减去,防止用户手动刷数据
HKSource *healthSource = source;
editDistance = [[sample sumQuantityForSource:healthSource] doubleValueForUnit:[HKUnit meterUnit]];
}
}
double distance = totalDistance/1000 - editDistance/1000;
NSString *value = [NSString stringWithFormat:@"%f",distance];
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
dateTime,@"dateTime",
value,@"value",nil];
[self.healthDistances addObject:dic];
}
NSLog(@"改造距离格式:%@",self.healthDistances);
};
[healthStore executeQuery:query];
}
在打包时候需要打开HealthKit使用的开关,如图:
此外打包证书也需要勾选苹果健康选项,由于开发者账号划归别的部门负责了,所以没法截图了。。。
踩的坑
之前做了一个健康运动的APP,要把苹果健康的数据和fitbit手环的数据比较处理后做展示,结果发现请求fitbit的数据产生的起止时间段内如果有没数据的,比如步数,卡路里,等,会都返回0,还比较人性化。但是苹果健康就没这么友好了,如果没有数据那就是没有,根本不会给你0,比如2015年1月1号到2015年1月20号的数据中1月10号这天的数据被删了,那返回的数据就只有19条,10号这天的不会返回0,直接就没有这条数据。再或者本身开始使用苹果健康记录数据的时间就是从2015年1月10号开始的,那1月10号之前的数据就都没有,不会返回0给你。
demo地址 GitHub给个Star噢!
喜欢就点个赞呗!
欢迎大家提出更好的改进意见和建议,一起进步!