最近开始跑步了,天天看到「健身纪录」的圆圈,挺有感触的。html
天天的「圈」里能看到当天的活动量、锻炼时长、站立时间。ios
打开详情后,还能看到跑步步数、跑步距离、以及从第三方同步过来的数据等。git
今天咱们的目标是拿到第一个量化数据:「健身记录」的圆圈数据,即 iPhone 提供的 HealthKit data 数据导出,放入第三方数据库中,以供后续统计和分析。github
简单看看个人「健康」APP 的数据: 数据库
有了健康 app,你能够将各类健康和健身信息都保存在一个地方,让它们尽在你的掌控。保存哪些信息,以及哪些 app 能够经过健康 app 访问你的数据都由你决定。当你使用密码、触控 ID 或面容 ID 锁定手机时,健康 app 中全部的健康和运动数据都将被加密,只有医疗急救卡中的信息除外。你能够经过 iCloud,让健康数据自动在你的各类设备上保持更新,包括传输和存储过程都会被加密保护。此外,访问 HealthKit 的 app 都必须备有隐私政策,所以在受权这些 app 访问你的健康和健身数据以前,请务必仔细查看这些政策。swift
咱们再来看看平时都有哪些第三方 app 受权访问咱们的健康数据:api
数据来源有了,接下来就是考虑如何将健康数据导出。经过对 HealthKit 的了解,咱们须要本身动手开发一个 iOS app,获取健康数据,并上传到咱们的服务器上,达到数据导出的目标。数组
这张「圈」图是我 11 月 9 日的数据,今天的目标就是经过 HealthKit 获取这个圈的数据,主要包括:bash
- 活动数据:1048 大卡
- 锻炼时间:92 分钟
- 站立时间:11 小时
在建立 iOS 应用 id 时,须要拥有「HealthKit」能力:服务器
在 Info.plist
配置中增长如下两项内容:
参考 HealthKit 开发文档,要获取「圈」运动统计数据,须要利用 Activity summary query
即,HKActivitySummaryQuery
查询。
结果字段类型为:HKActivitySummary
。
HKActivitySummaryQuery
A query for read activity summary objects from the HealthKit store.
参考:developer.apple.com/documentati…
HKActivitySummary
An object that contains the move, exercise, and stand data for a given day.
了解了 HKActivitySummaryQuery
和 HKActivitySummary
,咱们就能够进入开发了。
这里我主要借助「MBHealthTracker」,MBHealthTracker 封装好了和 HealthKit 交互的受权,获取数据等,只要直接调用便可。
MBHealthTracker Github: github.com/matybrennan…
当我发现 MBHealthTracker 没有对应的功能获取 HKActivitySummary
功能,因此须要咱们在 MBHealthTracker 基础上增长杜对应的获取方法。
先在 Presentation
下增长 ActivitySummary
模型,主要是建立数组。
import Foundation
import HealthKit
public struct ActivitySummary {
public let items: [HKActivitySummary]
}
复制代码
接着在 Business Logic
逻辑层,建立协议和实现方法:
// ActivitySummaryServiceProtocol.swift
import Foundation
import HealthKit
public protocol ActivitySummaryServiceProtocol {
// 根据起止时间获取 ActivitySummary
func getActivitySummary(startDate: Date, endDate: Date, completionHandler: @escaping (MBAsyncCallResult<ActivitySummary>) -> Void) throws
}
// ActivitySummaryService.swift
import Foundation
import HealthKit
class ActivitySummaryService {
public init() { }
}
extension ActivitySummaryService: ActivitySummaryServiceProtocol {
func getActivitySummary(startDate: Date, endDate: Date, completionHandler: @escaping (MBAsyncCallResult<ActivitySummary>) -> Void) throws {
try isDataStoreAvailable()
// Create the date components for the predicate
guard let calendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian) else {
fatalError("*** This should never fail. ***")
}
let units: NSCalendar.Unit = [.day, .month, .year, .era]
var startDateComponents = calendar.components(units, from: startDate)
startDateComponents.calendar = calendar as Calendar
var endDateComponents = calendar.components(units, from: endDate)
endDateComponents.calendar = calendar as Calendar
// Create the predicate for the query
let summariesWithinRange = HKQuery.predicate(forActivitySummariesBetweenStart: startDateComponents, end: endDateComponents)
// Build the query
let query = HKActivitySummaryQuery(predicate: summariesWithinRange) { (query, summaries, error) -> Void in
self.configure(query: query, summaries: summaries, error: error, completionHandler: completionHandler)
}
healthStore.execute(query)
}
}
private extension ActivitySummaryService {
func configure(query: HKActivitySummaryQuery, summaries: [HKActivitySummary]?, error: Error?, completionHandler: @escaping (MBAsyncCallResult<ActivitySummary>) -> Void) {
guard error == nil else {
completionHandler(.failed(error!))
return
}
let activitySummary = ActivitySummary(items: summaries!)
completionHandler(.success(activitySummary))
}
}
复制代码
参考官网的 demo 和 MBHealthTracker 的方法,依葫芦画瓢写好实现类,代码简单,就不详细说明。
剩下的就是在 MBHealthTrackerProtocol
和 MBHealthTracker
载入方法便可。
// MBHealthTrackerProtocol.swift
var activitySummary: ActivitySummaryServiceProtocol { get }
// MBHealthTracker.swift
private lazy var privateActivitySummaryService: ActivitySummaryServiceProtocol = {
return ActivitySummaryService()
}()
...
public var activitySummary: ActivitySummaryServiceProtocol {
return privateActivitySummaryService
}
复制代码
获取数据的部分暂时告一段落,下一步就是要经过配置,拿到受权能够读取 ActivitySummary 数据。
/// Just has read capabilities
public enum MBReadType: ReadableType {
// Characteristics
case dob
case gender
case activitySummary
public var readable: HKObjectType {
switch self {
case .dob:
return HKCharacteristicType.characteristicType(forIdentifier: .dateOfBirth)!
case .gender:
return HKCharacteristicType.characteristicType(forIdentifier: .biologicalSex)!
case .activitySummary: return HKActivitySummaryType.activitySummaryType()
}
}
}
复制代码
万事俱备,咱们测试下看看运行结果。
import Foundation
import HealthKit
protocol ViewInteractorProtocol {
func configurePermissions()
func runTest()
}
class ViewInteractor {
private let healthTracker: MBHealthTrackerProtocol
init(healthTracker: MBHealthTrackerProtocol) {
self.healthTracker = healthTracker
}
}
// MARK: - ViewInteractorProtocol
extension ViewInteractor: ViewInteractorProtocol {
// 配置写入和读取受权的类目
func configurePermissions() {
healthTracker.configuration.requestAuthorization(toShare: []
,toRead: [MBReadType.activitySummary]) { _ in }
}
// 测试,看看11-9号到11-3号得数据
func runTest() {
do {
print("-----------------get summary begin----------------")
let date = Date()
let start = date.parse("2019-11-09")
let end = date.parse("2019-11-13")
try healthTracker.activitySummary.getActivitySummary(startDate: start, endDate: end, completionHandler: { (result) in
print(result)
})
print("-----------------get summary end----------------")
} catch {
print("Unable to get: \(error.localizedDescription)")
}
}
}
复制代码
运行打印出来的结果:
success(Lianghua.ActivitySummary(items: [<<HKActivitySummary: 0x2832480c0>: Date=(Year: 2019, Month: 11, Day: 9) Active Energy Burned=(1048.476259360438/310) Apple Exercise Minutes=(92/30) Apple Stand Hours=(11/12)>, <<HKActivitySummary: 0x283248180>: Date=(Year: 2019, Month: 11, Day: 10) Active Energy Burned=(474.8101270220084/310) Apple Exercise Minutes=(33/30) Apple Stand Hours=(6/12)>, <<HKActivitySummary: 0x283240f00>: Date=(Year: 2019, Month: 11, Day: 11) Active Energy Burned=(357.55/310) Apple Exercise Minutes=(22/30) Apple Stand Hours=(16/12)>, <<HKActivitySummary: 0x283241200>: Date=(Year: 2019, Month: 11, Day: 12) Active Energy Burned=(344.8089999999997/310) Apple Exercise Minutes=(17/30) Apple Stand Hours=(16/12)>, <<HKActivitySummary: 0x2832412c0>: Date=(Year: 2019, Month: 11, Day: 13) Active Energy Burned=(181.595/310) Apple Exercise Minutes=(21/30) Apple Stand Hours=(4/12)>]))
复制代码
这里咱们对照开篇的「圈」图,和这里的数据彻底一致。
咱们导出健康数据后,就要考虑统一存放到云平台或者第三方存储平台上,以供后续统计分析。具体选择什么平台来存储数据呢,咱们下期再聊!
未完待续
参考: