iOS开发-关于苹果健康数据的获取

  • 关于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文件中添加关键字NSHealthShareUsageDescriptionNSHealthUpdateUsageDescription,写入和读取都需要。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使用的开关,如图:

屏幕快照 2016-08-25 下午5.15.31.png

此外打包证书也需要勾选苹果健康选项,由于开发者账号划归别的部门负责了,所以没法截图了。。。

踩的坑

之前做了一个健康运动的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噢!
喜欢就点个赞呗!
欢迎大家提出更好的改进意见和建议,一起进步!

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

推荐阅读更多精彩内容