Reputation:
UPDATED at bottom on 01/30/16 @ 7:40PM EST
So I'm trying to run a StatisticsQuery to get the total DistanceRunningWalking
of the day stored in HealthKit and then store the results of that query in a variable, to be worked with later. The query seems to be working fine, as I've tested printing the results of the query (totalDistance
) to a Label from within the function. The problem I'm running into is when trying to save the result to a variable instead.
Here is the code in my HealthKitManager.swift file:
import HealthKit
class HealthKitManager {
class var sharedInstance: HealthKitManager {
struct Singleton {
static let instance = HealthKitManager()
}
return Singleton.instance
}
let healthStore: HKHealthStore? = {
if HKHealthStore.isHealthDataAvailable() {
return HKHealthStore()
} else {
return nil
}
}()
let distanceCount = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning)
let distanceUnit = HKUnit(fromString: "mi")
}
Code at top of ViewController: (this is the variable I'd like to save to)
let healthKitManager = HealthKitManager.sharedInstance
//Set up variable to contain result of query
var distanceTotalLength:Double?
Code in viewDidLoad
:
//Run the function
requestHealthKitAuthorization()
//Set value of variable to query result
distanceTotalLength = queryDistanceSum()
Code in body of ViewController:
func requestHealthKitAuthorization() {
let dataTypesToRead = NSSet(objects: healthKitManager.distanceCount!)
healthKitManager.healthStore?.requestAuthorizationToShareTypes(nil, readTypes: dataTypesToRead as NSSet as? Set<HKObjectType>, completion: { [unowned self] (success, error) in
if success {
self.queryDistanceSum()
} else {
print(error!.description)
}
})
}
func queryDistanceSum() {
let sumOption = HKStatisticsOptions.CumulativeSum
let startDate = NSDate().dateByRemovingTime()
let endDate = NSDate()
let predicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: endDate, options: [])
let statisticsSumQuery = HKStatisticsQuery(quantityType: healthKitManager.distanceCount!, quantitySamplePredicate: predicate, options: sumOption) {
[unowned self] (query, result, error) in
if let sumQuantity = result?.sumQuantity() {
dispatch_async(dispatch_get_main_queue(), {
let totalDistance = sumQuantity.doubleValueForUnit(self.healthKitManager.distanceUnit)
self.distanceTotalLength = totalDistance
})
}
}
healthKitManager.healthStore?.executeQuery(statisticsSumQuery)
}
On the last line return (distanceTotalLength)!
I'm getting an error when launching the application which reads fatal error: unexpectedly found nil while unwrapping an Optional value
. I've realized that this is most likely a problem dealing with scope (although I may be doing something else wrong, so please point anything out) but I'm failing to see/find the solution to the issue on my own.
Any help with this would be greatly appreciated, so thanks in advance!
UPDATE: 01/30/16 @ ~7:40PM EST
Okay, so I've been working on trying to fix this myself and I've made a discovery: The code is definitely working. By assigning an initial value of 0
to distanceTotalLength
at the top of the ViewController, I was able to run the application without getting the fatal error. However, when I then tried passing the value of distanceTotalLength
to another view via a prepareForSegue function, I realized that it is being assigned after all. When I go to that view, it isn't using the initial value of 0
, but the result of the query instead.
The way I tested this out was by setting up the variable: var distanceTotalLength:Double = 0
at the very top of my viewController, before viewDidLoad
Then inside viewDidLoad
I assigned the value to a label using distanceLabel.text = String(distanceTotalLength)
and as I said, the label ends up reading 0
. But when I transition to another view, passing the value of distanceTotalLength
and printing the value out there, it works. On this second screen, it prints the result of the query, not 0
.
So I'm assuming that the problem is that the query runs and then assigns the value AFTER the view has already loaded in with all of it's predefined values. Unfortunately, this is where I get stuck again. Anyone out there know how to help me out now that I've gotten this far?
Upvotes: 0
Views: 1508
Reputation: 1671
You are right. The completionHandler closure that gets called after your HKStatisticsQuery
has finished executing happens at an undermined later time. Think of the query execution as sending a letter in the mail to someone, then waiting for their response; it's not going to be immediate, but in the meantime you can go off and do other things.
To handle this in your code, add a completion closure of your own to the queryDistanceSum method. Then after setting the self.distanceTotalLength = totalDistance
, call that closure. When implementing the code for the closure, add anything that needs to be done after the distance has been set, like update your UI.
func queryDistanceSum(completion: () -> Void) { // <----- add the closure here
let sumOption = HKStatisticsOptions.CumulativeSum
let startDate = NSDate().dateByRemovingTime()
let endDate = NSDate()
let predicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: endDate, options: [])
let statisticsSumQuery = HKStatisticsQuery(quantityType: healthKitManager.distanceCount!, quantitySamplePredicate: predicate, options: sumOption) {
[unowned self] (query, result, error) in
if let sumQuantity = result?.sumQuantity() {
dispatch_async(dispatch_get_main_queue(), {
let totalDistance = sumQuantity.doubleValueForUnit(self.healthKitManager.distanceUnit)
self.distanceTotalLength = totalDistance
completion() // <----- call the closure here
})
}
}
healthKitManager.healthStore?.executeQuery(statisticsSumQuery)
}
// Then whenever you need to update the distance sum call the function
// with the closure, then handle the result as needed
queryDistanceSum { () -> () in
// distanceTotalLength has now been set.
// Update UI for new distance value or whatever you need to do
}
Whenever you implement a closure you have to assume that the code in the closure will be executed at a later time.
Upvotes: 1