-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathHealthKitManager.swift
225 lines (204 loc) · 9.61 KB
/
HealthKitManager.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
//
// HealthKitManager.swift
// Cardiologic
//
// Created by Ben Zimring on 7/25/18.
// do whatever you'd like with this file.
//
import Foundation
import HealthKit
/* useful for requesting HealthKit authorization, among other things */
extension HKObjectType {
static let heartRateType = HKQuantityType.quantityType(forIdentifier: .heartRate)!
static let variabilityType = HKQuantityType.quantityType(forIdentifier: .heartRateVariabilitySDNN)!
static let restingHeartRateType = HKQuantityType.quantityType(forIdentifier: .restingHeartRate)!
static let stepsType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
static let heightType = HKQuantityType.quantityType(forIdentifier: .height)!
static let weightType = HKQuantityType.quantityType(forIdentifier: .bodyMass)!
static let genderType = HKObjectType.characteristicType(forIdentifier: .biologicalSex)!
static let dateOfBirthType = HKObjectType.characteristicType(forIdentifier: .dateOfBirth)!
static let workoutType = HKObjectType.workoutType()
}
/* useful for converting from HealthKit's weird output formats */
extension HKUnit {
static let heartRateUnit = HKUnit(from: "count/min")
static let variabilityUnit = HKUnit.secondUnit(with: .milli)
}
class HealthKitManager {
fileprivate let health = HKHealthStore()
/**
Fetches ALL saved heart rate readings bound by the input date range.
- Parameter from: start of date range
- Parameter to: end of date range
- Parameter handler: closure to handle the list of samples returned
*/
public func heartRate(from: Date, to: Date, handler: @escaping ([HKQuantitySample]) -> ()) {
let predicate = HKQuery.predicateForSamples(withStart: from, end: to, options: [.strictStartDate, .strictEndDate])
let heartRateQuery = HKSampleQuery(sampleType: .heartRateType, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { (query, results, error) in
guard let results = results as? [HKQuantitySample] else { NSLog("HealthKitManager: heartRate: nil HR samples.. auth'd?"); return }
handler(results)
}
health.execute(heartRateQuery)
}
/**
Fetches daily resting HR info from some date to another.
- Parameter from: Starting date from which data will be retrieved
- Parameter to: Ending date from which data will be retrieved
- Parameter handler: your closure to handle the list of HKQuantitySamples
*/
public func restingHeartRate(from: Date, to: Date, handler: @escaping ([HKQuantitySample]) -> ()) {
// HealthKit query
let predicate = HKQuery.predicateForSamples(withStart: from, end: to, options: [.strictStartDate, .strictEndDate])
let query = HKSampleQuery(sampleType: .restingHeartRateType, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { (query, samples, error) in
guard let samples = samples as? [HKQuantitySample] else {
NSLog("HealthKitManager: restingHeartRate: nil RHR samples.. auth'd?")
return
}
// found sample(s)
handler(samples)
}
health.execute(query)
}
/**
Fetches daily steps info.
- Parameter handler: closure handling the HKStatisticsCollection object
returned by the query. Use:
1. results.enumerateStatistics(from: Date, to: Date) {
2. statistics, stop in
.. ...
}
which loops through each day.
https://developer.apple.com/documentation/healthkit/hkstatisticscollection/1615783-enumeratestatistics
*/
public func dailySteps(handler: @escaping (HKStatisticsCollection) -> ()) {
let calendar = NSCalendar.current
var interval = DateComponents()
interval.day = 1
var anchorComponents = calendar.dateComponents([.day, .month, .year], from: Date())
anchorComponents.hour = 0
let anchorDate = calendar.date(from: anchorComponents)
// Define 1-day intervals starting from 0:00
let stepsQuery = HKStatisticsCollectionQuery(quantityType: .stepsType, quantitySamplePredicate: nil, options: .cumulativeSum, anchorDate: anchorDate!, intervalComponents: interval)
// Set the results handler
stepsQuery.initialResultsHandler = { query, results, error in
if let results = results {
handler(results)
} else {
print(error!.localizedDescription)
}
}
health.execute(stepsQuery)
}
/**
Fetches heart rate variability measurements for each day of the given range.
- note: there may be multiple readings for each day.. Apple's Health app averages these!
- Parameter from: start of date range
- Parameter to: end of date range
- Parameter handler: closure to handle the list of samples returned
*/
public func variability(from: Date, to: Date, handler: @escaping ([HKQuantitySample]) -> ()) {
// HealthKit query
let predicate = HKQuery.predicateForSamples(withStart: from, end: to, options: [.strictStartDate, .strictEndDate])
let query = HKSampleQuery(sampleType: .variabilityType, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { (query, samples, error) in
guard let samples = samples as? [HKQuantitySample] else {
NSLog("HealthKitManager: variability: nil variability samples.. auth'd?")
return
}
// found sample(s)
handler(samples)
}
health.execute(query)
}
/**
Fetches workouts recorded in the given date range.
- Parameter from: start of date range
- Parameter to: end of date range
- Parameter handler: closure to handle the list of samples returned
*/
public func workouts(from: Date, to: Date, handler: @escaping ([HKWorkout]) -> ()) {
// HealthKit query
let predicate = HKQuery.predicateForSamples(withStart: from, end: to, options: [.strictStartDate, .strictEndDate])
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)
let query = HKSampleQuery(sampleType: .workoutType(), predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: [sortDescriptor]) { (query, samples, error) in
guard let samples = samples as? [HKWorkout] else { NSLog("HealthKitManager: workouts: nil workout samples.. auth'd?"); return }
handler(samples)
}
health.execute(query)
}
/**
Requests HealthKit authorization for the given types, executes closure upon success
- Parameter readingTypes: types to request HealthKit for read access (optional)
- Parameter writingTypes: types to request HealthKit for write access (optional)
- Parameter completion: codeblock to execute upon authorization completion
- note: this does not mean the user actually granted you privileges!
*/
public func requestAuthorization(readingTypes: Set<HKObjectType>?, writingTypes: Set<HKSampleType>?, completion: @escaping () -> ()) {
health.requestAuthorization(toShare: writingTypes, read: readingTypes) { (success, error) in
if let error = error {
fatalError("** HealthKitManager: authorization failure. \(error.localizedDescription) **")
}
completion()
}
}
/**
Fetches user's Gender, if available
- Returns: HKBiologicalSex enum
*/
public func userGender() -> HKBiologicalSex {
do {
let sex = try health.biologicalSex()
return sex.biologicalSex
} catch {
return .notSet
}
}
/**
Fetches user's latest height, if available
- Parameter handler: closure to handle the user's height (in inches). If not set, value is -1.
*/
public func userHeight(handler: @escaping (Int) -> ()) {
let query = HKSampleQuery(sampleType: .heightType, predicate: nil, limit: -1, sortDescriptors: nil) { (query, results, error) in
if let result = results?.last as? HKQuantitySample {
handler(Int(result.quantity.doubleValue(for: HKUnit(from: .inch))))
} else if results?.count == 0 {
handler(-1)
} else {
print("HealthKitManager: userHeight failed to fetch samples... auth'd?")
handler(-1)
}
}
health.execute(query)
}
/**
Fetches user's latest weight, if available
- Parameter handler: closure to handle the user's weight (in lbs). If not set, value is -1.
*/
public func userWeight(handler: @escaping (Int) -> ()) {
let query = HKSampleQuery(sampleType: .weightType, predicate: nil, limit: -1, sortDescriptors: nil) {
(query, results, error) in
if let result = results?.last as? HKQuantitySample {
handler(Int(result.quantity.doubleValue(for: HKUnit(from: .pound))))
} else if results?.count == 0 {
handler(-1)
} else {
print("HealthKitManager: userWeight failed to fetch samples... auth'd?")
handler(-1)
}
}
health.execute(query)
}
/**
Fetches user's birthday.
- Returns: DateComponents if set, or nil if not set
*/
public func userBirthday() -> DateComponents? {
do {
return try health.dateOfBirthComponents()
} catch {
return nil
}
}
public func isHealthDataAvailable() -> Bool {
return HKHealthStore.isHealthDataAvailable()
}
}