美文网首页iOS开发资料收集区iOSiOS最实用干货
iOS开发-读取“健康”中的步数和步行+跑步距离

iOS开发-读取“健康”中的步数和步行+跑步距离

作者: BestVast | 来源:发表于2016-06-18 19:09 被阅读10290次

    借鉴于
    http://www.csdn.net/article/2015-01-23/2823686-healthkit-tutorial-with-swift/4

    • 注: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文件中声明苹果健康的使用权限,所以在info.plist中添加以下key就可以了。请求写入和请求读取都需要添加!

    请求写入
      <key>NSHealthUpdateUsageDescription</key>
    <string>some string value stating the reason</string>
    请求读取
       <key>NSHealthShareUsageDescription</key>
        <string>some string value stating the reason</string>
    

    iOS 10 因苹果健康导致闪退 crash - 借鉴与简书作者文章 找到的解决办法,谢谢

    1、第一步首先需要开启HealthKit


    HealthKit.png

    2、新建一个HealthKitManage类,继承于NSObject

    • (1)首先在.h文件里面声明一个属性
    @property (nonatomic, strong) HKHealthStore *healthStore;
    
    • (2)导入头文件
    #import <HealthKit/HealthKit.h>
    #import <UIKit/UIDevice.h>
    
    #define HKVersion [[[UIDevice currentDevice] systemVersion] doubleValue]
    #define CustomHealthErrorDomain @"com.sdqt.healthError"
    
    • (3)创建单例方法
    +(id)shareInstance
    {
        static id manager ;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            manager = [[[self class] alloc] init];
        });
        return manager;
    }
    
    • (4)检查是否支持获取健康数据
    /*
     *  @brief  检查是否支持获取健康数据
     */
    - (void)authorizeHealthKit:(void(^)(BOOL success, NSError *error))compltion
    {
        if(HKVersion >= 8.0)
        {
            if (![HKHealthStore isHealthDataAvailable]) {
                NSError *error = [NSError errorWithDomain: @"com.raywenderlich.tutorials.healthkit" code: 2 userInfo: [NSDictionary dictionaryWithObject:@"HealthKit is not available in th is Device"                                                                      forKey:NSLocalizedDescriptionKey]];
                if (compltion != nil) {
                    compltion(false, error);
                }
                return;
            }
            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 (compltion != nil) {
                        NSLog(@"error->%@", error.localizedDescription);
                        compltion (success, error);
                    }
                }];
            }
        }
        else {
            NSDictionary *userInfo = [NSDictionary dictionaryWithObject:@"iOS 系统低于8.0"                                                                      forKey:NSLocalizedDescriptionKey];
            NSError *aError = [NSError errorWithDomain:CustomHealthErrorDomain code:0 userInfo:userInfo];
            compltion(0,aError);
        }
    }
    
    /*!
     *  @brief  写权限
     *  @return 集合
     */
    - (NSSet *)dataTypesToWrite
    {
        HKQuantityType *heightType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeight];
        HKQuantityType *weightType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMass];
        HKQuantityType *temperatureType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyTemperature];
        HKQuantityType *activeEnergyType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned];
        
        return [NSSet setWithObjects:heightType, temperatureType, weightType,activeEnergyType,nil];
    }
    
    /*!
     *  @brief  读权限
     *  @return 集合
     */
    - (NSSet *)dataTypesRead
    {
        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 *distance = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceWalkingRunning];
        HKQuantityType *activeEnergyType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned];
        
        return [NSSet setWithObjects:heightType, temperatureType,birthdayType,sexType,weightType,stepCountType, distance, activeEnergyType,nil];
    }
    
    • (5)读取步数
    //获取步数
    - (void)getStepCount:(void(^)(double value, NSError *error))completion
    {
        HKQuantityType *stepType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
        NSSortDescriptor *timeSortDescriptor = [[NSSortDescriptor alloc] initWithKey:HKSampleSortIdentifierEndDate ascending:NO];
        
        // Since we are interested in retrieving the user's latest sample, we sort the samples in descending order, and set the limit to 1. We are not filtering the data, and so the predicate is set to nil.
        HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:stepType predicate:[HealthKitManage predicateForSamplesToday] limit:HKObjectQueryNoLimit sortDescriptors:@[timeSortDescriptor] resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {
                if(error)
                {
                    completion(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);
                    completion(totleSteps,error);
                }
        }];
        
        [self.healthStore executeQuery:query];
    }
    
    • (6)读取步行+跑步距离
    //获取公里数
    - (void)getDistance:(void(^)(double value, NSError *error))completion
    {
        HKQuantityType *distanceType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceWalkingRunning];
        NSSortDescriptor *timeSortDescriptor = [[NSSortDescriptor alloc] initWithKey:HKSampleSortIdentifierEndDate ascending:NO];
        HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:distanceType predicate:[HealthKitManage predicateForSamplesToday] limit:HKObjectQueryNoLimit sortDescriptors:@[timeSortDescriptor] resultsHandler:^(HKSampleQuery * _Nonnull query, NSArray<__kindof HKSample *> * _Nullable results, NSError * _Nullable error) {
            
            if(error)
            {
                completion(0,error);
            }
            else
            {
                double totleSteps = 0;
                for(HKQuantitySample *quantitySample in results)
                {
                    HKQuantity *quantity = quantitySample.quantity;
                    HKUnit *distanceUnit = [HKUnit meterUnitWithMetricPrefix:HKMetricPrefixKilo];
                    double usersHeight = [quantity doubleValueForUnit:distanceUnit];
                    totleSteps += usersHeight;
                }
                NSLog(@"当天行走距离 = %.2f",totleSteps);
                completion(totleSteps,error);
            }
        }];
        [self.healthStore executeQuery:query];
    }
    
    • (7)、NSPredicate当天时间段的方法实现
    /*!
     *  @brief  当天时间段
     *
     *  @return 时间段
     */
    + (NSPredicate *)predicateForSamplesToday {
        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;
    }
    

    3、在控制器里展示出来读取的数据
    (1)、首先导入头文件,并添加Label

    #import "HealthKitManage.h"
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    {
        UILabel *stepLabel;
        UILabel *distanceLabel;
    }
    

    (2)创建界面,展示数据

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        
        UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
        btn1.frame = CGRectMake(50, 100, 100, 40);
        [btn1 setTitle:@"计步" forState:UIControlStateNormal];
        btn1.backgroundColor = [UIColor cyanColor];
        [self.view addSubview:btn1];
        [btn1 addTarget:self action:@selector(onClickBtn1) forControlEvents:UIControlEventTouchUpInside];
        
        UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeCustom];
        btn2.frame = CGRectMake(50, 160, 100, 40);
        [btn2 setTitle:@"距离" forState:UIControlStateNormal];
        btn2.backgroundColor = [UIColor cyanColor];
        [self.view addSubview:btn2];
        [btn2 addTarget:self action:@selector(onClickBtn2) forControlEvents:UIControlEventTouchUpInside];
        
        stepLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 220, 200, 40)];
        stepLabel.backgroundColor = [UIColor cyanColor];
        [self.view addSubview:stepLabel];
        
        distanceLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 280, 200, 40)];
        distanceLabel.backgroundColor = [UIColor cyanColor];
        [self.view addSubview:distanceLabel];
    }
    
    - (void)onClickBtn1
    {
        HealthKitManage *manage = [HealthKitManage shareInstance];
        [manage authorizeHealthKit:^(BOOL success, NSError *error) {
            
            if (success) {
                NSLog(@"success");
                [manage getStepCount:^(double value, NSError *error) {
                    NSLog(@"1count-->%.0f", value);
                    NSLog(@"1error-->%@", error.localizedDescription);
                    dispatch_async(dispatch_get_main_queue(), ^{
                        stepLabel.text = [NSString stringWithFormat:@"步数:%.0f步", value];
                    });
                    
                }];
            }
            else {
                NSLog(@"fail");
            }
        }];
    }
    
    - (void)onClickBtn2
    {
        HealthKitManage *manage = [HealthKitManage shareInstance];
        [manage authorizeHealthKit:^(BOOL success, NSError *error) {
            
            if (success) {
                NSLog(@"success");
                [manage getDistance:^(double value, NSError *error) {
                    NSLog(@"2count-->%.2f", value);
                    NSLog(@"2error-->%@", error.localizedDescription);
                    dispatch_async(dispatch_get_main_queue(), ^{
                        distanceLabel.text = [NSString stringWithFormat:@"公里数:%.2f公里", value];
                    });
                    
                }];
            }
            else {
                NSLog(@"fail");
            }
        }];
    }
    

    4、展示成果


    成果展示图.png

    二、使用CoreMotion获取计步信息
    目前只使用到计步功能,个人感觉CoreMotion与HealthKit在计步方面的区别:HealthKit中计步信息可以通过“健康App”进行修改。
    1、info.plist中

    <key>NSMotionUsageDescription</key>
        <string>Privacy - Motion Usage Description</string>
    

    2、上代码

    #import <CoreMotion/CoreMotion.h>
    @property (nonatomic, strong) CMPedometer            *pedometer;
    
        _pedometer = [[CMPedometer alloc] init];
        if ([CMPedometer isStepCountingAvailable]) {
            //获取昨天的步数与距离数据
            [_pedometer queryPedometerDataFromDate:[NSDate dateWithTimeIntervalSinceNow:-60*60*24*2] toDate:[NSDate dateWithTimeIntervalSinceNow:-60*60*24*1] withHandler:^(CMPedometerData * _Nullable pedometerData, NSError * _Nullable error) {
                if (error) {
                    NSLog(@"error===%@",error);
                }
                else {
                    NSLog(@"步数===%@",pedometerData.numberOfSteps);
                    NSLog(@"距离===%@",pedometerData.distance);
                }
            }];
        } else {
            NSLog(@"不可用===");
        }
    

    借鉴两篇文章代码,多谢
    CMPedometer(计步器)使用,获取用户行走步数、距离等信息
    华山论剑之浅谈iOS计步功能

    相关文章

      网友评论

      • 心语风尚:使用CMPedometer 如果 选择“不允许”呢怎么再次打开权限. 使用CMPedometer在健康App中是不会记录你的App的 没有选项 只有用healthKit才有
        心语风尚:用cmpedomter 隐私中与健康app中上不会出现 你的app的 没有选项 只有用healthkit才有
        BestVast:跳转到App设置页面有没有这个权限?
        NSURL * url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
        if([[UIApplication sharedApplication] canOpenURL:url]) {
        [[UIApplication sharedApplication] openURL:url];
        }
        如果没有可能就需要去设置 - 隐私中操作吧
      • 心语风尚:使用CMPedometer 如果 选择“不允许”呢怎么再次打开权限
      • 答案在风中飘:如果我是骑车的, 这个距离会算进去么
        BestVast:如果你骑车时手机和走路一样晃,应该会算进去。正常骑车的情况都不会算进去的
      • 表弟1616:你好 我按着你的方法 写的 苹果审核拒绝为什么呢 求助 急
      • 不知蜕变的挣扎:requestAuthorizationToShareTypes 获取步数权限,权限关闭也进success
      • d3912052a56c:很感谢作者的分享。我想问下,我们获取步数都是从健康APP的获取步数,但是健康APP的步数是可以人为添加的。微信的记步数人为添加不了,这个是怎样才能做到跟微信一样的防止人为添加步数的呢?谢谢。
      • 6520a6aea01e:很赞,转走了
      • feng_dev:楼主再加个CMpedometer的吧:smile:
      • feng_dev:健康APP那里可以手动改步数,然后再获取到的步数就不是真实的步数了,好像微信步数用的是core motion 的那个CMpedometer吧
      • 呵呵哈哈嘿嘿:请问,卡路里该如何获取?
        BestVast:不好意思,这个我没做。我能说,我手机健康里的卡路里一直是0吗,都不会用:sob:
      • 记忆淡忘中:大神 你好 我想问下如果某一天没登录app那么就不能上传那天的步数给服务器。查看那天的历史步数时怎么办呢?因为有别的用户也可以看我的步数,通过您的这些方法直接获取好像也不行的 有什么好的建议吗?不胜感激:cold_sweat:
        BestVast:@记忆淡忘中 额,不知道那一天没数据:joy:
        记忆淡忘中:@风_雨 我并不知道那天我没有上传给服务器呀:sob:
        BestVast:@记忆淡忘中 (7)、NSPredicate当天时间段的方法实现
        + (NSPredicate *)predicateForSamplesToday
        {
        // NSDate *now = [NSDate date]; 用你想要的日期替换now,如下
        NSString *string = @"2017-01-05";
        NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
        [formatter setDateFormat:@"yyyy-MM-dd"];
        NSDate *now = [formatter dateFromString:string];
        。。。
        return predicate;
        }
        修改这个string日期就能获取某一天的步数
      • TigerNong:这个HealthKit最小的时间间隔是多少,有做过吗?
        BestVast:@TigerNong 我做的是取每天的步数
      • 夜生物:大神,调用健康数据,怎么区分人的?是不是任何一个人都可以调用手机上的健康数据?
        BestVast:@ChineseTiger 额,你这是在获取用户信息呀:joy: 都是些文本信息应该不大吧
        夜生物:@风_雨 第一次同步健康数据,如果一次性把所有数据都上传,会很慢,该怎么优化?
        BestVast:@ChineseTiger 我目前只是读取 步数 ,只要用户允许读取,就能获取到 健康app 里面的步数
      • Mighty_man:大神~请问一下,这个要获取从一定时间开始算起的步数,这个可以实现吗?怎么实现啊?谢谢~!!
      • 81332f614f12:楼主你好。感谢你的分享。我想咨询一下您,如何实时获取步行数和公里数? 感激不尽
        Hunter琼:以前的应该获取不了吧??
        81332f614f12:@风_雨 谢谢你了!
        BestVast:我写的这个就是过去今天实时的步数和公里数呀
      • 7cb2f3d717b2:楼主问一下,我想以天为单位,获取所有的步数,应该如果设置呢?
        BestVast:2、新建一个HealthKitManage类,继承于NSObject
        (7)、NSPredicate当天时间段的方法实现
        + (NSPredicate *)predicateForSamplesToday
        {
        // NSDate *now = [NSDate date]; 用你想要的日期替换now,如下
        NSString *string = @"2017-01-05";
        NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
        [formatter setDateFormat:@"yyyy-MM-dd"];
        NSDate *now = [formatter dateFromString:string];
        。。。
        return predicate;
        }
        修改这个string日期就能获取制定日期当天的步数
      • 黑白灰的绿i:为什么必须得打开健康一次 才能获取到步数呢 如果不打开就获取不到 为什么
        BestVast:@黑白灰的绿i 不用呀,第一次调用authorizeHealthKit这个方法之后,系统会弹出“访问健康数据”,让你选择允许此App读取或者写入那些健康数据,只要允许了就应该没有问题的。你之前没有打开过健康App吗,手机什么版本的?
        黑白灰的绿i:@风_雨 嗯嗯 这个我明白 但是已经允许了 也获取不到 只能打开一次健康 然后就正常获取了 为什么啊
        BestVast:@黑白灰的绿i app的使用者需要允许你的app读取健康数据,你的app才能获取到健康里面的数据
      • d17e852c43be:万能的楼主 请问怎么判断 用户是否同意获取健康中的数据 isHealthDataAvailable 这个方法好像只是判断 设备时候可以获取数据 :mask:
        d17e852c43be:@风_雨 万分感谢 :kissing_heart:
        BestVast:@孑立0631 authorizeHealthKit这个方法的block里的bool值就是返回的用户是否同意获取数据,程序里面第一次调用这个方法,系统会弹出界面让用户选择是否同意该程序获取你健康app的数据,以后也会默认这个选择,除非你在设置里面修改
      • f4f1537984e9:楼主可以上传swift 的版本吗
        BestVast:@f4f1537984e9 不好意思,我没有写swift版本的,我这篇是借鉴与网上的一篇swift文章,你可以看一下这篇文章
      • 7426426529e1:我是ios新手,访问健康数据能实现一个时间段的步数和距离吗?比如我点击按钮开始,到我点击按钮结束这段时间的步数和距离?
        景彧:@f4f1537984e9 你去学习一下这个你就知道了CMPedometer
        f4f1537984e9:@i_蓝天 求教。新手
        景彧:@Waston_王 那不是很简单?
      • 7426426529e1:请问有demo嘛?
      • e479d68cc262:作者,你有做过消耗卡路里的吗
        BestVast:@e479d68cc262 没有读取过卡路里数据,我健康里面的“活动能量”的数据好像是自己输入的,健康没有自己计算 :sweat:
      • dfdc868811f5:self.healthStore这个属性在哪里的?没有找到
        e479d68cc262:作者,你有做过消耗卡路里的吗
        BestVast:@ST_Zero 需要在.h文件里面声明一个属性
      • 请叫我小权权:请问作者怎么获取一周的数据呢?
        81332f614f12:@请叫我小权权 如何获取到一周的数据呢? 感激不尽
        请叫我小权权:@风_雨 我才看到你回复我了,我已经做完了,都实现了HealthKit的了,最后不用它了。。。
        BestVast:@请叫我小权权 不好意思,这个我之前没做,我这两天看一下
      • 方振:Authorization not determined 步数+公里,报这个错误呢
        方振:@丿尛峯 好久之前了,我把我权限都给你你比对下吧
        丿尛峯:@方振 请问少了什么什么权限 我也遇到这个报错
        方振:@方振 找到了,少写了一个权限
      • shushuzhen: if(HKVersion >= 8.0)这个判断进入之后就直接return了 所以就不行 把这个判读注释掉就能好获取到了
      • shushuzhen:请问作者 为什么我这边是直接打印了fail呢 我也是按照你这个步骤写的
        shushuzhen:@风_雨 设置允许了的 现在获取到了
        BestVast:@苏青青 你允许你的应用访问你“健康”的健康数据了吗?
      • 三秒嗨:点击计步 公里数的错误的 。变成了和步数一样的公里数。
        三秒嗨:@风_雨 :heart:
        BestVast:@三秒嗨 不好意思,之前没注意到,点击计步按钮应该只计算步数的,那个公里数是错误的,点击距离按钮显示的才是公里数,已修改
      • Mr_Bob_:[HealthKitManage predicateForSamplesToday] 这个方法你好像没有实现呢
        BestVast:@CoderZhou 多谢,我会加上这个的
        /*!
        * @Brief 当天时间段
        *
        * @Return 时间段
        */
        + (NSPredicate *)predicateForSamplesToday {
        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;
        }

      本文标题:iOS开发-读取“健康”中的步数和步行+跑步距离

      本文链接:https://www.haomeiwen.com/subject/piqbdttx.html