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/;
}
可用的话开始实现统计步数的算法
@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, ) + pow(y, ) + pow(z, )) - ; //将信息保存在步数模型中
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 == ) { // 步数缓存数组
NSMutableArray *arrBuffer = [[NSMutableArray alloc] init]; arrBuffer = [arrAll copy];
[arrAll removeAllObjects]; // 踩点数组
NSMutableArray *arrCaiDian = [[NSMutableArray alloc] init]; //遍历步数缓存数组
for (int i = ; i < arrBuffer.count - ; i++) {
//如果数组个数大于3,继续,否则跳出循环,用连续的三个点,要判断其振幅是否一样,如果一样,然并卵
if (![arrBuffer objectAtIndex:i-] || ![arrBuffer objectAtIndex:i] || ![arrBuffer objectAtIndex:i+])
{
continue;
}
VHSSteps *bufferPrevious = (VHSSteps *)[arrBuffer objectAtIndex:i-];
VHSSteps *bufferCurrent = (VHSSteps *)[arrBuffer objectAtIndex:i];
VHSSteps *bufferNext = (VHSSteps *)[arrBuffer objectAtIndex:i+];
//控制震动幅度,,,,,,根据震动幅度让其加入踩点数组,
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 = ; j < arrCaiDian.count; j++) {
VHSSteps *caidianCurrent = (VHSSteps *)[arrCaiDian objectAtIndex:j]; //如果之前的步数为0,则重新开始记录
if (self.arrSteps.count == ) {
//上次记录的时间
lastDate = caidianCurrent.date; // 重新开始时,纪录No初始化
record_no = ;
record_no_save = ; // 运动识别号
NSTimeInterval interval = [caidianCurrent.date timeIntervalSince1970];
NSNumber *numInter = [[NSNumber alloc] initWithDouble:interval*];
long long llInter = numInter.longLongValue;
//运动识别id
self.actionId = [NSString stringWithFormat:@"%lld",llInter]; self.distance = 0.00f;
self.second = ;
self.calorie = ;
self.step = ; 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] * ; // 步行最大每秒2.5步,跑步最大每秒3.5步,超过此范围,数据有可能丢失
int min = ;
if (intervalCaidian >= min) { if (self.motionManager.isAccelerometerActive) { //存一下时间
lastDate = caidianCurrent.date; if (intervalCaidian >= ACCELERO_START_TIME * ) {// 计步器开始计步时间(秒)
self.startStep = ;
} 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]] * ;
if (intervalMillSecond >= ) { 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 == || intervalStep >= DB_STEP_INTERVAL) {
//保存次数
record_no_save++;
caidianCurrent.record_no = record_no_save;
[self.arrStepsSave addObject:caidianCurrent];
}
}
}
}
}
}
}]; }
}@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:
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: toDate:startDate options:]; 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:**];//定义需要获取的数据为步数
HKQuantityType *quantityType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
//设置获取的步数时间间隔
NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
dateComponents.day = ; NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:today endDate:next options:HKQueryOptionStrictStartDate];
//创建查询统计对象collectionQuery
HKStatisticsCollectionQuery *collectionQuery = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:quantityType quantitySamplePredicate:predicate options: HKStatisticsOptionCumulativeSum | HKStatisticsOptionSeparateBySource anchorDate:[NSDate dateWithTimeIntervalSince1970:] intervalComponents:dateComponents];
collectionQuery.initialResultsHandler = ^(HKStatisticsCollectionQuery *query, HKStatisticsCollection * __nullable result, NSError * __nullable error) {
float numberOfSteps = ;
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];
};
[self.healthStore executeQuery:collectionQuery];
}
else
{
NSLog(@"获取步数权限失败");
}
}];
demo完整代码在这里:
iOS获取健康步数从加速计到healthkit的更多相关文章
- iOS获取设备唯一标识的8种方法
8种iOS获取设备唯一标识的方法,希望对大家有用. UDID UDID(Unique Device Identifier),iOS 设备的唯一识别码,是一个40位十六进制序列(越狱的设备通过某些工具可 ...
- iOS 获取文件的目录路径的几种方法 [转]
iOS 获取文件的目录路径的几种方法 2 years ago davidzhang iphone沙箱模型的有四个文件夹,分别是什么,永久数据存储一般放在什么位置,得到模拟器的路径的简单方式是什么. d ...
- iOS获取设备型号、装置类型等信息
iOS获取设备型号.设备类型等信息 设备标识 关于设备标识,历史上盛行过很多英雄,比如UDID.Mac地址.OpenUDID等,然而他们都陆陆续续倒在了苹果的门下.苹果目前提供了2个方法供App获取设 ...
- Swift3.0 iOS获取当前时间 - 年月日时分秒星期
Swift3.0 iOS获取当前时间 - 年月日时分秒星期func getTimes() -> [Int] { var timers: [Int] = [] // 返回的数组 let calen ...
- IOS 获取最新设备型号方法
1.IOS 获取最新设备型号方法列表最新对照表:http://theiphonewiki.com/wiki/Models方法: #import "sys/utsname.h” struct ...
- ios 获取通讯录的所有信息
iOS获取通讯录全部信息 ABAddressBookRef addressBook = ABAddressBookCreate(); CFArrayRef results = ABAddressBoo ...
- iOS获取汉字的拼音
在iOS开发中经常涉及到汉字的排序,最常见的就是需要根据首字母的字符顺序排列,比如常见的通讯录等.总结出来,大致可以分为两种方法,其中参考文献[1]中提供的方法十分复杂,利用查表的方法是先,并且代码量 ...
- IOS获取物理尺寸中7Plus中获取的是7的物理尺寸
IOS获取物理尺寸中7Plus中获取的是7的物理尺寸: 在开发调试过程中我的7Plus手机获取[uiscreen mainscreen].bounds为750 .1334. 解决方案:在手机中的显示 ...
- iOS: 获取文件路径
iOS: 获取文件路径 // 例如 - (NSString *)applicationDocumentsDirectory { return [NSSearchPathForDirectories ...
随机推荐
- 横向浅谈移动技术------( 原生,混合,web --- 谁能问鼎移动开发的明天)
目前移动互联网基本采用了NativeApp.WebApp.HybridApp三种开发模式,很难说这三种模式那种更优越,目前的情况可以说是三分天下吧,不同的开发者可以根据自己的实际情况选择不同的开发模式 ...
- SQL Server日期函数之获得一个月中的天数
SQL Server日期函数之获得一个月中的天数在实际中的应用比例还是占为多数的,如果你对这一技术,心存好奇的话,以下的文章将会揭开它的神秘面纱,望会在以后的学习或是工作中带来很大的帮助. 获得一个月 ...
- Python抓取双色球数据
数据来源网站http://baidu.lecai.com/lottery/draw/list/50?d=2013-01-01 HTML解析器http://pythonhosted.org/pyquer ...
- Yarn应用程序编程实例
Yarn自带的Application示例程序:DistributedShell 和 UnManaged AM1 DistributedShell ,故名思意,是一个分布式运行shell命令的应用程序, ...
- [BZOJ 1086] [SCOI2005] 王室联邦 【树分块】
题目链接:BZOJ - 1086 题目分析 这道题要求给树分块,使得每一块的大小在 [B, 3B] 之间,并且可以通过一个块外的节点(块根)使得整个块联通. 那么我们使用一种 DFS,维护一个栈,DF ...
- iReport中求和的问题
数据库取出值TAX_AMT,但是不想在数据库里面计算,太麻烦,后面group by 字段太多.那就放到ireport里面去计算咯 在字段的如下位置进行计算吧.
- DJANGO中获取登陆用名及别名
练练,标准认证的. VIEW中导入: from django.contrib.auth.models import User TEMPLATE中可引用: 列表 {{ user.username }}{ ...
- 在非UI线程中更改UI(Delphi使用隐藏窗口来处理,QT使用信号槽)
在Delphi里我记得是使用TThread.Synchronize(TThreadMethod),原理是利用了一个隐藏窗口来处理. 在QT Debug模式一下,碰到了同样的问题,显示错误: canno ...
- vijosP1902学姐的清晨问候
题目:https://vijos.org/p/1902 题解:sb题...扫一遍每个字母出现的次数即可 代码: #include<cstdio> #include<cstdlib&g ...
- Light OJ 1037 - Agent 47(预处理状态压缩DP)
题目大意: 有个特工要执行任务,他会遭遇到最多15个目标,特工必须把他们全部杀死.当他杀死一个目标后他可以使用目标的武器来杀死其他人.因此他必须有一个杀人的顺序,使得他开枪的次数最小. 现在给你一个表 ...