Search code examples
iosswiftwatchkithealthkit

Calories not being recorded for HealthKit Watch app


I can't get any calories/activeEnergyBurned to show up in my app, and don't know why?

WorkoutInterfaceController:

private func totalCalories() -> Double {
    return totalEnergyBurned.doubleValue(for: HKUnit.kilocalorie())
}

private func setTotalCalories(calories: Double) {
    totalEnergyBurned = HKQuantity(unit: HKUnit.kilocalorie(), doubleValue: calories)
}

func startQuery(quantityTypeIdentifier: HKQuantityTypeIdentifier) {
    let datePredicate = HKQuery.predicateForSamples(withStart: workoutStartDate, end: nil, options: .strictStartDate)
    let devicePredicate = HKQuery.predicateForObjects(from: [HKDevice.local()])
    let queryPredicate = NSCompoundPredicate(andPredicateWithSubpredicates:[datePredicate, devicePredicate])

    let updateHandler: ((HKAnchoredObjectQuery, [HKSample]?, [HKDeletedObject]?, HKQueryAnchor?, Error?) -> Void) = { query, samples, deletedObjects, queryAnchor, error in
        self.process(samples: samples, quantityTypeIdentifier: quantityTypeIdentifier)
    }

    let query = HKAnchoredObjectQuery(type: HKObjectType.quantityType(forIdentifier: quantityTypeIdentifier)!,
                                      predicate: queryPredicate,
                                      anchor: nil,
                                      limit: HKObjectQueryNoLimit,
                                      resultsHandler: updateHandler)
    query.updateHandler = updateHandler
    healthStore.execute(query)

    activeDataQueries.append(query)
}

func process(samples: [HKSample]?, quantityTypeIdentifier: HKQuantityTypeIdentifier) {
    DispatchQueue.main.async { [weak self] in
        guard let strongSelf = self, !strongSelf.isPaused else { return }

        if let quantitySamples = samples as? [HKQuantitySample] {
            for sample in quantitySamples {
                if quantityTypeIdentifier == HKQuantityTypeIdentifier.activeEnergyBurned {
                    let newKCal = sample.quantity.doubleValue(for: HKUnit.kilocalorie())
                    strongSelf.setTotalCalories(calories: strongSelf.totalCalories() + newKCal)
                    print("NewKCal: \(newKCal)")
                    print("TotalCalories: \(strongSelf.totalCalories())")
                }
            }

            strongSelf.updateLabels()
        }
    }
}

The log prints out '0' no matter how long I run the app for.

I've tested on the Simulator and on Device.

Per a question, here is the code for saving workout data:

private func saveWorkout() {
    // Create and save a workout sample
    let configuration = workoutSession!.workoutConfiguration
    let isIndoor = (configuration.locationType == .indoor) as NSNumber
    print("locationType: \(configuration)")

    let workout = HKWorkout(activityType: configuration.activityType,
                            start: workoutStartDate!,
                            end: workoutEndDate!,
                            workoutEvents: workoutEvents,
                            totalEnergyBurned: totalEnergyBurned,
                            totalDistance: nil,
                            metadata: [HKMetadataKeyIndoorWorkout:isIndoor]);

    healthStore.save(workout) { success, _ in
        if success {
            self.addSamples(toWorkout: workout)
        }
    }

    // Pass the workout to Summary Interface Controller
    WKInterfaceController.reloadRootControllers(withNames: ["SummaryInterfaceController"], contexts: [workout])
}

private func addSamples(toWorkout workout: HKWorkout) {
    // Create energy and distance samples
    let totalEnergyBurnedSample = HKQuantitySample(type: HKQuantityType.activeEnergyBurned(),
                                                   quantity: totalEnergyBurned,
                                                   start: workoutStartDate!,
                                                   end: workoutEndDate!)


    // Add samples to workout
    healthStore.add([totalEnergyBurnedSample], to: workout) { (success: Bool, error: Error?) in
        if success {
            // Samples have been added
            print("Samples have been added")
        }
    }
}

Solution

  • You can do it another way without using predicate.

    weak var delegate: WorkoutSessionManagerDelegate?
    let healthStore: HKHealthStore
    var workoutSession: HKWorkoutSession 
    var workoutStartDate: NSDate?
    var workoutEndDate: NSDate?
    var queries: [HKQuery] = []
    var activeEnergySamples: [HKQuantitySample] = []
    var distanceSamples: [HKQuantitySample] = []
    var heartRateSamples: [HKQuantitySample] = []
    let energyUnit = HKUnit.calorieUnit()
    let distanceUnit = HKUnit.meterUnit()
    let countPerMinuteUnit = HKUnit(fromString: "count/min")
    var anchor = HKQueryAnchor(fromValue: Int(HKAnchoredObjectQueryNoAnchor))
    let activeEnergyType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierActiveEnergyBurned)!
    let heartRateType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)! // 1/3
    
    var distanceType: HKQuantityType {
        if self.workoutSession.activityType == .Cycling {
            return HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceCycling)!
        } else {
            return HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning)!
        }
    }
    
    var currentActiveEnergyQuantity: HKQuantity
    var currentDistanceQuantity: HKQuantity
    var currentHeartRateSample: HKQuantitySample? 
    
    init(context: WorkoutSessionContext) {
        self.healthStore = context.healthStore
        self.workoutSession = HKWorkoutSession(activityType: context.activityType, locationType: context.locationType)
        self.currentActiveEnergyQuantity = HKQuantity(unit: self.energyUnit, doubleValue: 0.0)
        self.currentDistanceQuantity = HKQuantity(unit: self.distanceUnit, doubleValue: 0.0)
    
        super.init() 
        self.workoutSession.delegate = self
    }
    
    // MARK: Active Energy Burned Streaming
    func createActiveEnergyStreamingQuery(workoutStartDate: NSDate) -> HKQuery? {
    
        print("Active energy query started")
    
        // ** Creating a match samples predicate to sum the data is no longer the convention **
    
        // Sum the new quantities with the current active energy quantity.
        guard let quantityType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierActiveEnergyBurned) else {return nil}
    
        // Instantiate a HKAnchoredObjectQuery object with a results handler that calls our sumEnergyBurnedSamples function
        let activeEnergyQuery = HKAnchoredObjectQuery(type: quantityType, predicate: nil, anchor: anchor, limit: Int(HKObjectQueryNoLimit)) { (query, samples, deletedObjects, newAnchor, error) -> Void in
            guard let newAnchor = newAnchor else {return}
            self.anchor = newAnchor
            self.addActiveEnergySamples(samples)
        }
    
        // Results handler that calls our addActiveEnergySamples function
        activeEnergyQuery.updateHandler = {(query, samples, deletedObjects, newAnchor, error) -> Void in
                self.anchor = newAnchor!
                self.addActiveEnergySamples(samples)
        }
        return activeEnergyQuery
    }
    
    
    func addActiveEnergySamples(samples: [HKSample]?) {
    
        print("Updating calorie samples")
    
        guard let activeEnergyBurnedSamples = samples as? [HKQuantitySample] else { return }
    
        // addActiveEnergySamples method dispatches back to the main queue
        dispatch_async(dispatch_get_main_queue()) { 
    
            // Adds the new active energy sample to the running total
          self.currentActiveEnergyQuantity = self.currentActiveEnergyQuantity.addQuantitiesFromSamples(activeEnergyBurnedSamples, unit: self.energyUnit)
    
            // Adds that sample to an array of samples accumulated over the workout
            self.activeEnergySamples += activeEnergyBurnedSamples
    
            // Whenever new samples become available, call the corresponding delegate method. This updates the UI with new samples.
            self.delegate?.workoutSessionManager(self, didUpdateActiveEnergyQuantity: self.currentActiveEnergyQuantity)
    
            // Print checks
            guard let sample = activeEnergyBurnedSamples.first else{return}
            let value = sample.quantity.doubleValueForUnit(self.energyUnit)
            print(value)
        }
    }
    

    enter image description here