watchos-2healthkitwatchosapple-watch-complicationclockkit

Apple watch complication not refreshing in the background when using HealthKit queries


I am trying to display a user's daily step count in an Apple Watch complication. I setup my class by calling HKHealthStore's requestAuthorizationToShareTypes method and the complication displays steps correctly when it is first added to the watch face. However, the refresh is never successful while making a health kit query. I am suspicious that it is something to do with HealthKit permissions because the HKSampleQuery's completion handler doesn't get called. If I just comment out the health kit query, then my code refreshes as expected. Does anyone know what I may be missing? Or if complication background refresh are not allowed to access HealthKit?

Here is the code block that does work:

/// Provide the entry that should currently be displayed.
/// If you pass back nil, we will conclude you have no content loaded and will stop talking to you until you next call -reloadTimelineForComplication:.
func getCurrentTimelineEntryForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTimelineEntry?) -> Void) {

        let calendar = NSCalendar.currentCalendar()
        let now = NSDate()
        var startDate: NSDate? = nil
        var interval: NSTimeInterval = 0
        let endDate = NSDate()

        calendar.rangeOfUnit(NSCalendarUnit.Day, startDate: &startDate, interval: &interval, forDate: now)

        // Show dummy step data...
        let timelineEntry = self.buildTimelineEntry(complication, stepCount: 10, currentDateInterval: NSDate())
        handler(timelineEntry)
}

Here is the code block that does not work. The update in the error case doesn't even get called:

/// Provide the entry that should currently be displayed.
/// If you pass back nil, we will conclude you have no content loaded and will stop talking to you until you next call -reloadTimelineForComplication:.
func getCurrentTimelineEntryForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTimelineEntry?) -> Void) {

        let calendar = NSCalendar.currentCalendar()
        let now = NSDate()
        var startDate: NSDate? = nil
        var interval: NSTimeInterval = 0
        let endDate = NSDate()

        calendar.rangeOfUnit(NSCalendarUnit.Day, startDate: &startDate, interval: &interval, forDate: now)

        let predicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: endDate, options: HKQueryOptions.StrictStartDate)
        let sortDescriptor = NSSortDescriptor(key:HKSampleSortIdentifierStartDate, ascending: true)
        let stepSampleType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)!
        let sampleQuery = HKSampleQuery(sampleType: stepSampleType, predicate: predicate, limit: 0, sortDescriptors: [sortDescriptor]) { (sampleQuery, results, error ) -> Void in

            if error != nil {
                dispatch_async(dispatch_get_main_queue(), { () -> Void in
                    let timelineEntry = self.buildTimelineEntry(complication, stepCount: 10, currentDateInterval: NSDate())
                    handler(timelineEntry)
                })

                return
            }

            self.currentSteps = [HKQuantitySample]()

            if results != nil {
                self.currentSteps = results as! [HKQuantitySample]
            }

            let countUnit = HKUnit(fromString: "count")
            var stepCount = 0.0
            var currentDate = now
            for result in self.currentSteps {
                stepCount += result.quantity.doubleValueForUnit(countUnit)
                currentDate = result.endDate
            }

            dispatch_async(dispatch_get_main_queue(), { () -> Void in
                let timelineEntry = self.buildTimelineEntry(complication, stepCount: stepCount, currentDateInterval: currentDate)
                handler(timelineEntry)
            })
        }

        self.healthStore.executeQuery(sampleQuery)
}

Solution

  • Attempting to asynchronously fetch (HealthKit) data within the complication controller will be unreliable.

    In addition, trying to fetch or compute within the complication controller will needlessly use up the execution time budget that is allotted to your complication.

    Apple recommends that you fetch the data and cache it before the complication data source needs it.

    The job of your data source class is to provide ClockKit with any requested data as quickly as possible. The implementations of your data source methods should be minimal. Do not use your data source methods to fetch data from the network, compute values, or do anything that might delay the delivery of that data. If you need to fetch or compute the data for your complication, do it in your iOS app or in other parts of your WatchKit extension, and cache the data in a place where your complication data source can access it. The only thing your data source methods should do is take the cached data and put it into the format that ClockKit requires.