iOS获取健康步数从加速计到healthkit

计步模块接触了一年多,最近又改需求了,所以又换了全新的统计步数的方法,整理一下吧。

在iPhone5s以前机型因为没有陀螺仪的存在,所以需要用加速度传感器来采集加速度值信息,然后根据震动幅度让其加入踩点数组并过滤,获取自己需要的步数数据。

直接上代码吧:

首先需要一个步数的model如下:

#import <Foundation/Foundation.h>

@interface VHSSteps : NSObject
//步数模型
@property(nonatomic,strong) NSDate *date;

@property(nonatomic,assign) int record_no;

@property(nonatomic, strong) NSString *record_time;

@property(nonatomic,assign) int step;

//g是一个震动幅度的系数,通过一定的判断条件来判断是否计做一步
@property(nonatomic,assign) double g;

@end

然后是如何获取步数,首先判断传感器是否可用

   //加速度传感器
    self.motionManager = [[CMMotionManager alloc] init];
    // 检查传感器到底在设备上是否可用
    if (!self.motionManager.accelerometerAvailable) {
        return;
    } else {
        // 更新频率是100Hz
        //以pull方式获取数据
        self.motionManager.accelerometerUpdateInterval = 1.0/40;
    }

可用的话开始实现统计步数的算法

 @try
    {
        //如果不支持陀螺仪,需要用加速传感器来采集数据
        if (!self.motionManager.isAccelerometerActive) {//  isAccelerometerAvailable方法用来查看加速度器的状态:是否Active(启动)。

            // 加速度传感器采集的原始数组
            if (arrAll == nil) {
                arrAll = [[NSMutableArray alloc] init];
            }
            else {
                [arrAll removeAllObjects];
            }

            /*
             1.push方式
             这种方式,是实时获取到Accelerometer的数据,并且用相应的队列来显示。即主动获取加速计的数据。
             */
            NSOperationQueue *queue = [[NSOperationQueue alloc] init];

            [self.motionManager startAccelerometerUpdatesToQueue:queue withHandler:^(CMAccelerometerData *accelerometerData, NSError *error){

                if (!self.motionManager.isAccelerometerActive) {
                    return;
                }

                //三个方向加速度值
                double x = accelerometerData.acceleration.x;
                double y = accelerometerData.acceleration.y;
                double z = accelerometerData.acceleration.z;
                //g是一个double值 ,根据它的大小来判断是否计为1步.
                double g = sqrt(pow(x, 2) + pow(y, 2) + pow(z, 2)) - 1;

                //将信息保存在步数模型中
                VHSSteps *stepsAll = [[VHSSteps alloc] init];

                stepsAll.date = [NSDate date];

                //日期
                NSDateFormatter *df = [[NSDateFormatter alloc] init] ;
                df.dateFormat  = @"yyyy-MM-dd HH:mm:ss";
                NSString *strYmd = [df stringFromDate:stepsAll.date];
                df = nil;
                stepsAll.record_time =strYmd;

                stepsAll.g = g;
                // 加速度传感器采集的原始数组
                [arrAll addObject:stepsAll];

                // 每采集10条,大约1.2秒的数据时,进行分析
                if (arrAll.count == 10) {

                    // 步数缓存数组
                    NSMutableArray *arrBuffer = [[NSMutableArray alloc] init];

                    arrBuffer = [arrAll copy];
                    [arrAll removeAllObjects];

                    // 踩点数组
                    NSMutableArray *arrCaiDian = [[NSMutableArray alloc] init];

                    //遍历步数缓存数组
                    for (int i = 1; i < arrBuffer.count - 2; i++) {
                        //如果数组个数大于3,继续,否则跳出循环,用连续的三个点,要判断其振幅是否一样,如果一样,然并卵
                        if (![arrBuffer objectAtIndex:i-1] || ![arrBuffer objectAtIndex:i] || ![arrBuffer objectAtIndex:i+1])
                        {
                            continue;
                        }
                        VHSSteps *bufferPrevious = (VHSSteps *)[arrBuffer objectAtIndex:i-1];
                        VHSSteps *bufferCurrent = (VHSSteps *)[arrBuffer objectAtIndex:i];
                        VHSSteps *bufferNext = (VHSSteps *)[arrBuffer objectAtIndex:i+1];
                        //控制震动幅度,,,,,,根据震动幅度让其加入踩点数组,
                        if (bufferCurrent.g < -0.12 && bufferCurrent.g < bufferPrevious.g && bufferCurrent.g < bufferNext.g) {
                            [arrCaiDian addObject:bufferCurrent];
                        }
                    }

                    //如果没有步数数组,初始化
                    if (nil == self.arrSteps) {
                        self.arrSteps = [[NSMutableArray alloc] init];
                        self.arrStepsSave = [[NSMutableArray alloc] init];
                    }

                    // 踩点过滤
                    for (int j = 0; j < arrCaiDian.count; j++) {
                        VHSSteps *caidianCurrent = (VHSSteps *)[arrCaiDian objectAtIndex:j];

                        //如果之前的步数为0,则重新开始记录
                        if (self.arrSteps.count == 0) {
                            //上次记录的时间
                            lastDate = caidianCurrent.date;

                            // 重新开始时,纪录No初始化
                            record_no = 1;
                            record_no_save = 1;

                            // 运动识别号
                            NSTimeInterval interval = [caidianCurrent.date timeIntervalSince1970];
                            NSNumber *numInter = [[NSNumber alloc] initWithDouble:interval*1000];
                            long long llInter = numInter.longLongValue;
                            //运动识别id
                            self.actionId = [NSString stringWithFormat:@"%lld",llInter];

                            self.distance = 0.00f;
                            self.second = 0;
                            self.calorie = 0;
                            self.step = 0;

                            self.gpsDistance = 0.00f;
                            self.agoGpsDistance = 0.00f;
                            self.agoActionDistance = 0.00f;

                            caidianCurrent.record_no = record_no;
                            caidianCurrent.step = self.step;

                            [self.arrSteps addObject:caidianCurrent];
                            [self.arrStepsSave addObject:caidianCurrent];

                        }
                        else {

                            int intervalCaidian = [caidianCurrent.date timeIntervalSinceDate:lastDate] * 1000;

                            // 步行最大每秒2.5步,跑步最大每秒3.5步,超过此范围,数据有可能丢失
                            int min = 259;
                            if (intervalCaidian >= min) {

                                if (self.motionManager.isAccelerometerActive) {

                                    //存一下时间
                                    lastDate = caidianCurrent.date;

                                    if (intervalCaidian >= ACCELERO_START_TIME * 1000) {// 计步器开始计步时间(秒)
                                        self.startStep = 0;
                                    }

                                    if (self.startStep < ACCELERO_START_STEP) {//计步器开始计步步数 (步)

                                        self.startStep ++;
                                        break;
                                    }
                                    else if (self.startStep == ACCELERO_START_STEP) {
                                        self.startStep ++;
                                        // 计步器开始步数
                                        // 运动步数(总计)
                                        self.step = self.step + self.startStep;
                                    }
                                    else {
                                        self.step ++;
                                    }

                                    //步数在这里
                                    NSLog(@"步数%d",self.step);

                                    int intervalMillSecond = [caidianCurrent.date timeIntervalSinceDate:[[self.arrSteps lastObject] date]] * 1000;
                                    if (intervalMillSecond >= 1000) {

                                        record_no++;

                                        caidianCurrent.record_no = record_no;

                                        caidianCurrent.step = self.step;
                                        [self.arrSteps addObject:caidianCurrent];
                                    }

                                    // 每隔100步保存一条数据(将来插入DB用)
                                    VHSSteps *arrStepsSaveVHSSteps = (VHSSteps *)[self.arrStepsSave lastObject];
                                    int intervalStep = caidianCurrent.step - arrStepsSaveVHSSteps.step;

                                    // DB_STEP_INTERVAL 数据库存储步数采集间隔(步) 100步
                                    if (self.arrStepsSave.count == 1 || intervalStep >= DB_STEP_INTERVAL) {
                                        //保存次数
                                        record_no_save++;
                                        caidianCurrent.record_no = record_no_save;
                                        [self.arrStepsSave addObject:caidianCurrent];

                                        // 备份当前运动数据至文件中,以备APP异常退出时数据也不会丢失
                                        // [self bkRunningData];

                                    }
                                }
                            }

                            // 运动提醒检查
                            // [self checkActionAlarm];
                        }
                    }
                }
            }];

        }
    }@catch (NSException * e) {
        NSLog(@"Exception: %@", e);
        return;
    }

然后iPhone 5s出现了, 增加了 M7 运动协处理器,也带来了CMStepCounter类,从此我们就不用自己计算步数了,只要直接读取就好。

  首先还是要检测协处理器是否可用

    if (!([CMStepCounter isStepCountingAvailable] || [CMMotionActivityManager isActivityAvailable])) {

        NSString *msg = @"demo只支持iPhone5s以上机型.";
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Opps!"
                                                        message:msg
                                                       delegate:nil
                                              cancelButtonTitle:@"OK"
                                              otherButtonTitles:nil];
        [alert show];

}

然后才是获取步数的方法,主要有两种:

计步 第一种方法
     
     startStepCountingUpdatesToQueue:updateOn:withHandler:
     
     开始分发当前步数计数数据到第三方应用
     
     - (void)startStepCountingUpdatesToQueue:(NSOperationQueue *)queue updateOn:(NSInteger)stepCounts withHandler:(CMStepUpdateHandler)handler
     Parameters
     
     queue
     
     被指定执行特定的handler块的操作队列。第三方可以指定一个定制队列或者使用操作队列协助app的主线程。该参数不能为nil
     
     stepCounts
     
     记录的步伐数据,达到该数值去执行handler块。该数值必须大于0
     
     handler
     
     该块在步伐计数达到或超出数值时会被执行,该参数不能为nil。更多块方法信息参考CMStepQueryHandler。
     
     Discussion
     
     该方法实现对用户步伐数据的追踪,并周期性地唤起块方法去分发结果。当第三方调用了该方法,步伐计数器会重置当前步伐数为0,并开始计数。每次计数到达指定的步伐数时,会执行指定的handler块方法。比如,当设定stepCounts为100时,会在100,200,300等数目时发送更新,激活该块方法。每次发送到该块方法的步伐数目都是从你调用该方法开始的步伐数目总和。
     
     每次超过设定步数值时,指定的处理程序块handler会被执行。如果当超过设定值时第三方应用处在被挂起的状态,那程序块也不会被执行。当第三方应用被唤醒,程序块也不会执行,直到再次超过设定步数值。
     
     可以调用stopStepCountingUpdates方法去停止分发步数计数,当然当步数计数对像被销毁的时候,分发过程也会被停止。
   
  代码如下:

  if ([CMStepCounter isStepCountingAvailable]) {
    self.stepCounter = [[CMStepCounter alloc] init];
    [self.stepCounter startStepCountingUpdatesToQueue:self.operationQueue
                                             updateOn:1
                                          withHandler:
     ^(NSInteger numberOfSteps, NSDate *timestamp, NSError *error) {

         dispatch_async(dispatch_get_main_queue(), ^{

             if (error) {
                 UIAlertView *error = [[UIAlertView alloc] initWithTitle:@"Opps!" message:@"error" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
                 [error show];
             }
             else {

                 NSString *text = [NSString stringWithFormat:@"当前步数: %ld", (long)numberOfSteps];
                 //这里是步数
                 weakSelf.stepsLabel.text = text;
             }
         });
     }];
}

计步 第二种方法
   
     queryStepCountStartingFrom:to:toQueue:withHandler:
     
     收集并返回某一时间段内的历史步数数据
     
     - (void)queryStepCountStartingFrom:(NSDate *)start to:(NSDate *)end toQueue:(NSOperationQueue *)queuewithHandler:(CMStepQueryHandler)handler
     Parameters
     
     start
     
     收集步数数据的开始时间,该参数不能为 nil.
     
     end
     
     收集步数数据的停止时间,该参数不能为nil.
     
     queue
     
     执行指定handler块的操作队列,第三方可以指定一个定制队列或者使用操作队列协助app的主线程。该参数不能为nil
     
     handler
     
     执行处理结果的块方法,该参数不能为nil。更多块方法信息参考CMStepQueryHandler。
     
     Discussion
     
     该方法为异步方法,会立即返回并且把结果分发到指定的handler块中处理。系统最多仅存储最近7天内的有效步数数据。如果在指定时间范围内没有数据,则会传递一个0值到handler块中。
  
代码如下

// 获取今日步数  __weak ViewController *weakSelf = self;
self.operationQueue = [[NSOperationQueue alloc] init];

NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *now = [NSDate date];
NSDateComponents *components = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:now];
// 开始日期
NSDate *startDate = [calendar dateFromComponents:components];
// 结束日期
NSDate *endDate = [calendar dateByAddingUnit:NSCalendarUnitDay value:1 toDate:startDate options:0];

  if ([CMStepCounter isStepCountingAvailable]) {
    [self.stepCounter  queryStepCountStartingFrom:startDate to:endDate toQueue:self.operationQueue withHandler:^(NSInteger numberOfSteps, NSError * _Nullable error) {
        NSLog(@"%ld",numberOfSteps);
        dispatch_async(dispatch_get_main_queue(), ^{
            if (error) {
                UIAlertView *error = [[UIAlertView alloc] initWithTitle:@"Opps!" message:@"error" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
                [error show];
            }
            else {
                weakSelf.totalLabel.text = [NSString stringWithFormat:@"今日总步数%ld",numberOfSteps];
            }
        });
    }];
}

另外,iOS7还增加了CMMotionActivity类,用来获取运动状态

  if ([CMMotionActivityManager isActivityAvailable]) {
    self.activityManager = [[CMMotionActivityManager alloc] init];
    [self.activityManager startActivityUpdatesToQueue:self.operationQueue
                                          withHandler:
     ^(CMMotionActivity *activity) {

         dispatch_async(dispatch_get_main_queue(), ^{

             NSString *status = [weakSelf statusForActivity:activity];
             NSString *confidence = [weakSelf stringFromConfidence:activity.confidence];

             weakSelf.statusLabel.text = [NSString stringWithFormat:@"状态: %@", status];
             weakSelf.confidenceLabel.text = [NSString stringWithFormat:@"速度: %@", confidence];
         });
     }];
}
- (NSString *)statusForActivity:(CMMotionActivity *)activity {

    NSMutableString *status = @"".mutableCopy;

    if (activity.stationary) {

        [status appendString:@"not moving"];
    }

    if (activity.walking) {

        if (status.length) [status appendString:@", "];

        [status appendString:@"on a walking person"];
    }

    if (activity.running) {

        if (status.length) [status appendString:@", "];

        [status appendString:@"on a running person"];
    }

    if (activity.automotive) {

        if (status.length) [status appendString:@", "];

        [status appendString:@"in a vehicle"];
    }

    if (activity.unknown || !status.length) {

        [status appendString:@"unknown"];
    }

    return status;
}

- (NSString *)stringFromConfidence:(CMMotionActivityConfidence)confidence {

    switch (confidence) {

        case CMMotionActivityConfidenceLow:

            return @"Low";

        case CMMotionActivityConfidenceMedium:

            return @"Medium";

        case CMMotionActivityConfidenceHigh:

            return @"High";

        default:

            return nil;
    }
}

好吧,随着时间的推移,iOS8来了,也带来了healthkit,不过之前的方法满足需求也就还是用的CMStepCounter方法。

不过最近客户改需求了,手环,iWatch的数据也需要统计进来,就不得不用healthkit的方法了。

还是老套路,先检查能不能用

 //查看healthKit在设备上是否可用,ipad不支持HealthKit
    if(![HKHealthStore isHealthDataAvailable])
    {
        NSLog(@"设备不支持healthKit");
    }

然后获取步数

 //创建healthStore实例对象
    self.healthStore = [[HKHealthStore alloc] init];

    //设置需要获取的权限这里仅设置了步数
    HKObjectType *stepCount = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];

    NSSet *healthSet = [NSSet setWithObjects:stepCount, nil];

    //从健康应用中获取权限
    [self.healthStore requestAuthorizationToShareTypes:nil readTypes:healthSet completion:^(BOOL success, NSError * _Nullable error) {
        if (success)
        {
            NSDateFormatter *formatter = [[NSDateFormatter alloc ]init];
            [formatter setDateFormat:@"yyyy-MM-dd"];
            NSDate *now = [NSDate date];
            NSString *todaystr = [formatter stringFromDate:now];
            NSDate *today = [formatter dateFromString:todaystr];

            NSDate *next = [today dateByAddingTimeInterval:24*60*60];       //定义需要获取的数据为步数
            HKQuantityType *quantityType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
       //设置获取的步数时间间隔       NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
            dateComponents.day = 1;

            NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:today endDate:next options:HKQueryOptionStrictStartDate];
            //创建查询统计对象collectionQuery       HKStatisticsCollectionQuery *collectionQuery = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:quantityType quantitySamplePredicate:predicate options: HKStatisticsOptionCumulativeSum | HKStatisticsOptionSeparateBySource anchorDate:[NSDate dateWithTimeIntervalSince1970:0] intervalComponents:dateComponents];
            collectionQuery.initialResultsHandler = ^(HKStatisticsCollectionQuery *query, HKStatisticsCollection * __nullable result, NSError * __nullable error) {
                float numberOfSteps = 0;
                for (HKStatistics *statistic in result.statistics) {

                    for (HKSource *source in statistic.sources) {
                         //HKSource对象中的name可用于区分健康数据来源
                        if ([source.name isEqualToString:deviceName]) {
                            float steps = [[statistic sumQuantityForSource:source] doubleValueForUnit:[HKUnit countUnit]];
                            numberOfSteps += steps;

                        }                //deviceName是根据接入的设备做的标记,
                        if ([deviceName isEqualToString:@"iPhone"]) {
                            if ([source.name isEqualToString:[UIDevice currentDevice].name]) {
                                float steps = [[statistic sumQuantityForSource:source] doubleValueForUnit:[HKUnit countUnit]];
                                numberOfSteps += steps;

                            }

                        }else if ([deviceName isEqualToString:@"iWatch"] && ![source.name isEqualToString:[UIDevice currentDevice].name]){
                            if ([source.bundleIdentifier hasPrefix:@"com.apple.health"]) {
                                float steps = [[statistic sumQuantityForSource:source] doubleValueForUnit:[HKUnit countUnit]];
                                numberOfSteps += steps;

                            }
                        }else if ([deviceName isEqualToString:@"xiaomi"]){
                            if ([source.name isEqualToString:@"小米运动"] || [source.bundleIdentifier isEqualToString:@"HM.wristband"]) {
                                float steps = [[statistic sumQuantityForSource:source] doubleValueForUnit:[HKUnit countUnit]];
                                numberOfSteps += steps;

                            }

                        }
                    }

                }
                NSLog(@"ff = %f",numberOfSteps);          //步数看这里就好
                stepString = [NSString stringWithFormat:@"%.0f",numberOfSteps];
            
                //CGFloat distance = [VHSCommon getDistance:numberOfSteps];
                //int calorie = [VHSCommon getActionCalorie:distance speed:distance * 3600 / 24*60*60];
                //distanceString = [NSString stringWithFormat:@"%.2f",distance];
                //calorString = [NSString stringWithFormat:@"%d",calorie];   

            };
            [self.healthStore executeQuery:collectionQuery];
        }
        else
        {
            NSLog(@"获取步数权限失败");
        }
    }];

demo完整代码在这里:

加速度传感器进行计步

CMStepCounter获取健康步数

关于healthkit的还没有整理好,过段时间再补充吧。

时间: 2024-10-28 14:44:49

iOS获取健康步数从加速计到healthkit的相关文章

检测SDWebImage有没有缓存图片 IOS 获取网络图片大小

NSURL *url = [NSURL URLWithString:[model.content objectForKey:@"image"]];             //请求网络地址数据的同步方法             //因为这个方法在子线程(全局队列)中执行,所以不需要考虑死线程的问题             SDWebImageManager *manager = [SDWebImageManager sharedManager];              [manag

iOS获取汉字的拼音

在iOS开发中经常涉及到汉字的排序,最常见的就是需要根据首字母的字符顺序排列,比如常见的通讯录等.总结出来,大致可以分为两种方法,其中参考文献[1]中提供的方法十分复杂,并且代码量很大,不建议采用.另一种方法是直接采用Core Foundation中提供的方法,十分简单,封装好的代码如下: 1 - (NSString *)transform:(NSString *)chinese{ 2 //将NSString装换成NSMutableString 3 NSMutableString *pinyin

iOS获取本地视频和网络URL视频的缩略图方法

iOS获取本地视频和网络URL视频的缩略图方法 字数222 阅读612 评论0 喜欢13 首先大家先添加AVFoundation和CoreMedia.framework两个框架 第一种本地视频获取缩略图 NSString *path = @"www.51ios.net/本地路径" MPMoviePlayerController *51iosMPMovie = [[MPMoviePlayerController alloc] initWithContentURL:[NSURL fileU

ios 获取屏幕的属性和宽度

1.app尺寸,去掉状态栏 CGRect r = [ UIScreen mainScreen ].applicationFrame; r=0,20,320,460 另外:self.view.bounds.size 2.屏幕尺寸 CGRect rx = [ UIScreen mainScreen ].bounds; r=0,0,320,480 3.状态栏尺寸 CGRect rect; rect = [[UIApplication sharedApplication] statusBarFrame]

ios 获取当前视图第一响应者

Football on Table 题意:一些杆上有人,人有一个宽度,然后现在有一个球射过去,要求出球不会碰到任何人的概率 思路:计算出每根杆的概率,之后累乘,计算杆的概率的时候,可以先把每块人的区间长度再移动过程中会覆盖多少长度累加出来,然后(1?总和/可移动距离)就是不会碰到的概率 代码: #include <stdio.h> #include <string.h> #include <math.h> const double eps = 1e-8; int t,

iOS 获取当前时间格式化字符串

iOS 获取当前时间格式化字符串 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS.Android.Html5.Arduino.pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作. // 获取系统当前时间 NSDate * date = [NSDate date]; NSDateFormatt

iOS获取app图标和启动图片名字(AppIcon and LaunchImage&#39;s name)

在某种场景下,可能我们需要获取app的图标名称和启动图片的名称.比如说app在前台时,收到了远程通知但是通知栏是不会有通知提醒的,这时我想做个模拟通知提示,需要用到icon名称:再比如在加载某个控制器时,想设置该控制器的背景图片为启动图片,需要用到启动图片名称. 而事实上icon图片放在系统AppIcon文件夹里,启动图片放在系统LaunchImage文件夹里,取这些图片的名称和其他一般资源图片名称不一样. 为了方便举例子,咱们先简单粗暴点 假设当前项目只支持iPhone设备,并且只支持竖屏:而

iOS获取设备型号、装置类型等信息

iOS获取设备型号.设备类型等信息 设备标识 关于设备标识,历史上盛行过很多英雄,比如UDID.Mac地址.OpenUDID等,然而他们都陆陆续续倒在了苹果的门下.苹果目前提供了2个方法供App获取设备标识:idfa和idfv idfa:全称advertisingIdentifier,官方解释是广告标识,适用于广告推广,这个建议不要轻易使用,如果用了,则App里必须提供广告功能,否则很有可能会在AppStore审核时被拒.而且idfa是可以被用户关闭的(设置->隐私),一旦被关闭,就获取不到了.

iOS获取当前设备方向

http://www.cnblogs.com/Yukang1989/p/3160980.html iOS获取当前设备方向 三种方式: self.interfaceOrientation [[UIApplication sharedApplication] statusBarOrientation] [[UIDevice currentDevice] orientation] 但是实际使用时有区别的: self.interfaceOrientation returns UIInterfaceOri