iOS 计步
计步方法有两种,一种是采用CMPedometer获取手机计步器数据,另一种是采用HealthKit框架从手机健康App中获取计步数据。
第一种使用:CMPedometer类来获取步数
使用CMPedometer类来获取步数和距离 使用时需要导入 #import <CoreMotion/CoreMotion.h> CMPedometer方法 + (BOOL)isStepCountingAvailable; 设备是否支持计步功能 + (BOOL)isDistanceAvailable; 除了计步,设备是否支持距离估计 + (BOOL)isFloorCountingAvailable; 除了计步,设备是否支持台阶计数 + (BOOL)isPaceAvailable NS_AVAILABLE(NA,9_0);除了计步,设备是否支持速度估计 +(BOOL)isCadenceAvailable NS_AVAILABLE(NA,9_0);除了计步,设备是否支持节奏估计 + (BOOL)isPedometerEventTrackingAvailable NS_AVAILABLE(NA,10_0) __WATCHOS_AVAILABLE(3_0);设备是否支持计步器事件 - (void)queryPedometerDataFromDate:(NSDate *)start toDate:(NSDate *)end withHandler:(CMPedometerHandler)handler;在给定时间范围内查询用户的行走活动,数据最多可以使用7天内有效,返回的数据是从系统范围的历史记录中计算出来的,该历史记录是在后台连续收集的。结果返回在串行队列中。 - (void)startPedometerUpdatesFromDate:(NSDate *)start withHandler:(CMPedometerHandler)handler;在串行队列上启动一系列连续计步器更新到处理程序。 对于每次更新,应用程序将从指定的开始日期和与最新确定相关联的时间戳开始收到累积的行人活动。 如果应用程序在后台进行背景调整,则应用程序将在下次更新中收到在后台期间累积的所有行人活动。 -(void)stopPedometerUpdates;停止计步器更新 -(void)startPedometerEventUpdatesWithHandler:(CMPedometerEventHandler)handler NS_AVAILABLE(NA,10_0) __WATCHOS_AVAILABLE(3_0);在串行队列上启动计步器事件更新。 事件仅在应用程序在前台/后台运行时可用。 -(void)stopPedometerEventUpdates NS_AVAILABLE(NA,10_0) __WATCHOS_AVAILABLE(3_0);停止计步器事件更新 CMPedometerData @property(readonly, nonatomic) NSDate *startDate;计步器数据有效期间的开始时间。这是会话或历史查询请求的开始时间。 @property(readonly, nonatomic) NSDate *endDate;计步器数据有效期间的结束时间。对于更新,这是最新更新的时间。 对于历史查询,这是请求的结束时间。 @property(readonly, nonatomic) NSNumber *numberOfSteps;用户的步数 @property(readonly, nonatomic, nullable) NSNumber *distance; 用户行走和跑步时估计的一米为单位的距离。若设备不支持则值为nil @property(readonly, nonatomic, nullable) NSNumber *floorsAscended;上楼的大概楼层数,若设备不支持则值为nil @property(readonly, nonatomic, nullable) NSNumber *floorsDescended;下楼的大概楼层数, 若设备不支持则值为nil @property(readonly, nonatomic, nullable) NSNumber *currentPace NS_AVAILABLE(NA,9_0);对于更新,这将以s / m(每米秒)返回当前速度。 如果满足以下条件,则值为零:1. 资料尚未公布 2. 历史查询 3.平台不支持 @property(readonly, nonatomic, nullable) NSNumber *currentCadence NS_AVAILABLE(NA,9_0);对于更新,这将返回以秒为单位执行行走的节奏。 如果满足以下条件,则值为零:1. 资料尚未公布 2. 历史查询 3.平台不支持 @property(readonly, nonatomic, nullable) NSNumber *averageActivePace NS_AVAILABLE(NA,10_0);对于更新,这将返回自startPedometerUpdatesFromDate:withHandler :,以s / m(每米秒))的平均活动速度。 对于历史查询,这将返回startDate和endDate之间的平均活动速度。 平均主动速度省略了非活动时间,平均步调从用户移动。 如果满足以下条件,则值为零:1. 对于历史信息查询,信息无效。例如用户在开始时间和结束时间内没有移动 2. 平台不支持 CMPedometerEvent @property(readonly, nonatomic) NSDate *date;事件发生的时间 @property(readonly, nonatomic) CMPedometerEventType type;描述行走活动过渡的事件类型 typedef void (^CMPedometerHandler)(CMPedometerData * __nullable pedometerData, NSError * __nullable error) __TVOS_PROHIBITED;当计步器数据可用时要调用的block的类型 typedef void (^CMPedometerEventHandler)(CMPedometerEvent * __nullable pedometerEvent, NSError * __nullable error) NS_AVAILABLE(NA, 10_0) __WATCHOS_AVAILABLE(3_0) __TVOS_PROHIBITED;//当计步器事件可用时将被调用的block的类型。
获取步数和距离的方法
[self.pedometer startPedometerUpdatesFromDate:fromDate withHandler:^(CMPedometerData * _Nullable pedometerData, NSError * _Nullable error) { // 如果没有错误,具体信息从pedometerData参数中获取 }];
不需要使用的时候,调用stopPedometerUpdates
方法停止更新
[self.pedometer stopPedometerUpdates];
如果不需要实时更新数据,可直接调用- (void)queryPedometerDataFromDate:(NSDate *)start
toDate:(NSDate *)end
withHandler:(CMPedometerHandler)handler;查询某个时间段内的数据,不过只能查询七天内的数据。
[self.pedometer queryPedometerDataFromDate:start toDate:end withHandler:^(CMPedometerData * _Nullable pedometerData, NSError * _Nullable error) { // 如果没有错误,具体信息从pedometerData参数中获取 }];
范例:
1.info.plist文件需要设置
Privacy – Motion Usage Description
2.代码
// // ChartUtilityPlugin.m // Hello // // Created by 张艳锋 on 2021/4/30. // Copyright © 2018年 张艳锋. All rights reserved. // #import "ChartUtilityPlugin.h" //需要引用 #import <CoreMotion/CoreMotion.h> @interface ChartUtilityPlugin()
//这个参数不能作为局部变量,负责会报错(报错注意事项里面有展示) @property (nonatomic, strong) CMPedometer *pedometer; @end @implementation ChartUtilityPlugin - (void)showChartView:(CDVInvokedUrlCommand*)command{
[self getHealthData];
} /**************************/ - (void)getHealthData{ _pedometer = [[CMPedometer alloc] init]; NSInteger status = [CMPedometer authorizationStatus]; if (status==CMAuthorizationStatusDenied) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"请打开权限" delegate:self cancelButtonTitle:@"去设置" otherButtonTitles:nil,nil]; [alert show]; }else{ if ([CMPedometer isStepCountingAvailable]) { NSDateFormatter *df = [[NSDateFormatter alloc] init]; [df setDateFormat:@"yyyy-MM-dd"]; NSString *yestr = [df stringFromDate:[NSDate date]]; [df setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; //今天0点时间 NSDate *d = [df dateFromString:[NSMutableString stringWithFormat:@"%@ 00:00:00",yestr]]; NSDate *to = [NSDate dateWithTimeIntervalSinceNow:0]; //查询0点到当前时间的数据 [_pedometer queryPedometerDataFromDate:d toDate:to withHandler:^(CMPedometerData * _Nullable pedometerData, NSError * _Nullable error) { if (error) { NSLog(@"error===%@",error); }else { NSLog(@"步数===%@",pedometerData.numberOfSteps); NSLog(@"距离===%@",pedometerData.distance); } }]; }else{ NSLog(@"不可用==="); } } } @end
注意事项:
_pedometer只能作为全局变量,一定不能作为局部变量,如下
/**************************/
- (void)getHealthData{
CMPedometer *_pedometer = [[CMPedometer alloc] init];
/***若在此处定义,会报错[Generic_deprecated] Error on message reply (Connection invalid)**/
NSInteger status = [CMPedometer authorizationStatus];
if (status==CMAuthorizationStatusDenied) {
// UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@”请打开权限” delegate:self cancelButtonTitle:@”去设置” otherButtonTitles:nil,nil];
// [alert show];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:@”请到设置里面打开隐私下面的运动与健身权限” preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@”去设置” style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
UIApplication *application = [UIApplication sharedApplication];
NSURL *URL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
#if __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_10_0
[application openURL:URL];
#else
[application openURL:URL options:@{UIApplicationOpenURLOptionUniversalLinksOnly:@NO} completionHandler:^(BOOL success) {
NSLog(@”%d”,success);
}];
#endif
}]];
[self presentViewController:alert animated:YES completion:nil];
}else{
if ([CMPedometer isStepCountingAvailable]) {
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setDateFormat:@"yyyy-MM-dd"];
NSString *yestr = [df stringFromDate:[NSDate date]];
[df setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
//今天0点时间
NSDate *d = [df dateFromString:[NSMutableString stringWithFormat:@"%@ 00:00:00",yestr]];
NSDate *to = [NSDate dateWithTimeIntervalSinceNow:0];
//查询0点到当前时间的数据
[_pedometer queryPedometerDataFromDate:d toDate:to withHandler:^(CMPedometerData * _Nullable pedometerData, NSError * _Nullable error) {
if (error) {
NSLog(@"error===%@",error);
}else {
NSLog(@"步数===%@",pedometerData.numberOfSteps);
NSLog(@"距离===%@",pedometerData.distance);
}
//局部变量会引起这部分不运行,也就是直接不进入这个方法里面
}];
}else{
NSLog(@"不可用===");
}
}
}
第二种方法:
1.新建HealthKitManage类 HealthKitManage.h #import <Foundation/Foundation.h> #import <HealthKit/HealthKit.h> #import <UIKit/UIDevice.h> #define HKVersion [[[UIDevice currentDevice] systemVersion] doubleValue] #define CustomHealthErrorDomain @“com.sdqt.healthError” NS_ASSUME_NONNULL_BEGIN @interface HealthKitManage : NSObject @property (nonatomic, strong) HKHealthStore *healthStore; +(id)shareInstance; – (void)authorizeHealthKit:(void(^)(BOOL success, NSError *error))compltion; – (void)getDistance:(void(^)(double value, NSError *error))completion; – (void)getStepCount:(void(^)(double value, NSError *error))completion; @end NS_ASSUME_NONNULL_END HealthKitManage.m #import “HealthKitManage.h” @implementation HealthKitManage +(id)shareInstance { static id manager ; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ manager = [[[self class] alloc] init]; }); return manager; } /* * @brief 检查是否支持获取健康数据 */ – (void)authorizeHealthKit:(void(^)(BOOL success, NSError *error))compltion { if(HKVersion >= 8.0) { if (![HKHealthStore isHealthDataAvailable]) { NSError *error = [NSError errorWithDomain: @”com.raywenderlich.tutorials.healthkit” code: 2 userInfo: [NSDictionary dictionaryWithObject:@”HealthKit is not available in th is Device” forKey:NSLocalizedDescriptionKey]]; if (compltion != nil) { compltion(false, error); } return; } if ([HKHealthStore isHealthDataAvailable]) { if(self.healthStore == nil) self.healthStore = [[HKHealthStore alloc] init]; /* 组装需要读写的数据类型 */ NSSet *writeDataTypes = [self dataTypesToWrite]; NSSet *readDataTypes = [self dataTypesRead]; /* 注册需要读写的数据类型,也可以在“健康”APP中重新修改 */ [self.healthStore requestAuthorizationToShareTypes:writeDataTypes readTypes:readDataTypes completion:^(BOOL success, NSError *error) { if (compltion != nil) { NSLog(@”error->%@”, error.localizedDescription); compltion (success, error); } }]; } } else { NSDictionary *userInfo = [NSDictionary dictionaryWithObject:@”iOS 系统低于8.0″ forKey:NSLocalizedDescriptionKey]; NSError *aError = [NSError errorWithDomain:CustomHealthErrorDomain code:0 userInfo:userInfo]; compltion(0,aError); } } /*! * @brief 写权限 * @return 集合 */ – (NSSet *)dataTypesToWrite { HKQuantityType *heightType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeight]; HKQuantityType *weightType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMass]; HKQuantityType *temperatureType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyTemperature]; HKQuantityType *activeEnergyType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned]; return [NSSet setWithObjects:heightType, temperatureType, weightType,activeEnergyType,nil]; } /*! * @brief 读权限 * @return 集合 */ – (NSSet *)dataTypesRead { HKQuantityType *heightType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeight]; HKQuantityType *weightType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMass]; HKQuantityType *temperatureType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyTemperature]; HKCharacteristicType *birthdayType = [HKObjectType characteristicTypeForIdentifier:HKCharacteristicTypeIdentifierDateOfBirth]; HKCharacteristicType *sexType = [HKObjectType characteristicTypeForIdentifier:HKCharacteristicTypeIdentifierBiologicalSex]; HKQuantityType *stepCountType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]; HKQuantityType *distance = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceWalkingRunning]; HKQuantityType *activeEnergyType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned]; return [NSSet setWithObjects:heightType, temperatureType,birthdayType,sexType,weightType,stepCountType, distance, activeEnergyType,nil]; } //获取步数 – (void)getStepCount:(void(^)(double value, NSError *error))completion { HKQuantityType *stepType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]; NSSortDescriptor *timeSortDescriptor = [[NSSortDescriptor alloc] initWithKey:HKSampleSortIdentifierEndDate ascending:NO]; // Since we are interested in retrieving the user’s latest sample, we sort the samples in descending order, and set the limit to 1. We are not filtering the data, and so the predicate is set to nil. HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:stepType predicate:[HealthKitManage predicateForSamplesToday] limit:HKObjectQueryNoLimit sortDescriptors:@[timeSortDescriptor] resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) { if(error) { completion(0,error); } else { NSInteger totleSteps = 0; for(HKQuantitySample *quantitySample in results) { HKQuantity *quantity = quantitySample.quantity; HKUnit *heightUnit = [HKUnit countUnit]; double usersHeight = [quantity doubleValueForUnit:heightUnit]; totleSteps += usersHeight; } NSLog(@”当天行走步数 = %ld”,(long)totleSteps); completion(totleSteps,error); } }]; [self.healthStore executeQuery:query]; } //获取公里数 – (void)getDistance:(void(^)(double value, NSError *error))completion { HKQuantityType *distanceType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceWalkingRunning]; NSSortDescriptor *timeSortDescriptor = [[NSSortDescriptor alloc] initWithKey:HKSampleSortIdentifierEndDate ascending:NO]; HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType:distanceType predicate:[HealthKitManage predicateForSamplesToday] limit:HKObjectQueryNoLimit sortDescriptors:@[timeSortDescriptor] resultsHandler:^(HKSampleQuery * _Nonnull query, NSArray<__kindof HKSample *> * _Nullable results, NSError * _Nullable error) { if(error) { completion(0,error); } else { double totleSteps = 0; for(HKQuantitySample *quantitySample in results) { HKQuantity *quantity = quantitySample.quantity; HKUnit *distanceUnit = [HKUnit meterUnitWithMetricPrefix:HKMetricPrefixKilo]; double usersHeight = [quantity doubleValueForUnit:distanceUnit]; totleSteps += usersHeight; } NSLog(@”当天行走距离 = %.2f”,totleSteps); completion(totleSteps,error); } }]; [self.healthStore executeQuery:query]; } /*! * @brief 当天时间段 * * @return 时间段 */ + (NSPredicate *)predicateForSamplesToday { NSCalendar *calendar = [NSCalendar currentCalendar]; NSDate *now = [NSDate date]; NSDateComponents *components = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:now]; [components setHour:0]; [components setMinute:0]; [components setSecond: 0]; NSDate *startDate = [calendar dateFromComponents:components]; NSDate *endDate = [calendar dateByAddingUnit:NSCalendarUnitDay value:1 toDate:startDate options:0]; NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:endDate options:HKQueryOptionNone]; return predicate; } @end 2.函数类调用 – (void)onClickBtn1 { HealthKitManage *manage = [HealthKitManage shareInstance]; [manage authorizeHealthKit:^(BOOL success, NSError *error) { if (success) { NSLog(@”success”); [manage getStepCount:^(double value, NSError *error) { NSLog(@”1count–>%.0f”, value); NSLog(@”1error–>%@”, error.localizedDescription); dispatch_async(dispatch_get_main_queue(), ^{ self->stepLabel.text = [NSString stringWithFormat:@”步数:%.0f步”, value]; }); }]; } else { NSLog(@”fail”); } }]; } – (void)onClickBtn2 { HealthKitManage *manage = [HealthKitManage shareInstance]; [manage authorizeHealthKit:^(BOOL success, NSError *error) { if (success) { NSLog(@”success”); [manage getDistance:^(double value, NSError *error) { NSLog(@”2count–>%.2f”, value); NSLog(@”2error–>%@”, error.localizedDescription); dispatch_async(dispatch_get_main_queue(), ^{ self->distanceLabel.text = [NSString stringWithFormat:@”公里数:%.2f公里”, value]; }); }]; } else { NSLog(@”fail”); } }]; } 3.如果用这个类,需要在info.plist文件声明2个权限 Privacy – Health Share Usage Description Privacy – Health Update Usage Description 4.打开工程,选择target,在Capabilities里面把healthkit打开
优先选用第一种方法。