Harish Harish - 5 months ago 9
iOS Question

How to check when HealthKit function has been completed (Swift)

Well, the title really says it all, and I haven't been able to find an answer anywhere that works for me so I am turning to StackOverFlow. I am trying to get a users step count and assign that value to a UILabel. So here is some of my code (please note that this function is contained in another class as a result the label is not within the scope of this function):

func readTodayHealthData() -> Int {
var stepCount: Int = 0
func getStepsHealthData() {
let stepsUnit = HKUnit.countUnit()
let sumOption = HKStatisticsOptions.CumulativeSum
let stepsHealthDataQuery = HKStatisticsQuery(quantityType: stepsHealth, quantitySamplePredicate: predicate, options: sumOption) {
query, results, error in
if let sumQuantity = results?.sumQuantity() {
dispatch_async(dispatch_get_main_queue(), {
stepCount = sumQuantity.doubleValueForUnit(stepsUnit) * 2
})
}
}
healthKitStore?.executeQuery(stepsHealthDataQuery)
}
return stepCount
}
//Set UILabel Value
//**This code is in my View Controller which is in a separate class as a result this label is NOT within the scope of this function.**
myLabel.text = String(readTodayHealthData)


Then when I run the app on an actual device I see the label text is zero, and I know for a fact that I have done some walking today :). So, I think the issue is that when I try to set the labels value the function hasn't fully finished executing.

I know this because when I use the delay function and wait for two seconds I end up getting a value, but if I don't wait then I get a value of zero.

So the main question is: How do I check when a function is completely finished executing?

Answer

The thing is that the operation you're using is async, then you need to handle properly, you have two options here:

  1. Update the UILabel in the completionHandler inside your function getStepsHealthData in the main thread because you are going to update the UI, like in this way:

    func getStepsHealthData() {
         var stepCount: Int = 0
         let stepsUnit = HKUnit.countUnit()
         let sumOption = HKStatisticsOptions.CumulativeSum
    
         let stepsHealthDataQuery = HKStatisticsQuery(quantityType: stepsHealth, quantitySamplePredicate: predicate, options: sumOption) {
            query, results, error in
              if let sumQuantity = results?.sumQuantity() {
                 dispatch_async(dispatch_get_main_queue(), {
                   stepCount = sumQuantity.doubleValueForUnit(stepsUnit) * 2
    
                   //Set UILabel Value
                   myLabel.text = String(stepCount)
                 })
              }
         }
         healthKitStore?.executeQuery(stepsHealthDataQuery)
    }
    

    And you don't need to return anything.

  2. If you want to return the step counts from the function so you need to play a little with closures and modify your function like in the following way:

    func getStepsHealthData(completion: (steps: Int) -> ()) {
         var stepCount: Int = 0
         let stepsUnit = HKUnit.countUnit()
         let sumOption = HKStatisticsOptions.CumulativeSum
    
         let stepsHealthDataQuery = HKStatisticsQuery(quantityType: stepsHealth, quantitySamplePredicate: predicate, options: sumOption) {
            query, results, error in
              if let sumQuantity = results?.sumQuantity() {
                  stepCount = sumQuantity.doubleValueForUnit(stepsUnit) * 2
                  completion(stepCount)
              }
         }
         healthKitStore?.executeQuery(stepsHealthDataQuery)
    }
    

    And then you can call it like in this way from outside:

    self.getStepsHealthData() { (steps) -> Void in
       dispatch_async(dispatch_get_main_queue(), {
           //Set UILabel Value
           myLabel.text = String(stepCount)
       })
    }
    

I hope this help you.

Comments