Calories not being recorded for HealthKit Watch app
Asked Answered
A

1

15

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")
        }
    }
}
Alanaalanah answered 13/8, 2016 at 22:53 Comment(2)
Where do you save it ?Gelsenkirchen
@Gelsenkirchen I added the code above for how I save the data. Let me know if you have any thoughts or need me to add anything else!Alanaalanah
F
4

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

Freberg answered 14/8, 2016 at 7:22 Comment(4)
Yup. I recommend watching the video I was watching to see how the main class was done with initialization. Starts at about the 29 minute mark. developer.apple.com/videos/play/wwdc2015/203. He uses predicate but you can just replace with the query block. There is also lots of code in my own questions. I added my init to my answer.Freberg
Did you watch the 2016 video? Wouldn't the way they do it in the 2016 video be more updated or no?Alanaalanah
Not sure if I've seen the same one you have as there are many. My data is printing successfully to the console. I just have to send that data to my labels and make sure I'm storing data to the store and other details but the main part is done. It's been a lot of work and I needed a lot of help.Freberg
I think you should to watch this: developer.apple.com/wwdc21/10009Selia

© 2022 - 2024 — McMap. All rights reserved.