Search code examples
ioshealthkit

HKAnchoredObjectQuery updateHandler not being called


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)
}

Solution

  • 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 HKQuerys: Do HKQuery's need to be stopped in View Controllers