Skip to content

Instantly share code, notes, and snippets.

@kalm42
Created March 8, 2017 04:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kalm42/b7d4a14976e0167f4612c2ff406a2ff5 to your computer and use it in GitHub Desktop.
Save kalm42/b7d4a14976e0167f4612c2ff406a2ff5 to your computer and use it in GitHub Desktop.
Health Store
class HealthData {
//MARK: - Class Properties
private let healthStore = HKHealthStore()
private let queue = DispatchQueue(label: "com.kalm42.your-fat.hd", attributes: .concurrent)
private let group = DispatchGroup()
var perferredUnitOfMeasurement: UnitOfMeasurement = .lb
var isFinishedLoading: Bool = false
//HealthKit variables
private var hkUnitOfMeasurement = HKUnit.pound()
private var bodyMassQuantityType: HKQuantityType? = HKObjectType.quantityType(
forIdentifier: HKQuantityTypeIdentifier.bodyMass
)
private var bodyFatPercentageQuantityType: HKQuantityType? = HKObjectType.quantityType(
forIdentifier: HKQuantityTypeIdentifier.bodyFatPercentage
)
private var bodyMassSampleType: HKSampleType? = HKObjectType.quantityType(
forIdentifier: HKQuantityTypeIdentifier.bodyMass
)
private var bodyFatPercentageSampleType: HKSampleType? = HKObjectType.quantityType(
forIdentifier: HKQuantityTypeIdentifier.bodyFatPercentage
)
//
enum HKHealthStoreError: Error {
case HealthDataNotAvailable
case RequestForAccessNotDisplayed
}
//init
init() {
if HKHealthStore.isHealthDataAvailable() {
do {
try requestAccess()
} catch {
print("Request for access failed to display to the user and was not previously displayed.")
}
} else {
fatalError("The health store is not available.")
}
getPreferredUnits()
queue.async {
if let bodyMassSampleType = self.bodyMassSampleType {
self.getSamplesFromHealthKit(for: bodyMassSampleType)
}
}
queue.async {
if let bodyFatPercentageSampleType = self.bodyFatPercentageSampleType {
self.getSamplesFromHealthKit(for: bodyFatPercentageSampleType)
}
}
group.notify(queue: .main){
self.isFinishedLoading = true
}
}
//MARK: - Private Functions
private func requestAccess() throws {
if let bodyMassSampleType = bodyMassSampleType,
let bodyFatPercentageSampleType = bodyFatPercentageSampleType,
let bodyMassQuantityType = bodyMassQuantityType,
let bodyFatPercentageQuantityType = bodyFatPercentageQuantityType
{
let dataTypesToWrite: Set = [bodyMassSampleType, bodyFatPercentageSampleType]
let dataTypesToRead: Set = [bodyMassQuantityType, bodyFatPercentageQuantityType]
// Request auth
healthStore.requestAuthorization(toShare: dataTypesToWrite, read: dataTypesToRead, completion: successOrError)
} else {
throw HKHealthStoreError.RequestForAccessNotDisplayed
}
}
private func getPreferredUnits() {
if let bodyMass = bodyMassQuantityType {
let bodyMassSet: Set<HKQuantityType> = [bodyMass]
healthStore.preferredUnits(for: bodyMassSet, completion: unitDictionaryOrError)
}
}
private func successOrError(success: Bool, AuthorizationError: Error?) -> Void {
if success {
print("Success.")
} else {
print("Failure.")
}
if let error = AuthorizationError {
print(error)
}
}
// sets the perferredUnitOfMeasurement
private func unitDictionaryOrError (unitForQuantityTypeDictionary: [HKQuantityType: HKUnit], preferredUnitError: Error?) -> Void {
for (quantityType, unit) in unitForQuantityTypeDictionary {
if quantityType == self.bodyMassQuantityType, let preferredUnit = UnitOfMeasurement(rawValue: unit.unitString) {
self.hkUnitOfMeasurement = unit
self.perferredUnitOfMeasurement = preferredUnit
}
}
}
private func getSamplesFromHealthKit(for sampleType: HKSampleType){
//1. Set the SampleType to find
// Its a global var bodyFatPercentageSample
//2. Write the query, date range, and all other options for the query
//2a. Dates
let endDate = Date() //now
let startDate = Date(timeIntervalSinceNow: -TimeLengthInSeconds.oneYear.rawValue)
//2b. Query Options
let queryOptions = HKQueryOptions()
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: queryOptions)
//3. How many results can be returned.
let resultLimit = Int(HKObjectQueryNoLimit)
//4. Run this bitch
let query = HKSampleQuery(
sampleType: sampleType, predicate: predicate, limit: resultLimit, sortDescriptors: nil, resultsHandler: convertHealthKitSample
)
self.healthStore.execute(query)
}
private func convertHealthKitSample(query: HKSampleQuery, samples: [HKSample]?, error: Error?) -> Void {
queue.async {
self.group.enter()
guard let samples = samples as? [HKQuantitySample] else {
fatalError("Failed to downcast health kit samples to hk quantity samples")
}
for sample in samples {
let quantity = sample.quantity
var bodyFatSamples: [BodyFatPercentageSample]? = nil
var bodyMassSamples: [BodyMassSample]? = nil
if quantity.is(compatibleWith: HKUnit.percent()) {
//is body fat percentage
let value = quantity.doubleValue(for: HKUnit.percent())
let date = sample.endDate
let bodyFat = BodyFatPercentageSample(date: date, value: value)
bodyFatSamples?.insert(bodyFat, at: 0)
} else {
//is body mass
let value = quantity.doubleValue(for: self.hkUnitOfMeasurement)
let date = sample.endDate
let bodyMass = BodyMassSample(date: date, value: value, unit: self.perferredUnitOfMeasurement)
bodyMassSamples?.insert(bodyMass, at: 0)
}
}
}
}
//MARK: - Public Methods
func record(bodyMass: Double, bodyFatPercentage: Double, at date: Date){
var samples: [HKObject] = []
// Add the body mass to the array.
if let bodyMassQuantityType = self.bodyMassQuantityType {
let bodyMassQuantity = HKQuantity(unit: hkUnitOfMeasurement, doubleValue: bodyMass)
samples.append(
HKQuantitySample(type: bodyMassQuantityType, quantity: bodyMassQuantity, start: date, end: date)
)
}
// Add the body fat percentage to the array.
if let bodyFatPercentageQuantityType = self.bodyFatPercentageQuantityType {
let bodyFatPercentageQuantity = HKQuantity(unit: HKUnit.percent(), doubleValue: bodyFatPercentage)
samples.append(
HKQuantitySample(type: bodyFatPercentageQuantityType, quantity: bodyFatPercentageQuantity, start: date, end: date)
)
}
// Now that everything is packaged up, send the array to the health kit for saving.
healthStore.save(samples, withCompletion: successOrError)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment