概述
这是一篇介绍两种可以获取计步数据的方法的文章,一种是采用CMPedometer获取手机计步器数据,另一种是采用 HealthKit 框架从手机健康 App 中获取计步数据。另外也可以写入数据到健康 App。
花絮
用 HealthKit 框架构建 App,写入数据到苹果健康 App中,QQ 和 Keep 等第三方 App 的运动数据都会随之改变,猜测它们的运动数据是直接从苹果健康 App 中获取,而且没有过滤掉其它数据来源。而微信运动的数据不会变,猜测其来源可能是使用 CMPedometer 类获取的,因为测试发现把微信运动的数据来源(苹果健康)关闭后,依然会有运动数据,而且该运动数据和 CMPedometer 类获取的一致。
使用 CMPedometer 类来获取步数和距离
使用时需要导入,此类在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);
// 在给定时间范围内查询用户的行走活动,数据最多可以使用7天内有效,返回的数据是从系统范围的历史记录中计算出来的,该历史记录是在后台连续收集的。结果返回在串行队列中。
- (void)queryPedometerDataFromDate:(NSDate *)start toDate:(NSDate *)end withHandler:(CMPedometerHandler)handler;
// 在串行队列上启动一系列连续计步器更新到处理程序。 对于每次更新,应用程序将从指定的开始日期和与最新确定相关联的时间戳开始收到累积的行人活动。 如果应用程序在后台进行背景调整,则应用程序将在下次更新中收到在后台期间累积的所有行人活动。
- (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;
// 用户行走和跑步时估计的一米为单位的距离。若设备不支持则值为nil
@property(readonly, nonatomic, nullable) NSNumber *distance;
// 上楼的大概楼层数,若设备不支持则值为nil
@property(readonly, nonatomic, nullable) NSNumber *floorsAscended;
// 下楼的大概楼层数, 若设备不支持则值为nil
@property(readonly, nonatomic, nullable) NSNumber *floorsDescended;
// 对于更新,这将以s / m(每米秒)返回当前速度。 如果满足以下条件,则值为零:1. 资料尚未公布 2. 历史查询 3.平台不支持
@property(readonly, nonatomic, nullable) NSNumber *currentPace NS_AVAILABLE(NA,9_0);
// 对于更新,这将返回以秒为单位执行行走的节奏。 如果满足以下条件,则值为零:1. 资料尚未公布 2. 历史查询 3.平台不支持
@property(readonly, nonatomic, nullable) NSNumber *currentCadence NS_AVAILABLE(NA,9_0);
// 对于更新,这将返回自startPedometerUpdatesFromDate:withHandler :,以s / m(每米秒))的平均活动速度。 对于历史查询,这将返回startDate和endDate之间的平均活动速度。 平均主动速度省略了非活动时间,平均步调从用户移动。 如果满足以下条件,则值为零:1. 对于历史信息查询,信息无效。例如用户在开始时间和结束时间内没有移动 2. 平台不支持
@property(readonly, nonatomic, nullable) NSNumber *averageActivePace NS_AVAILABLE(NA,10_0);
CMPedometerEvent
// 事件发生的时间
@property(readonly, nonatomic) NSDate *date;
// 描述行走活动过渡的事件类型
@property(readonly, nonatomic) CMPedometerEventType type;
// 当计步器数据可用时要调用的block的类型
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;
获取步数和距离的方法
使用库需要在 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 框架获取苹果健康数据
在 HealthKit 中,使用 HKHealthStore 类来访问健康数据,健康数据的类型有很多类,苹果健康app中的健身记录、营养摄入、睡眠状况等等都可以进行数据读取和共享(即第三方app写入数据到苹果健康app)。
大概步骤:
- 在 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 时,上架需要注意点:
Your app may not use information gained through the use of the HealthKit framework for advertising or similar services. Note that you may still serve advertising in an app that uses the HealthKit framework, but you cannot use data from the HealthKit store to serve ads.
// 你的应用不应该将HealthKit收集的数据用于广告或类似的服务。注意,可能在使用HealthKit框架应用中还是要服务广告,但是你不能使用HealthKit中的数据来服务广告。
You must not disclose any information gained through HealthKit to a third party without express permission from the user. Even with permission, you can only share information to a third party if they are also providing a health or fitness service to the user.
// 在没有用户的明确允许下,你不能向第三方展示任何 HealthKit 收集的数据。即使用户允许,你也只能向提供健康或健身服务的第三方展示这些数据
You cannot sell information gained through HealthKit to advertising platforms, data brokers, or information resellers.
// 你不能将 HealthKit 收集的数据出售给广告平台、数据代理人或者信息经销商
If the user consents, you may share his or her HealthKit data with a third party for medical research.
// 如果用户允许,你可以将HealthKit数据共享给第三方用于医学研究。
You must clearly disclose to the user how you and your app will use their HealthKit data.
// 你必须明确说明,你和你的应用会怎样使用用户的 HealthKit 数据。
You must also provide a privacy policy for any app that uses the HealthKit framework. You can find guidance on creating a privacy policy at the following sites:
// 你必须为每个使用 HealthKit 框架的应用提供一份隐私策略。你可以在以下网站找到创建隐私策略的指导:
- Personal Health Record model (for non-HIPAA apps): http://www.healthit.gov/policy-researchers-implementers/personal-health-record-phr-model-privacy-notice
- HIPAA model (for HIPAA covered apps): http://www.hhs.gov/ocr/privacy/hipaa/modelnotices.html