又改需求了,所以又換了全新的統計步數的方法,整理一下吧。
在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; //運動識別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 = 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]; } } } } } } }]; } }@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:1 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: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.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: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];//創建查詢統計對象collectionQuery
HKStatisticsCollectionQuery *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]; }; [self.healthStore executeQuery:collectionQuery]; } else { NSLog(@"獲取步數權限失敗"); } }];
demo完整代碼在這里: