Swiftpack.co -  javierdemartin/HKCombine as Swift Package
Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.
javierdemartin/HKCombine
Combine-based wrapper for common HealthKit operations
.package(url: "https://github.com/javierdemartin/HKCombine.git", from: "v1.0.1")

platforms platforms code-size

HKCombine

Combine-based wrapper to perform HealthKit related queries.

My app, Singlet, makes full use of this Swift Package.

Installation

Add the repository link as a dependency on Xcode from File, Swift Packages & Add Package Dependency...

Usage

This package makes extensive use of the Combine framework.

Check if the device needs to request HealthKit authorization.
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)
Request permission to HealthKit for the given types.
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)
Query HealthKit samples.
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)
Query HKWorkout with all the associated heart rate and location data.

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)
HKStatisticQuery

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)
Splits/Paces from a HKWorkout

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)

Disclaimers

Strava

GitHub

link
Stars: 3
Last commit: 6 days ago

Ad: Job Offers

iOS Software Engineer @ Perry Street Software
Perry Street Software is Jack’d and SCRUFF. We are two of the world’s largest gay, bi, trans and queer social dating apps on iOS and Android. Our brands reach more than 20 million members worldwide so members can connect, meet and express themselves on a platform that prioritizes privacy and security. We invest heavily into SwiftUI and using Swift Packages to modularize the codebase.

Release Notes

Version 1.0.3
6 days ago

Adds support for HKQuantitySeriesSampleQuery via the series method.

Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API