iOS 之CMPedometer详解

3,848 阅读4分钟

简介

iOS8以后,CoreMotion框架中为我们提供了一个获取用户活动信息的对象CMPedometer,通过CMPedometer我们可以获取用户的活动信息,如:行走步数,行走的公里数,上下楼层数以及平均速度等。以获取运动步数为例:

iOS中获取运动步数,一般有两种方法:

  • CMPedometer
  • HealthKit框架从手机健康App中获取数据

需要了解HealthKit可以看这个:HealthKit

其实健康App也是通过CMPedometer获取的步数,两者得到步数有一定的差异,一般后者的数据比前者多。

因为CMPedometer获取的是iPhone的步数,如果用户佩戴iWatch的话,在相同或有交叉时间内,iPhone和iWatch都记录了运动步数,笔者猜测健康App进行了一些校准和优化算法,比如存在iWatch数据,如果同一时间段iPhone与iWatch都运动的话,猜测取的是数值较高的那一组设备数据。

两种方法都需要用户授权,一般我们需要获取用户运动步数的时候,优先使用第二种,从健康App中获取数据。如果用户不授权,则再通过CMPedometer获取步数。

注意

需要注意的是,健康App中的步数值,用户是可以手动编辑添加的,所以我们在获取的时候,一定要过滤人为手动编辑的值。

CMPedometer

基础类

  • CMPedometer:计步器类
  • CMPedometerEvent:计步器事件类
  • CMPedometerData:计步器数据类,步数,距离,楼层等信息通过此类获取

相关功能判断:

  • isStepCountingAvailable:是否支持步数计数功能
  • isDistanceAvailable:是否支持距离估计
  • isFloorCountingAvailable:是否支持计算楼梯段数
  • isPaceAvailable:是否支持速度估计
  • isCadenceAvailable:是否支持频率估算
  • isPedometerEventTrackingAvailable:是否支持计步器事件

注意事项:

  • 需导入<CoreMotion/CoreMotion.h>
  • 此类在iOS8之后才可用,在iOS8之前,使用CMStepCounter类来获取步数(iPhone 5S及以上机型)
  • 使用CMPedometer对象,需要设计成全局属性,强引用,防止提前释放
  • 在info.plist文件中增加NSMotionUsageDescription键,简单描述下

主要方法:

1、

- (void)queryPedometerDataFromDate:(NSDate *) 
                       starttoDate:(NSDate *)end 
                       withHandler:(CMPedometerHandler)handler;

在给定时间范围内查询用户的行走活动,数据最多可以使用7天内有效,返回的数据是从系统范围的历史记录中计算出来的,该历史记录是在后台连续收集的。结果返回在串行队列中。

2、

- (void)startPedometerUpdatesFromDate:(NSDate *)start
                          withHandler:(CMPedometerHandler)handler;

在串行队列上启动一系列连续计步器更新到处理程序。对于每次更新,应用程序将从指定的开始日期和与最新确定相关联的时间戳开始收到累积的行人活动。如果应用程序在后台进行背景调整,则应用程序将在下次更新中收到在后台期间累积的所有行人活动。

说人话就是从某一时间段开始,连续的采集步数,距离,楼层等信息,当设备中的活动数据发生变更就会回调此方法。

对应stopPedometerUpdates方法,停止计步器更新运动数据

3、

- (void)startPedometerEventUpdatesWithHandler:(CMPedometerEventHandler)handler

在串行队列上启动计步器事件更新(事件时间、暂停、重新开始等、)。事件仅在应用程序在前台/后台运行时可用。

对应stopPedometerEventUpdates方法,停止计步器事件更新

完整代码

#import "ViewController.h"
#import <CoreMotion/CoreMotion.h>

@interface ViewController ()
// 定义为属性,否则可能无法正常获取运动信息
// 若错误Error Domain=CMErrorDomain Code=104(或者103?),则pedometer没有设置为property属性
@property (nonatomic,strong) CMPedometer *pedometer;
@end

@implementation ViewController

- (CMPedometer *)pedometer{
    if(_pedometer == nil){
        _pedometer = [[CMPedometer alloc]init];
    }
    return _pedometer;
}

- (void)viewDidLoad {

    [super viewDidLoad];
    // 通过计步器获取运动信息
    [self usePedometer];
}

- (void)usePedometer{
    if ([CMPedometer isStepCountingAvailable] && 
        [CMPedometer isDistanceAvailable] && 
        [CMPedometer isFloorCountingAvailable] && 
        [CMPedometer isPaceAvailable] && 
        [CMPedometer isCadenceAvailable] && 
        [CMPedometer isPedometerEventTrackingAvailable]){ 
        
        NSCalendar *calendar = [NSCalendar currentCalendar];
        NSDate *now = [NSDate date];
        // 开始时间
        NSDate *startDate = [calendar startOfDayForDate:now];
        // 结束时间
        NSDate *endDate = [calendar dateByAddingUnit:NSCalendarUnitDay value:1 toDate:startDate options:0];

        // 查询当天数据
        [self.pedometer queryPedometerDataFromDate:startDate toDate:endDate withHandler:^(CMPedometerData * _Nullable pedometerData, NSError * _Nullable error) {
            if (error) {
                NSLog(@"获取失败");
            } else {

                // 步数
                double stepCount = [pedometerData.numberOfSteps doubleValue];
                NSLog(@" 通过计步器获取步数: %ld",(long)stepCount);
                
                // 距离,若值为nil,不支持平台(下同)
                double distance = [pedometerData.distance doubleValue];
                NSLog(@" 通过计步器获取估计距离(米): %ld",(long)distance);
                
                // 上楼
                NSInteger floorsAscended = [pedometerData.floorsAscended integerValue];
                NSLog(@" 通过计步器获取上楼: %ld",(long)floorsAscended);
                
                // 下楼
                NSInteger floorsDescended = [pedometerData.floorsDescended integerValue];
                NSLog(@" 通过计步器获取下楼: %ld",(long)floorsDescended);
               
                // 速度 s/m
                double currentPace = [pedometerData.currentPace doubleValue];
                NSLog(@" 通过计步器获取速度(秒/米): %ld",(long)currentPace);
                
                // 频率(step/s)
                double currentCadence = [pedometerData.currentCadence doubleValue];
                NSLog(@" 通过计步器获取频率(步/秒): %ld",(long)currentCadence);
                
                // 记得去主线程更新UI
                dispatch_async(dispatch_get_main_queue(), ^{
               
                });             
            }
        }];

        // 获取更新数据
        [self.pedometer startPedometerUpdatesFromDate:[NSDate date] withHandler:^(CMPedometerData * _Nullable pedometerData, NSError * _Nullable error) {
           // 处理同上

           // 停止更新运动数据
           // [self.pedometer stopPedometerUpdates];
        }];

        // 计步器事件
        [self.pedometer startPedometerEventUpdatesWithHandler:^(CMPedometerEvent * _Nullable pedometerEvent, NSError * _Nullable error) {
            // 停止计步器事件更新
            //[self.pedometer stopPedometerEventUpdates];
        }];
    }
}

@end