Search code examples
iosswifthealthkitcompletionhandler

Check if user has authorised steps in HealthKit


I'm trying to figure out how I can manage to set a completion block inside a HKStatisticsCollectoniQuery.

The whole idea with this is because I want to know if a user has accepted the permissions to fetch for instance steps. But according to Apple's documentation it's not possible.

Apple's documentation for authorizationStatus(for:)

This method checks the authorization status for saving data. To help prevent possible leaks of sensitive health information, your app cannot determine whether or not a user has granted permission to read data. If you are not given permission, it simply appears as if there is no data of the requested type in the HealthKit store. If your app is given share permission but not read permission, you see only the data that your app has written to the store. Data from other sources remains hidden.


So I was thinking about fetching steps between the intervals January 2014 until today. And see if the value is still 0, which can mean two things. Either that the user hasn't accepted the permission... or that the user just hasn't walked any steps. I do not know if this is the right approach. So if there is a better way, then I would appreciate any answer!

Alright, so far I've done this following function:

func retrieveTotalCount(typeIdentifier: HKQuantityTypeIdentifier, completion: @escaping (_ total: Double) -> Void) {
    //   Define the Step Quantity Type
    let unitsCount = HKQuantityType.quantityType(forIdentifier: typeIdentifier)!


    // "1391269654" stands for january 1st 2014
    let initialDate = Date(timeIntervalSince1970: Double(1391269654))

    //  Set the Predicates & Interval
    let predicate = HKQuery.predicateForSamples(withStart: initialDate, end: Date(), options: .strictStartDate)
    var interval = DateComponents()
    interval.day = 1

    //  Perform the Query
    let query = HKStatisticsCollectionQuery(quantityType: unitsCount, quantitySamplePredicate: predicate, options: [.cumulativeSum], anchorDate: initialDate as Date, intervalComponents:interval)

    query.initialResultsHandler = { query, results, error in
        if error != nil {
            //  Something went Wrong
            return
        }

        if let myResults = results { 
            myResults.enumerateStatistics(from: initialDate, to: Date()) { statistics, stop in

                if let quantity = statistics.sumQuantity() {
                    if quantity.is(compatibleWith: HKUnit.meter()) {
                        let count = quantity.doubleValue(for: HKUnit.meter())
                        completion(count)
                    } else if quantity.is(compatibleWith: HKUnit.kilocalorie()) {
                        let count = quantity.doubleValue(for: HKUnit.kilocalorie())
                        completion(count)
                    } else {
                        let count = quantity.doubleValue(for: HKUnit.count())
                        completion(count)
                    }
                }
            }
        }
    }
    self.healthStore.execute(query)
}

Whenever I call this function it will give me multiply completions, just because I'm going through each statistic.

override func viewDidLoad() {
    super.viewDidLoad()

    var counter = 0
    HealthKitManager.shared.retrieveTotalCount(typeIdentifier: .stepCount) { (count) in
        counter += 1
        print("Counter: \(counter)")
        // In my case the Counter will print out:
        // Counter: 1
        // Counter: 2
        // Counter: ...
        // Counter: n
    }
}

How can I make sure the completion block is just completed once?


Solution

  • The most efficient way to determine whether your app has access to any steps at all is to execute a single HKSampleQuery for steps with a limit of 1 and no predicate or sort descriptors. You don’t need to execute multiple queries for separate periods of time.