Reputation: 2322
I have a ViewController that calls a class HKQueryWeight which runs a HealthKit Query (very, very slow btw) and saves the data to CoreData. If the user leaves the VC before the query is over, the app crashes.
fatal error: unexpectedly found nil while unwrapping an Optional value (lldb)
Initially I thought I could patch this by adding an activityIndicator which starts animating in viewDidAppear and stops at the end of the last function in the VC. It works. However, due to, I believe, the asynchronous nature of healthKit Querys, the animation stops before the actual healthKit query completes.
I am not sure if it is necessary to provide the code, but I have done so in case it is useful
ViewController:
class ViewController: UIViewController {
@IBOutlet var activityIndicator: UIActivityIndicatorView!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
activityIndicator.startAnimating()
setupArrays ()
}
func setupArrays (){
println("setting up arrays")
if NSUserDefaults.standardUserDefaults().boolForKey("hrSwitch") == true {
var hkQueryHeartRate = HKQueryHeartRate()
hkQueryHeartRate.performHKQuery()
}
if NSUserDefaults.standardUserDefaults().boolForKey("weightSwitch") == true {
var hkQueryWeight = HKQueryWeight()
hkQueryWeight.performHKQuery()
}
self.activityIndicator.stopAnimating()
}
HKQuery
import Foundation
import CoreData
import HealthKit
class HKQueryWeight: HKQueryProtocol {
func performHKQuery() {
var appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
var context = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext!
let healthKitManager = HealthKitManager.sharedInstance
let calendar = NSCalendar.currentCalendar()
let interval = NSDateComponents()
interval.day = 1
// Set the anchor date to Monday at 3:00 a.m.
let anchorComponents =
calendar.components(.CalendarUnitDay | .CalendarUnitMonth |
.CalendarUnitYear | .CalendarUnitWeekday, fromDate: NSDate())
let offset = (7 + anchorComponents.weekday - 2) % 7
anchorComponents.day -= offset
anchorComponents.hour = 3
//let now = NSDate()
let anchorDate = calendar.dateFromComponents(anchorComponents)
let quantityType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMass)
// Create the query
let query = HKStatisticsCollectionQuery(quantityType: quantityType,
quantitySamplePredicate: nil,
options: .DiscreteAverage,
anchorDate: anchorDate,
intervalComponents: interval)
// Set the results handler
query.initialResultsHandler = {
query, results, error in
if error != nil {
// Perform proper error handling here
println("*** An error occurred while calculating the statistics: \(error.localizedDescription) ***")
abort()
}
let endDate = NSDate()
let startDate =
calendar.dateByAddingUnit(.MonthCalendarUnit,
value: -6, toDate: endDate, options: nil)
// Plot the weekly step counts over the past 6 months
results.enumerateStatisticsFromDate(startDate, toDate: endDate) {
statistics, stop in
if let quantity = statistics.averageQuantity() {
let date = statistics.startDate
let weight = quantity.doubleValueForUnit(HKUnit.gramUnitWithMetricPrefix(.Kilo))
println("weight date: \(date)")
println("weight value: \(weight)")
var weightData = NSEntityDescription.insertNewObjectForEntityForName("HKWeight", inManagedObjectContext: context) as HKWeight
//Saving to CoreData
weightData.setValue(weight, forKey: "weight_data")
weightData.setValue(date, forKey: "weight_date")
context.save(nil)
}
}
}
healthKitManager.healthStore.executeQuery(query)
}
}
Upvotes: 0
Views: 467
Reputation: 37290
Right now, self.activityIndicator.stopAnimating()
is being called immediately after the queries are called. Since the queries are asynchronous, they can still be being executed in the background for some time after they're called, so if you remove the activity indicator immediately after the queries are invoked, the queries probably won't be complete yet. If you want the activity indicator to stop animating after your queries are complete, you have to call for it to stop animating from within your asynchronous query block.
Since your queries are in a different class, you can post a notification to end the activity indicator's animation at the end of each query, then stop animating the UIActivityIndicatorView
after the second query finishes and the second notification is received, ex:
var notificationCount:Int = 0
var totalQueries = 0
func setupArrays (){
println("setting up arrays")
notificationCount = 0
totalQueries = 0
NSNotificationCenter.defaultCenter().addObserver(self, selector: "removeActivityIndicator", name:"ActivityIndicatorNotification", object: nil)
if NSUserDefaults.standardUserDefaults().boolForKey("hrSwitch") == true {
totalQueries = totalQueries + 1
var hkQueryHeartRate = HKQueryHeartRate()
hkQueryHeartRate.performHKQuery()
}
if NSUserDefaults.standardUserDefaults().boolForKey("weightSwitch") == true {
totalQueries = totalQueries + 1
var hkQueryWeight = HKQueryWeight()
hkQueryWeight.performHKQuery()
}
if totalQueries == 0 {
self.activityIndicator.stopAnimating()
}
}
func removeActivityIndicator () {
notificationCount = notificationCount + 1
if notificationCount == totalQueries {
dispatch_async(dispatch_get_main_queue()) {
self.activityIndicator.stopAnimating()
NSNotificationCenter.defaultCenter().removeObserver(self, name:"ActivityIndicatorNotification", object:nil)
}
}
}
Then in HKQueryWeight:
func performHKQuery() {
// ...All the code before the query...
// Set the results handler
query.initialResultsHandler = {
query, results, error in
//...All the code currently within your query...
NSNotificationCenter.defaultCenter().postNotificationName("ActivityIndicatorNotification", object: nil) // <-- post notification to stop animating the activity indicator once the query's complete
}
Upvotes: 3