iOS 實(shí)時(shí)計(jì)步功能的簡單實(shí)現(xiàn)

寫作背景

今天就和大家聊一聊iOS中計(jì)步功能的實(shí)現(xiàn)。為什么會突然想到實(shí)現(xiàn)這個功能呢伐庭?喧兄,哎,都是淚呀辽装。 之前面試期間有家公司就是做計(jì)步功能的。問我如何實(shí)現(xiàn)相味?然而在我印象中直接調(diào)用HealthKit框架獲取蘋果的健康應(yīng)用數(shù)據(jù)不就行了拾积。。丰涉。
然后Boss說這樣非常容易作弊拓巧。請戳我,固然沒有修改成功,那是微信團(tuán)隊(duì)有這樣的技術(shù)實(shí)力一死,去判斷數(shù)據(jù)來源 所以不打算采用這樣的方式肛度。
于是乎,話音剛落投慈。就被Boss反問一句:“那如果不讓你用HealthKit框架呢贤斜?”
我:“啊,這樣啊逛裤。瘩绒。。我也不知道”
Boss:“......(此處你們懂得)”

首先iOS中的計(jì)步功能带族,比較普遍的應(yīng)該有兩種方式:

No.1

非常直接了當(dāng)一種方式锁荔,直接調(diào)用系統(tǒng)的健康數(shù)據(jù),基于HealthKit框架的蝙砌,但是貌似是一小時(shí)更新一次數(shù)據(jù)阳堕。如果要實(shí)時(shí)獲取步數(shù)跋理,這種方式并不是最佳。
這種例子已經(jīng)有很多了恬总。就不再貼此代碼了前普。
http://www.reibang.com/p/42e913588380

No.2

基于CoreMotion框架,本人也是才疏學(xué)淺壹堰,了解的并不多拭卿。大家可以看下這個框架中一些常見的其他應(yīng)用場景

http://www.reibang.com/p/50aa5dbc6d16

本文重點(diǎn)介紹的一種方式:加速度傳感器

運(yùn)動傳感器\加速度傳感器\加速計(jì)(Motion/Accelerometer Sensor)

最早出現(xiàn)在iOS設(shè)備上的傳感器之一

加速計(jì)用于檢測設(shè)備在X、Y贱纠、Z軸上的加速度 (哪個方向有力的作用)

加速計(jì)可以用于檢測設(shè)備的搖晃峻厚,經(jīng)典應(yīng)用場景

  • 搖一搖
  • 計(jì)步器

如果有對傳感器很陌生,想要普及下的童鞋谆焊,下面鏈接會幫到你
http://www.cnblogs.com/dongwenbo/p/4301530.html

傳感器的類型
運(yùn)動傳感器\加速度傳感器\加速計(jì)(Motion/Accelerometer Sensor)
環(huán)境光傳感器(Ambient Light Sensor)
距離傳感器(Proximity Sensor)
磁力計(jì)傳感器(Magnetometer Sensor)
內(nèi)部溫度傳感器(Internal Temperature Sensor)
濕度傳感器(Moisture Sensor)
陀螺儀(Gyroscope)

下面是實(shí)現(xiàn)代碼惠桃,因?yàn)橐獙?shí)時(shí)計(jì)步,就寫成了單例

StepManager.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface StepManager : NSObject


@property (nonatomic) NSInteger step;                       // 運(yùn)動步數(shù)(總計(jì))

+ (StepManager *)sharedManager;


//開始計(jì)步
- (void)startWithStep;

//得到計(jì)步所消耗的卡路里
//+ (NSInteger)getStepCalorie;
//
//得到所走的路程(單位:米)
//+ (CGFloat)getStepDistance;
//
//得到運(yùn)動所用的時(shí)間
//+ (NSInteger)getStepTime;

@end

StepManager.m

#import "StepManager.h"
#import "StepModel.h"
#import <CoreMotion/CoreMotion.h>

// 計(jì)步器開始計(jì)步時(shí)間(秒)
#define ACCELERO_START_TIME 2

// 計(jì)步器開始計(jì)步步數(shù)(步)
#define ACCELERO_START_STEP 100

// 數(shù)據(jù)庫存儲步數(shù)采集間隔(步)
#define DB_STEP_INTERVAL 1


@interface StepManager ()

{

    
    NSMutableArray *arrAll;                 // 加速度傳感器采集的原始數(shù)組
    int record_no_save;
    int record_no;
    NSDate *lastDate;
    
}
@property (nonatomic) NSInteger startStep;                          // 計(jì)步器開始步數(shù)

@property (nonatomic, retain) NSMutableArray *arrSteps;         // 步數(shù)數(shù)組
@property (nonatomic, retain) NSMutableArray *arrStepsSave;     // 數(shù)據(jù)庫紀(jì)錄步數(shù)數(shù)組

@property (nonatomic) CGFloat gpsDistance;                  // GPS軌跡的移動距離(總計(jì))
@property (nonatomic) CGFloat agoGpsDistance;               // GPS軌跡的移動距離(之前)
@property (nonatomic) CGFloat agoActionDistance;            // 實(shí)際運(yùn)動的移動距離(之前)

@property (nonatomic, retain) NSString *actionId;           // 運(yùn)動識別ID
@property (nonatomic) CGFloat distance;                     // 運(yùn)動里程(總計(jì))
@property (nonatomic) NSInteger calorie;                    // 消耗卡路里(總計(jì))
@property (nonatomic) NSInteger second;                     // 運(yùn)動用時(shí)(總計(jì))

@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;
}

//開始計(jì)步
- (void)startWithStep
{
    if (!motionManager.isAccelerometerAvailable) {
        NSLog(@"加速度傳感器不可用");
        return;
    }else {
        
        motionManager.accelerometerUpdateInterval = 1.0/40;
    }
    [self startAccelerometer];
  
}

- (void)startAccelerometer
{
 /*  @try 辖试。辜王。。@catch 罐孝。誓禁。。
  *  @try 后面跟或許會出現(xiàn)異常的程序肾档,代碼塊
  *  @catch  當(dāng)@try拋出異常時(shí) 系統(tǒng)會進(jìn)行異常捕捉  具體可以了解下 NSException 異常類     @catch 在這里處理 程序所拋出的異常
  */
           
    @try  
    {
        //如果不支持陀螺儀,需要用加速傳感器來采集數(shù)據(jù)
        if (!motionManager.isAccelerometerActive) {//  isAccelerometerAvailable方法用來查看加速度器的狀態(tài):是否Active(啟動)。
            
            // 加速度傳感器采集的原始數(shù)組
            if (arrAll == nil) {
                arrAll = [[NSMutableArray alloc] init];
            }
            else {
                [arrAll removeAllObjects];
            }
            
            /*
             1.push方式
             這種方式辫继,是實(shí)時(shí)獲取到Accelerometer的數(shù)據(jù)怒见,并且用相應(yīng)的隊(duì)列來顯示。即主動獲取加速計(jì)的數(shù)據(jù)姑宽。
             */
            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值 ,根據(jù)它的大小來判斷是否計(jì)為1步.
                double g = sqrt(pow(x, 2) + pow(y, 2) + pow(z, 2)) - 1;
                
                //將信息保存在步數(shù)模型中
                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;
                // 加速度傳感器采集的原始數(shù)組
                [arrAll addObject:stepsAll];
                
                // 每采集10條遣耍,大約1.2秒的數(shù)據(jù)時(shí),進(jìn)行分析
                if (arrAll.count == 10) {
                    
                    // 步數(shù)緩存數(shù)組
                    NSMutableArray *arrBuffer = [[NSMutableArray alloc] init];
                    
                    arrBuffer = [arrAll copy];
                    [arrAll removeAllObjects];
                    
                    // 踩點(diǎn)數(shù)組
                    NSMutableArray *arrCaiDian = [[NSMutableArray alloc] init];
                    
                    //遍歷步數(shù)緩存數(shù)組
                    for (int i = 1; i < arrBuffer.count - 2; i++) {
                        //如果數(shù)組個數(shù)大于3,繼續(xù),否則跳出循環(huán),用連續(xù)的三個點(diǎn),要判斷其振幅是否一樣,如果一樣,然并卵
                        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];
                        //控制震動幅度,,,,,,根據(jù)震動幅度讓其加入踩點(diǎn)數(shù)組,
                        if (bufferCurrent.g < -0.12 && bufferCurrent.g < bufferPrevious.g && bufferCurrent.g < bufferNext.g) {
                            [arrCaiDian addObject:bufferCurrent];
                        }
                    }
                    
                    //如果沒有步數(shù)數(shù)組,初始化
                    if (nil == self.arrSteps) {
                        self.arrSteps = [[NSMutableArray alloc] init];
                        self.arrStepsSave = [[NSMutableArray alloc] init];
                    }
                    
                    // 踩點(diǎn)過濾
                    for (int j = 0; j < arrCaiDian.count; j++) {
                        StepModel *caidianCurrent = (StepModel *)[arrCaiDian objectAtIndex:j];
                        
                        //如果之前的步數(shù)為0,則重新開始記錄
                        if (self.arrSteps.count == 0) {
                            //上次記錄的時(shí)間
                            lastDate = caidianCurrent.date;
                            
                            // 重新開始時(shí)炮车,紀(jì)錄No初始化
                            record_no = 1;
                            record_no_save = 1;
                            
                            // 運(yùn)動識別號
                            NSTimeInterval interval = [caidianCurrent.date timeIntervalSince1970];
                            NSNumber *numInter = [[NSNumber alloc] initWithDouble:interval*1000];
                            long long llInter = numInter.longLongValue;
                            //運(yùn)動識別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步,超過此范圍瘦穆,數(shù)據(jù)有可能丟失
                            int min = 259;
                            if (intervalCaidian >= min) {
                                
                                if (motionManager.isAccelerometerActive) {
                                    
                                    //存一下時(shí)間
                                    lastDate = caidianCurrent.date;
                                    
                                    if (intervalCaidian >= ACCELERO_START_TIME * 1000) {// 計(jì)步器開始計(jì)步時(shí)間(秒)
                                        self.startStep = 0;
                                    }
                                    
                                    if (self.startStep < ACCELERO_START_STEP) {//計(jì)步器開始計(jì)步步數(shù) (步)
                                        
                                        self.startStep ++;
                                        break;
                                    }
                                    else if (self.startStep == ACCELERO_START_STEP) {
                                        self.startStep ++;
                                        // 計(jì)步器開始步數(shù)
                                        // 運(yùn)動步數(shù)(總計(jì))
                                        self.step = self.step + self.startStep;
                                    }
                                    else {
                                        self.step ++;
                                    }
                                   
                                
                   
                                    //步數(shù)在這里
                                    NSLog(@"步數(shù)%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步保存一條數(shù)據(jù)(將來插入DB用)
                                    StepModel *arrStepsSaveVHSSteps = (StepModel *)[self.arrStepsSave lastObject];
                                    int intervalStep = caidianCurrent.step - arrStepsSaveVHSSteps.step;
                                    
                                    // DB_STEP_INTERVAL 數(shù)據(jù)庫存儲步數(shù)采集間隔(步) 100步
                                    if (self.arrStepsSave.count == 1 || intervalStep >= DB_STEP_INTERVAL) {
                                        //保存次數(shù)
                                        record_no_save++;
                                        caidianCurrent.record_no = record_no_save;
                                        [self.arrStepsSave addObject:caidianCurrent];
                                       
                                                    NSLog(@"---***%ld",self.step);
                                // 備份當(dāng)前運(yùn)動數(shù)據(jù)至文件中纪隙,以備APP異常退出時(shí)數(shù)據(jù)也不會丟失
                                        // [self bkRunningData];
                                        
                                    }
                                }
                            }
                            
                            // 運(yùn)動提醒檢查
                            // [self checkActionAlarm];
                        }
                    }
                }
            }];
            
        }
    }@catch (NSException * e) {
        NSLog(@"Exception: %@", e);
        return;
    }
}

////得到計(jì)步所消耗的卡路里
//+ (NSInteger)getStepCalorie
//{
//    在這里原諒我并沒有對其實(shí)現(xiàn)。本以為卡路里和步數(shù)的換算單位扛或,一個公式就可以了绵咱。不查不知道,一查嚇一跳原來還和其他眾多因素有關(guān):走路的快慢熙兔,步子的大小悲伶,體重的大小等等有關(guān)艾恼。。麸锉。筆者已嚇尿钠绍,還是找算法大牛吧。
//}
//
////得到所走的路程(單位:米)
//+ (CGFloat)getStepDistance
//{
//    
//}
//
////得到運(yùn)動所用的時(shí)間
//+ (NSInteger)getStepTime
//{
//    
//}

其次是外部調(diào)用

#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];
}

另外還需要注意的一點(diǎn)是花沉,因?yàn)槲覀円獙?shí)時(shí)計(jì)步所以柳爽,當(dāng)我們應(yīng)用程序在后臺的時(shí)候仍然需要計(jì)步,所以要做一些處理
AppDelegate.m

- (void)applicationDidEnterBackground:(UIApplication *)application {
    //播放一段無聲音樂,使其可以一直在后臺進(jìn)行計(jì)步  此方法為第三方 若要詳細(xì)了解主穗,請下載demo自行研究
    [[MMPDeepSleepPreventer sharedSingleton] startPreventSleep];
}

最后

至此泻拦,就先普及這么多吧。文中的說話方式和一些內(nèi)容純屬娛樂忽媒,希望大家不要介意争拐。我也是菜鳥,希望大神可以批評指正晦雨。因?yàn)檫@樣才能進(jìn)步架曹。文中鏈接比較多。大家也可以收藏下本文闹瞧,今后備不時(shí)之需绑雄。實(shí)時(shí)計(jì)步還算挺火的。

對了 附上demo 鏈接
大哥大姐奥邮,別打臉万牺!點(diǎn)我傳送

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市洽腺,隨后出現(xiàn)的幾起案子脚粟,更是在濱河造成了極大的恐慌,老刑警劉巖蘸朋,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件核无,死亡現(xiàn)場離奇詭異,居然都是意外死亡藕坯,警方通過查閱死者的電腦和手機(jī)团南,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來炼彪,“玉大人吐根,你說我怎么就攤上這事》恚” “怎么了佑惠?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我膜楷,道長旭咽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任赌厅,我火速辦了婚禮穷绵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘特愿。我一直安慰自己仲墨,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布揍障。 她就那樣靜靜地躺著目养,像睡著了一般。 火紅的嫁衣襯著肌膚如雪毒嫡。 梳的紋絲不亂的頭發(fā)上癌蚁,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音兜畸,去河邊找鬼努释。 笑死,一個胖子當(dāng)著我的面吹牛咬摇,可吹牛的內(nèi)容都是我干的伐蒂。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼肛鹏,長吁一口氣:“原來是場噩夢啊……” “哼逸邦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起在扰,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤缕减,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后健田,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡佛纫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年妓局,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呈宇。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡好爬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出甥啄,到底是詐尸還是另有隱情存炮,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站穆桂,受9級特大地震影響宫盔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜享完,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一灼芭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧般又,春花似錦彼绷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至堕义,卻和暖如春猜旬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背胳螟。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工昔馋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人糖耸。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓秘遏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親嘉竟。 傳聞我的和親對象是個殘疾皇子邦危,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容