花絮(用HealthKit框架构建app,写入数据到苹果健康app中,QQ和Keep等第三方app的运动数据都会随之改变,猜测它们的运动数据是直接从苹果健康app中获取,而且没有过滤掉其它数据来源。而微信运动的数据不会变,猜测其来源可能是使用CMPedometer类获取的,因为测试发现把微信运动的数据来源(苹果健康)关闭后,依然会有运动数据,而且该运动数据和CMPedometer类获取的一致。)
使用CMPedometer类来获取步数和距离
使用时需要导入<CoreMotion/CoreMotion.h>,此类在iOS8之后才可用,在iOS8之前,使用CMStepCounter
类(在iOS8之后被CMPedometer
替代)来获取步数,使用方法如CMPedometer
类相似。
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的类型。
获取步数和距离的方法
- 使用<CoreMotion/CoreMotion.h>库需要在info.plist文件中增加
NSMotionUsageDescription
键。 - 可以使用
isStepCountingAvailable
或者isDistanceAvailable
来检查设备是否支持计步功能或距离功能。 - 创建
CMPedometer
实例对象
/// 创建计步器对象
if ([CMPedometer isStepCountingAvailable]) { // 8.0 之后可使用
self.pedometer = [[CMPedometer alloc] init];
}
- 调用
- (void)startPedometerUpdatesFromDate:(NSDate *)start withHandler:(CMPedometerHandler)handler
方法获取从某个时间点到现在的步数,距离,楼层等信息。此方法会实时更新数据。
[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参数中获取
}];
使用HealthKit框架获取苹果健康数据
大概步骤:
- 在
Xcode
中, 打开HealthKit
功能
- 调用
isHealthDataAvailable
方法检查设备HealthKit是否可用。
if ([HKHealthStore isHealthDataAvailable]) {
// add code to use HealthKit here...
}
- 如果可用,创建
HKHealthStore
对象
self.healthStore = [[HKHealthStore alloc] init];
- 向用户请求授权共享或读取健康数据, 调用
- (void)requestAuthorizationToShareTypes:(nullable NSSet<HKSampleType *> *)typesToShare readTypes:(nullable NSSet<HKObjectType *> *)typesToRead completion:(void (^)(BOOL success, NSError * _Nullable error))completion;
方法,例如下面请求读取步数和距离数据
NSSet<HKSampleType *> *shareTypes = nil;
HKQuantityType *stepType = [HKQuantityType quantityTypeForIdentifier:(HKQuantityTypeIdentifierStepCount)];
HKQuantityType *distanceType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceWalkingRunning];
NSSet<HKObjectType *> *readTypes = [NSSet setWithObjects:stepType, distanceType, nil];
[self.healthStore requestAuthorizationToShareTypes:shareTypes readTypes:readTypes completion:^(BOOL success, NSError * _Nullable error) {
}];
在info.plist文件中,增加
NSHealthShareUsageDescription
用于读取数据的描述和NSHealthUpdateUsageDescription
用于写入数据的描述用户授权之后,就可以对健康数据中授权的项目进行读取或写入操作。下面的代码是查询一段历史的计步记录的示例,如
CMPedemoter
不同的是查询到的数据不是实时更新的。
// 查询数据的类型,比如计步,行走+跑步距离等等
HKQuantityType *quantityType = [HKQuantityType quantityTypeForIdentifier:(HKQuantityTypeIdentifierStepCount)];
// 谓词,用于限制查询返回结果
NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:start endDate:end options:(HKQueryOptionNone)];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *anchorComponents = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:[NSDate date]];
// 用于锚集合的时间间隔
NSDate *anchorDate = [calendar dateFromComponents:anchorComponents];
// 采样时间间隔
NSDateComponents *intervalComponents = [[NSDateComponents alloc] init];
intervalComponents.day = 1;
// 创建统计查询对象
HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:quantityType quantitySamplePredicate:predicate options:(HKStatisticsOptionCumulativeSum|HKStatisticsOptionSeparateBySource) anchorDate:anchorDate intervalComponents:intervalComponents];
query.initialResultsHandler = ^(HKStatisticsCollectionQuery * _Nonnull query, HKStatisticsCollection * _Nullable result, NSError * _Nullable error) {
NSMutableArray *resultArr = [NSMutableArray array];
if (error) {
NSLog(@"error: %@", error);
} else {
for (HKStatistics *statistics in [result statistics]) {
NSLog(@"statics: %@,\n sources: %@", statistics, statistics.sources);
for (HKSource *source in statistics.sources) {
// 过滤掉其它应用写入的健康数据
if ([source.name isEqualToString:[UIDevice currentDevice].name]) {
// 获取到步数
double step = round([[statistics sumQuantityForSource:source] doubleValueForUnit:[HKUnit countUnit]]);
}
}
}
}
// 执行查询请求
[self.healthStore executeQuery:query];
- 如果要写入数据到苹果HealtkKit中,过程类似,下面的示例是写入步数到健康数据。(QQ中运动的步数和Keep中的步数都是从健康数据中获取的步数,而且没有过滤其它应用写入的数据,所以想要修改QQ或Keep中的步数,可以用自己的app写入步数数据,亲测有效)
- 请求用户授权
HKQuantityType *stepType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
NSSet *shareTypes = [NSSet setWithObjects:stepType, nil];
[self.healthStore requestAuthorizationToShareTypes:shareTypes readTypes:nil completion:^(BOOL success, NSError * _Nullable error) {
}];
- 写入数据
double step = [self.textField.text doubleValue];
HKQuantityType *stepType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
HKQuantity *stepQuantity = [HKQuantity quantityWithUnit:[HKUnit countUnit] doubleValue:step];
HKQuantitySample *stepSample = [HKQuantitySample quantitySampleWithType:stepType quantity:stepQuantity startDate:[self getTodayStartDate] endDate:[NSDate date]];
[self.healthStore saveObject:stepSample withCompletion:^(BOOL success, NSError * _Nullable error) {
if (error) {
NSLog(@"error: %@", error.localizedDescription);
}
dispatch_async(dispatch_get_main_queue(), ^{
self.stateLabel.text = success ? @"成功" : @"失败";
});
}];
项目中使用了HealthKit时,上架需要注意点:
项目文件截图:
参考文章
https://developer.apple.com/documentation/healthkit#classes
iOS 计步器的几种实现方式