2

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?

Community
  • 1
  • 1
Harish
  • 1,374
  • 17
  • 39

3 Answers3

5

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.

Victor Sigler
  • 23,243
  • 14
  • 88
  • 105
0

The completion handlers (which you're already using) are called when the data is available. readTodayHealthData() will return well before that happens.

You need to use the data within the scope of the completion handler. For example, you could rewrite your function like this:

func updateLabel() {
        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
                    self.myLabel.text = "\(stepCount)"
                })
            }
        }
        healthKitStore?.executeQuery(stepsHealthDataQuery)
    }
}

This will update the label when the data has been returned.

Aaron Brager
  • 65,323
  • 19
  • 161
  • 287
0

You aren't actually trying to check if a function has finished executing though. That block that you pass to HKStatisticsQuery isn't really part of your original function. If you really want to prevent the myLabel.text= line from executing until after the block is called, you could use a semaphore, but that's a terrible solution to your actual problem. Why not have your block that's passed to HK update the label directly?

dispatch_async(dispatch_get_main_queue(), {
                    stepCount = sumQuantity.doubleValueForUnit(stepsUnit) * 2
                    myLabel.text = "\(stepCount)"
                })
RyanR
  • 7,728
  • 1
  • 25
  • 39
  • Ok, let me update my question the label is **not** in the scope of the function. – Harish Feb 05 '16 at 23:48
  • Regardless of the scope of the label, you need to understand the concept of blocks and why your function returns without the `stepCount` being set. Read the [docs from Apple on blocks](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html) and you'll know exactly why this is happening and how to address it. – RyanR Feb 05 '16 at 23:52