iOS 实时计步功能的简单实现
写作背景
今天就和大家聊一聊iOS中计步功能的实现。为什么会突然想到实现这个功能呢?,哎,都是泪呀。 之前面试期间有家公司就是做计步功能的。问我如何实现?然而在我印象中直接调用
HealthKit
框架获取苹果的健康应用数据不就行了。。。
然后Boss说这样非常容易作弊。请戳我,固然没有修改成功,那是微信团队有这样的技术实力,去判断数据来源 所以不打算采用这样的方式。
于是乎,话音刚落。就被Boss反问一句:“那如果不让你用HealthKit
框架呢?”
我:“啊,这样啊。。。我也不知道”
Boss:“......(此处你们懂得)”
首先iOS中的计步功能,比较普遍的应该有两种方式:
No.1
非常直接了当一种方式,直接调用系统的健康数据,基于
HealthKit
框架的,但是貌似是一小时更新一次数据。如果要实时获取步数,这种方式并不是最佳。
这种例子已经有很多了。就不再贴此代码了。
http://www.jianshu.com/p/42e913588380
No.2
基于
CoreMotion
框架,本人也是才疏学浅,了解的并不多。大家可以看下这个框架中一些常见的其他应用场景
http://www.jianshu.com/p/50aa5dbc6d16
本文重点介绍的一种方式:加速度传感器
运动传感器\加速度传感器\加速计(Motion/Accelerometer Sensor)
最早出现在iOS设备上的传感器之一
加速计用于检测设备在X、Y、Z轴上的加速度 (哪个方向有力的作用)
加速计可以用于检测设备的摇晃,经典应用场景
-
摇一摇
-
计步器
如果有对传感器很陌生,想要普及下的童鞋,下面链接会帮到你
http://www.cnblogs.com/dongwenbo/p/4301530.html
传感器的类型
运动传感器\加速度传感器\加速计(Motion/Accelerometer Sensor)
环境光传感器(Ambient Light Sensor)
距离传感器(Proximity Sensor)
磁力计传感器(Magnetometer Sensor)
内部温度传感器(Internal Temperature Sensor)
湿度传感器(Moisture Sensor)
陀螺仪(Gyroscope)
下面是实现代码,因为要实时计步,就写成了单例
StepManager.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface StepManager : NSObject
@property (nonatomic) NSInteger step; // 运动步数(总计)
+ (StepManager *)sharedManager;
//开始计步
- (void)startWithStep;
//得到计步所消耗的卡路里
//+ (NSInteger)getStepCalorie;
//
//得到所走的路程(单位:米)
//+ (CGFloat)getStepDistance;
//
//得到运动所用的时间
//+ (NSInteger)getStepTime;
@end
StepManager.m
#import "StepManager.h"
#import "StepModel.h"
#import <CoreMotion/CoreMotion.h>
// 计步器开始计步时间(秒)
#define ACCELERO_START_TIME 2
// 计步器开始计步步数(步)
#define ACCELERO_START_STEP 100
// 数据库存储步数采集间隔(步)
#define DB_STEP_INTERVAL 1
@interface StepManager ()
{
NSMutableArray *arrAll; // 加速度传感器采集的原始数组
int record_no_save;
int record_no;
NSDate *lastDate;
}
@property (nonatomic) NSInteger startStep; // 计步器开始步数
@property (nonatomic, retain) NSMutableArray *arrSteps; // 步数数组
@property (nonatomic, retain) NSMutableArray *arrStepsSave; // 数据库纪录步数数组
@property (nonatomic) CGFloat gpsDistance; // GPS轨迹的移动距离(总计)
@property (nonatomic) CGFloat agoGpsDistance; // GPS轨迹的移动距离(之前)
@property (nonatomic) CGFloat agoActionDistance; // 实际运动的移动距离(之前)
@property (nonatomic, retain) NSString *actionId; // 运动识别ID
@property (nonatomic) CGFloat distance; // 运动里程(总计)
@property (nonatomic) NSInteger calorie; // 消耗卡路里(总计)
@property (nonatomic) NSInteger second; // 运动用时(总计)
@end
@implementation StepManager
static StepManager *sharedManager;
static CMMotionManager *motionManager;
+ (StepManager *)sharedManager
{
@synchronized (self) {
if (!sharedManager) {
sharedManager = [[StepManager alloc]init];
motionManager = [[CMMotionManager alloc]init];
}
}
return sharedManager;
}
//开始计步
- (void)startWithStep
{
if (!motionManager.isAccelerometerAvailable) {
NSLog(@"加速度传感器不可用");
return;
}else {
motionManager.accelerometerUpdateInterval = 1.0/40;
}
[self startAccelerometer];
}
- (void)startAccelerometer
{
/* @try 。。。@catch 。。。
* @try 后面跟或许会出现异常的程序,代码块
* @catch 当@try抛出异常时 系统会进行异常捕捉 具体可以了解下 NSException 异常类 @catch 在这里处理 程序所抛出的异常
*/
@try
{
//如果不支持陀螺仪,需要用加速传感器来采集数据
if (!motionManager.isAccelerometerActive) {// isAccelerometerAvailable方法用来查看加速度器的状态:是否Active(启动)。
// 加速度传感器采集的原始数组
if (arrAll == nil) {
arrAll = [[NSMutableArray alloc] init];
}
else {
[arrAll removeAllObjects];
}
/*
1.push方式
这种方式,是实时获取到Accelerometer的数据,并且用相应的队列来显示。即主动获取加速计的数据。
*/
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[motionManager startAccelerometerUpdatesToQueue:queue withHandler:^(CMAccelerometerData *accelerometerData, NSError *error){
if (!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;
//将信息保存在步数模型中
StepModel *stepsAll = [[StepModel 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;
}
StepModel *bufferPrevious = (StepModel *)[arrBuffer objectAtIndex:i-1];
StepModel *bufferCurrent = (StepModel *)[arrBuffer objectAtIndex:i];
StepModel *bufferNext = (StepModel *)[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++) {
StepModel *caidianCurrent = (StepModel *)[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 = (int)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 (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(@"步数%ld",self.step);
int intervalMillSecond = [caidianCurrent.date timeIntervalSinceDate:[[self.arrSteps lastObject] date]] * 1000;
if (intervalMillSecond >= 1000) {
record_no++;
caidianCurrent.record_no = record_no;
caidianCurrent.step = (int)self.step;
[self.arrSteps addObject:caidianCurrent];
}
// 每隔100步保存一条数据(将来插入DB用)
StepModel *arrStepsSaveVHSSteps = (StepModel *)[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];
NSLog(@"---***%ld",self.step);
// 备份当前运动数据至文件中,以备APP异常退出时数据也不会丢失
// [self bkRunningData];
}
}
}
// 运动提醒检查
// [self checkActionAlarm];
}
}
}
}];
}
}@catch (NSException * e) {
NSLog(@"Exception: %@", e);
return;
}
}
////得到计步所消耗的卡路里
//+ (NSInteger)getStepCalorie
//{
// 在这里原谅我并没有对其实现。本以为卡路里和步数的换算单位,一个公式就可以了。不查不知道,一查吓一跳原来还和其他众多因素有关:走路的快慢,步子的大小,体重的大小等等有关。。。笔者已吓尿,还是找算法大牛吧。
//}
//
////得到所走的路程(单位:米)
//+ (CGFloat)getStepDistance
//{
//
//}
//
////得到运动所用的时间
//+ (NSInteger)getStepTime
//{
//
//}
其次是外部调用
#import "ViewController.h"
#import "StepManager.h"
@interface ViewController ()
{
NSTimer *_timer;
UILabel *lable;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[StepManager sharedManager] startWithStep];
lable =[[ UILabel alloc]initWithFrame:CGRectMake(100, 300, 300, 40)];
[self.view addSubview:lable];
_timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(getStepNumber) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
}
- (void)getStepNumber
{
lable.text = [NSString stringWithFormat:@"我走了 %ld步",[StepManager sharedManager].step];
}
另外还需要注意的一点是,因为我们要实时计步所以,当我们应用程序在后台的时候仍然需要计步,所以要做一些处理
AppDelegate.m
- (void)applicationDidEnterBackground:(UIApplication *)application {
//播放一段无声音乐,使其可以一直在后台进行计步 此方法为第三方 若要详细了解,请下载demo自行研究
[[MMPDeepSleepPreventer sharedSingleton] startPreventSleep];
}
最后
至此,就先普及这么多吧。文中的说话方式和一些内容纯属娱乐,希望大家不要介意。我也是菜鸟,希望大神可以批评指正。因为这样才能进步。文中链接比较多。大家也可以收藏下本文,今后备不时之需。实时计步还算挺火的。
对了 附上demo 链接
大哥大姐,别打脸!点我传送