Combine-based wrapper to perform HealthKit related queries.
My app, Singlet, makes full use of this Swift Package.
Add the repository link as a dependency on Xcode from File, Swift Packages & Add Package Dependency...
This package makes extensive use of the Combine framework.
HKHealthStore()
.needsAuthorization(for: TYPES_TO_QUERY, toShare: false, toRead: true)
.replaceError(with: false)
.sink(receiveValue: { needsAuthorization in
/// Perform an action based on the result
requestPermissionButtonEnabled = !needsAuthorization
}).store(in: &cancellableBag)
HKHealthStore()
.requestAuthorization(for: TYPES_TO_QUERY, toShare: false, toRead: true)
.replaceError(with: false)
.sink(receiveValue: { finished in
/// Finish the authorization process
presentMainScreen = true
}).store(in: &cancellableBag)
HKHealthStore()
.get(sample: SAMPLE_TYPE, start: START_RANGE, end: END_RANGE)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { subscription in
/// Do something at the subscriber's end of life or error
}, receiveValue: { samples in
/// Save samples or do something with them
}).store(in: &cancellableBag)
You can also query for a number of samples instead of using a Date
range.
Bear in mind that this is an expensive request as it requests both heart rate data and the workout's route from every requested HKWorkout.
var samples: [HKCWorkoutDetails] = []
HKHealthStore()
.workouts(type: .running, from: START_RANGE, to: END_RANGE)
.flatMap({ $0.publisher })
.flatMap({ $0.workoutWithDetails })
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { comp in
switch comp {
case .finished:
/// `samples` contains all the data asked for
case .failure(_):
/// Act on the error
}
}, receiveValue: { details in
samples.append(details)
})
.store(in: &cancellableBag)
The gist of this is to replace the possible error that might surface if the queried sample doesn't have permissions for it with a nil
, or whatever suits your purpose, before continuing.
HKHealthStore()
.statistic(for: HKObjectType.quantityType(forIdentifier: .restingHeartRate)!, with: .discreteAverage, from: Date().startOfMonth!, to: Date())
.map({ $0.averageQuantity()?.doubleValue(for: UserUnits.shared().heartCountUnits) })
.replaceError(with: nil)
.assertNoFailure()
.receive(on: DispatchQueue.main)
.assign(to: &$VARIABLE)
If the HKWorkout
you're querying has been recorded from an Apple Watch using the native Workouts.app this is straightforward.
workout.appleWatchPaces
.receive(on: DispatchQueue.main)
.replaceError(with: [])
.sink(receiveCompletion: { sub in
switch sub {
case .finished:
break
case .failure(_):
fatalError()
}
},receiveValue: { events in
/// Work with the received `HKWorkoutEvents`
}).store(in: &bag)
There are other times when you want to query paces from an Apple Watch if it exists and default to manual calculations if it fails or they don't exist. This requires access to query .distanceWalkingRunning
samples.
NOTE: These manual calculations, splits
might have some errors as this is an algorithm where some issues might appear.
NOTE 2: Apps like Strava might not produce reliable calculations by the way the save data on HealthKit
. As far as I know there is no workaround around this. If you have a better solution for this feel free to open a pull request.
workout.appleWatchPaces
.receive(on: DispatchQueue.main)
.replaceError(with: [])
.flatMap({ applePaces -> AnyPublisher<[HKWorkoutEvent], Error> in
if applePaces.isEmpty {
return workout.workout.splits
} else {
return Just(applePaces).setFailureType(to: Error.self).eraseToAnyPublisher()
}
})
.sink(receiveCompletion: { sub in
switch sub {
case .finished:
break
case .failure(_):
fatalError()
}
},receiveValue: { events in
/// Work with the `HKWorkoutEvents`
}).store(in: &bag)
Strava
link |
Stars: 8 |
Last commit: 2 years ago |
Adds support for HKQuantitySeriesSampleQuery via the series
method.
Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics