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 链接
大哥大姐,别打脸!点我传送

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容