I have a UITableView
backed by an HKAnchoredObjectQuery
. My resultsHandler
is called fine, but my updateHandler
is never called. I am not looking for background delivery, just live foreground delivery when a new sample is added to the health store. According to the docs on the updateHandler:
If this property is set to nil, the anchor query automatically stops as soon as it has finished calculating the initial results. If this property is not nil, the query behaves similarly to the observer query. It continues to run, monitoring the HealthKit store. If any new, matching samples are saved to the store—or if any of the existing matching samples are deleted from the store—the query executes this update handler on a background queue.
I do set my updateHandler
on the query, and there are being new samples written to the health store.
In my viewDidLoad
I set up my query:
override func viewDidLoad() {
super.viewDidLoad()
// Setup TableView
tableView.dataSource = self
tableView.delegate = self
let calendar = Calendar.current
let nextDay = calendar.date(byAdding: .day, value: 1, to: self.date)!
let startOfNextDay = calendar.startOfDay(for: nextDay)
let resultsHandler: HKAnchoredObjectQueryHandler = { [weak self](query, samples, deletedObjects, anchor, error) in
guard let strongSelf = self else { return }
guard let samples = samples as? [HKCorrelation],
let _ = deletedObjects else {
// Need error handling here
return debugPrint("Query error occurred: \(error!.localizedDescription)")
}
DispatchQueue.main.async {
// Get a reference to the query and anchor
strongSelf.query = query
strongSelf.anchor = anchor
// Add new samples
strongSelf.foodEntries = samples
for entry in strongSelf.foodEntries {
debugPrint(entry.metadata?[HKMetadataKey.mealOfDay] as! Int)
}
}
}
let updateHandler: HKAnchoredObjectQueryHandler = {[weak self](query, samples, deletedObjects, anchor, error) in
guard let strongSelf = self else { return }
guard let samples = samples as? [HKCorrelation],
let deletedObjects = deletedObjects else {
// Need error handling here
return debugPrint("Query error occurred: \(error!.localizedDescription)")
}
DispatchQueue.main.async {
// Get a reference to
strongSelf.anchor = anchor
print(#line, "HKAnchoredObjectQuery IS LONG RUNNING")
// Add new samples
strongSelf.foodEntries.append(contentsOf: samples)
// Remove deleted samples
let deletedObjects = deletedObjects
for object in deletedObjects {
if let index = strongSelf.foodEntries.index(where: {$0.uuid == object.uuid} ) {
strongSelf.foodEntries.remove(at: index)
}
}
strongSelf.tableView.reloadData()
}
}
HealthManager.shared.anchoredFoodQuery(startDate: self.date, endDate: startOfNextDay, anchor: anchor, resultsHandler: resultsHandler, updateHandler: updateHandler)
}
The anchored object methods in my HealthManager
are:
func anchoredFoodQuery(startDate: Date, endDate: Date, anchor: HKQueryAnchor? = nil, resultsHandler: @escaping HKAnchoredObjectQueryHandler, updateHandler: HKAnchoredObjectQueryHandler?) {
let predicate = HKSampleQuery.predicateForSamples(withStart: startDate, end: endDate, options: [.strictStartDate, .strictEndDate]) //NSPredicate(format: "date => %@ && date < %@", startDate as NSDate, endDate as NSDate)
guard let foodIdentifier = HKCorrelationType.correlationType(forIdentifier: .food) else {
fatalError("Can't make food Identifier")
}
anchoredQuery(for: foodIdentifier, predicate: predicate, resultsHandler: resultsHandler, updateHandler: updateHandler)
}
/// Wrapper to create and execute a query
private func anchoredQuery(for sampleType: HKSampleType, predicate: NSPredicate?, anchor: HKQueryAnchor? = nil, limit: Int = HKObjectQueryNoLimit, resultsHandler: @escaping HKAnchoredObjectQueryHandler , updateHandler: HKAnchoredObjectQueryHandler?) {
let query = HKAnchoredObjectQuery(type: sampleType,
predicate: predicate,
anchor: anchor,
limit: limit,
resultsHandler: resultsHandler)
query.updateHandler = updateHandler
healthStore.execute(query)
}
My UITableViewController
was in a UIPageViewController
. viewDidAppear()
on the UITableViewController
does not get called when returning from the detail screen, so I assumed viewDidDisappear()
would not get called as well and I was stopping the query in there. My assumption was wrong.
A question on where to stop long running HKQuery
s: Do HKQuery's need to be stopped in View Controllers