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

news/2024/12/2 19:38:58/

转自:http://www.cnblogs.com/dongliu/p/5629065.html

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

 

在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;//运动识别idself.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:msgdelegate:nilcancelButtonTitle:@"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.operationQueueupdateOn:1withHandler:^(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.operationQueuewithHandler:^(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不支持HealthKitif(![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];//创建查询统计对象collectionQueryHKStatisticsCollectionQuery *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的还没有整理好,过段时间再补充吧。



http://www.ppmy.cn/news/695333.html

相关文章

Android 计步器 - 手机自带系统级的 健康运动App 授权

#1. Android 手机自带系统级的 运动健康App 于是我整理一份列表&#xff0c;如下&#xff1a; 三星手机上的 “三星健康” ; 三星开发者平台&#xff1a;SDK&#xff1a; Hello Health Data | Samsung Developers华为手机上的“运动健康”&#xff1b;华为开发者平台&#x…

Elasticsearch同义词自定义词库未生效原因

检查文件及目录是否存在所有节点配置完之后都要重启检查测试脚本是否正确的,我遇到的问题就是脚本不正确但是确能执行,就是拿不到正确结果 错误脚本: PUT test_idx_analyzer3 {"settings": {// 这里是分析器 不是分词器; 分词器可以包含设置过滤器和分词器"an…

论文教程之如何写教学类研究方法

五大常用教学研究方法 1.文献研究法 2.行动研究法 3. 问卷调查法 4. 课堂观察法 5. 访谈法模版 1.文献研究法 本研究在理论分析阶段和xxxx模型构建阶段应用文献分析法&#xff0c;通过 CNKI、IEE 等数据库搜集、查阅相关研究文献。首先以“计算思维”为主题进行文献检索&…

论文写作的常用方法

写论文时&#xff0c;经常要介绍自己所使用的研究方法。那么&#xff0c;常用的研究方法有哪些呢?以下是论文网站小编搜集整理的论文写作的常用研究方法&#xff0c;供大家阅读参考。 一、方法 系统科学方法 思维方法 数学方法 描述法 经验总结法 信息研究方法 探索法 模拟法(…

C++实现websocket单server单client全双工通信(基于boost!!!)

自身环境&#xff1a;ubuntu18.04gcc7.5.0boost1.7,3 环境配置 gcc或者g一般都有&#xff0c;这里主要介绍一下boost的配置方法   执行如下代码&#xff1a; wget https://boostorg.jfrog.io/artifactory/main/release/1.73.0/source/boost_1_73_0.tar.bz2 --no-check-cert…

如何通过Python下载GSMap数据集(解决.dat无法打开的问题)?

目录 01 前言 02 GSMap-MVK的存储方式和数据集介绍 03 代码实现 01 前言 这么晚了我还是希望将这篇博客写一下&#xff0c;记录生活。 我所下载的数据集为GSMap-MVK数据集&#xff0c;延迟大概2.5月左右我记得.边下载我就想着先处理着吧。 例如&#xff0c;其中一个文件如…

MACBOOK快捷键输入

快捷键输入 通用 Command Z 撤销Command X 剪切Command C 拷贝&#xff08;Copy&#xff09;Command V 粘贴Command A 全选&#xff08;All&#xff09;Command S 保存&#xff08;Save)Command F 查找&#xff08;Find&#xff09; 截图 Command Shift 3 截取全部…

Macbook常用快捷键

1. Mac常用快捷键符号 2. 快捷键操作 2.1 基本 操作 Command-Z 撤销Command-X 剪切  Command-C 拷贝&#xff08;Copy&#xff09;  Command-V 粘贴  Command-A 全选&#xff08;All&#xff09;Command-S 保存&#xff08;Save) Command-F 查找&#xff08;Find&…