Extensions for Apple HealthKit to make the API easier to use. This includes:
- Dedicated types for all sample types
- Automatic unit conversions and metadata handling
- A wrapper around
HKHealthStore
for easier querying and saving
Normally, one would create HKSample
samples manually:
let metadata: [String: Any] = [HKMetadataKeySexualActivityProtectionUsed : true]
let sample = HKCategorySample(
type: .init(.sexualActivity),
value: HKCategoryValue.notApplicable.rawValue,
start: .now,
end: .now,
metadata: metadata)
Using the extensions, this becomes much easier:
let activity = SexualActivity(protectionUsed: true, start: .now, end: .now)
The types automatically use the correct sample identifier, and set the correct value and metadata fields for the specific type. The created HealthKit sample can be accessed as a property:
let object: HKObject = activity.object
let sample: HKSample = activity.sample
let category: HKCategorySample = activity.categorySample
Category samples either have a value (usually an enum) or don't allow a value. Depending on the type, a value can be provided in the constructor:
let result = PregnancyTestResult(value: .positive, start: .now, end: .now)
let pregnancy = Pregnancy(start: now, end: now)
Whenever a category sample requires or allows additional metadata fields, it can be set using the constructor. The types also expose these fields as properties.
let sample = SexualActivity(protectionUsed: true, start: .now, end: .now)
let wasSafe = sample.protectionUsed // Bool?
HKQuantitySample
types only include a quantity, which can be given to the constructor as a value or a quantity:
let value = BodyFatPercentage(value: 10.3, start: .now, end: .now)
let quantity = HKQuantity(unit: .precent(), doubleValue: 10.3)
let value = BodyFatPercentage(quantity: quantity, start: .now, end: .now)
When only the value is provided, then it is interpreted in the defaultUnit
for the type.
Depending on the quantity, there are different properties exposed for samples that are either cumulative
or discrete
.
Cumulative samples have an aggregationStyle == .cumulative
and provide the sumQuantity
property.
let sample = ActiveEnergyBurned(...)
let sum = sample.sumQuantity
Discrete samples have aggregationStyle
values of .discreteArithmetic
, .discreteTemporallyWeighted
, or .discreteEquivalentContinuousLevel
and expose the properties averageQuantity
, maximumQuantity
, minimumQuantity
, mostRecentQuantity
, and mostRecentQuantityDateInterval
.
let sample = CyclingPower(...)
let max = sample.maximumQuantity
It's possible to assign custom UUIDs directly in the constructor:
let uuid = UUID()
let sample = SexualActivity(protectionUsed: true, start: .now, end: .now, uuid: uuid)
print(sample.preferredUUID == uuid) // true
When manipulating and accessing HKSample
and HKObject
types, there are many metadata keys required to get certain properties.
Most of the time, it's unnecessary to access metadata directly, since the types provide the expected fields as properties or constructor arguments.
The library still provides a more convenient way to interact with metadata:
var metadata = Metadata() // typealias for [String : Any]
metadata.menstrualCycleStart = true // Sets HKMetadataKeyMenstrualCycleStart
let cycleStart: Bool = metadata.menstrualCycleStart
This shorthand format works for all known keys defined in the HKMetadataKey
enum.
Interacting with the HKHealthStore
can be simplified by wrapping it in a HealthStore
:
let store = HealthStore(wrapping: HKHealthStore())
It's then possible to use all the convenience functions on HealthStore
, or (for complex queries) just use the store: HKHealthStore
property as oine normally would.
The HealthStore
functions make a lot of use of async
/await
instead of completion handlers, and use the convenience sample types described above.
Before saving or reading samples from HealthKit, the permissions must be obtained:
try await store.requestAuthorization(
toShare: Vomiting.self, SoreThroat.self,
read: SkippedHeartbeat.self, SleepChanges.self)
It's also possible to pass an array of types instead of variadic arguments. To check the authorization status, there are also overloads:
let status = store.authorizationStatus(for: SexualActivity.self)
All user characteristic functions of HKHealthStore
have been mapped to HealthStore
, but directly returning the value instead of the object.
let sex: HKBiologicalSex = try store.biologicalSex()
There are function overloads to directly pass objects to the health store:
let sample = SexualActivity(...)
try await store.save(sample)
There are functions to directly retrieve typed objects for quantities, category samples and correlations:
let samples: [Vo2Max] = try await store.samples(
from: Date.distantPast,
to: Date.now,
sortedBy: .ascendingStartDate,
limitedTo: 100)
let runs = try await store.workouts(
activityType: .running,
from: Date.distantPast,
to: Date.now,
sortedBy: .ascendingStartDate,
limitedTo: 100)
let workout: HKWorkout = runs.first!
let heartRates: [HeartRate] = try await store
.samples(associatedWith: workout)
The function automatically queries for condensed samples, and returns all samples at once.
let route = try store.route(associatedWith: workout)
let locations = try store.locations(associatedWith: route!)