11

Building a HealthKit/WatchKit app based off WWDC 2015 - Session 203.

There is no source code so I am writing it on the fly. There is a method I am having difficulty with since they don't discuss it.

Luckily it's the same addQuantitiesFromSamples method for all the workout types that add sample quantities to the workout session.

Of course I have this error because that method does not exist in my code.

Value of type 'HKQuantity' has no member 'addQuantitiesFromSamples'

I am not sure how to write a method that adds sample quantities. The method must be relatively basic because it's being used on all three of the sample queries in the project.

The sumDistanceSamples function is where the mystery addQuantitiesFromSamples method is called.

This is one of the three blocks containing the same error so I just need to find a solution for one of them.

WorkoutSessionManager.swift

class WorkoutSessionManager: NSObject, HKWorkoutSessionDelegate {

var activeEnergySamples: [HKQuantitySample] = []
var distanceSamples: [HKQuantitySample] = []
var heartRateSamples: [HKQuantitySample] = []

// ... code

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?

// ... code


// MARK: Data queries

// Create streaming query helper method.
func createStreamingDistanceQuery(workoutStartDate: NSDate) -> HKQuery? {
    guard let quantityType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning) else {return nil}

    // Instantiate a HKAnchoredObjectQuery object with a results handler.
    let distanceQuery = 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.sumDistanceSamples(samples)
    }

    // Results handler that calls sumDistanceSamples function.
    distanceQuery.updateHandler = {(query, samples, deletedObjects, newAnchor, error) -> Void in
        self.anchor = newAnchor!
        self.sumDistanceSamples(samples)
    }

    return distanceQuery
}

func sumDistanceSamples(samples: [HKSample]?) {
    guard let currentDistanceSamples = samples as? [HKQuantitySample] else { return }

    dispatch_async(dispatch_get_main_queue()) {

        // Error point - "no member 'addQuantitiesFromSamples'"
        self.currentDistanceQuantity = self.currentDistanceQuantity.addQuantitiesFromSamples(currentDistanceSamples, unit: self.distanceUnit)

        // Add sample to array of samples accumulated over the workout.
        self.distanceSamples += currentDistanceSamples

        self.delegate?.workoutSessionManager(self, didUpdateDistanceQuantity: self.currentDistanceQuantity)

    }
}

// MARK: HEART RATE STREAMING
func createHearRateStreamingQuery(workoutStartDate: NSDate) -> HKQuery? {

    // alternative method to creating a match samples predicate

    // Append the new quantities with the current heart rate quantity.
    guard let quantityType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate) else {return nil}

    // Instantiate a HKAnchoredObjectQuery object with a results handler that calls our sumHeartRateSamples function
    let heartRateQuery = HKAnchoredObjectQuery(type: quantityType, predicate: nil, anchor: anchor, limit: Int(HKObjectQueryNoLimit)) { (query, samples, deletedObjectts, newAnchor, error) -> Void in
        guard let newAnchor = newAnchor else {return}
        self.anchor = newAnchor
        self.updateHeartRateSamples(samples)
    }

    // Results handler that calls our addActiveEnergySamples function
    heartRateQuery.updateHandler = {(query, samples, deletedObjects, newAnchor, error) -> Void in
        self.anchor = newAnchor!
        self.updateHeartRateSamples(samples)
    }

    return heartRateQuery
}

func updateHeartRateSamples(samples: [HKSample]?) {
    guard let heartRateCountSamples = samples as? [HKQuantitySample] else { return }

    // updateHeartRateSamples method dispatches back to the main queue.
    dispatch_async(dispatch_get_main_queue()) { 

       // Error: Value of type 'HKQuantitySample?' has no member 'addQuantitiesFromSamples
       self.currentHeartRateSample = self.currentHeartRateSample.addQuantitiesFromSamples(heartRateCountSamples, unit: self.countPerMinuteUnit)

        // appends/updates that sample to an array of samples accumulated over the workout.
        self.heartRateSamples += heartRateCountSamples

        self.delegate?.workoutSessionManager(self, didUpdateHeartRateSample: self.currentHeartRateSample!)
    }

}
Edison
  • 11,881
  • 5
  • 42
  • 50

1 Answers1

7

I'm not sure if this is what you're looking for, but this appears to be your missing method from someone else who watched the same WWDC video:

extension HKQuantity {
    func addQuantitiesFromSamples(samples : [HKQuantitySample], unit: HKUnit) -> HKQuantity  {

        var currentValue = doubleValueForUnit(unit)

        for sample in samples {
            let value = sample.quantity.doubleValueForUnit(unit)
            currentValue += value
        }
        let result = HKQuantity(unit: unit, doubleValue: currentValue)
        return result
    }
}

Source: Calories and Distance data from query

JAL
  • 41,701
  • 23
  • 172
  • 300
  • It's funny. I obviously found that thread as well since I was searching everywhere. But I didn't realize that extension might work. Let me try it and get back to you. Thanks. – Edison Aug 04 '16 at 20:17
  • @tymac placing it at the end of your Swift file outside of the class should work. Is your class private or internal? Have you tried making the extension public? Are you using multiple modules in your application? – JAL Aug 04 '16 at 20:32
  • Oh wait a second! I think I understand now. 2 out of the 3 summing blocks seem to be ok. The `addDistanceSamples` method and `addActiveEnergySamples` method appear to accept the extension. But the heart rate summing doesn't like the extension because the heart rate doesn't sum. It just updates right? Could I use duplicate the same extension to create a separate method for the heart rate? How do I work with `updateHeartRateSamples`? – Edison Aug 04 '16 at 20:35
  • @tymac Absolutely. You could add an additional method to the extension, or change the extension and check for the heart rate sample type and do something else. – JAL Aug 04 '16 at 20:37
  • @tymac Not sure, there are no syntax errors. I stepped away from my computer but will try to recreate your issue with a MCVE when I get back. – JAL Aug 04 '16 at 21:05
  • Found out why. `currentActiveEnergyQuantity` and `currentDistanceQuantity` are of type `HKQuantity`. But `currentHeartRateSample` is of type `HKQuantitySample`. Actually this `updateHeatRateSamples` method is not in the Apple WWDC project (I just assumed to copy it) so they are obviously not using the same `addQuantitiesFromSamples` method. I will create a `updateHeartRateValueFromSamples` function in the new extension. Would it be possible to briefly show me an adjusted version of the function you gave me that can work with my new `updateHeartRateValueFromSamples` function? – Edison Aug 04 '16 at 21:23
  • I found this. http://stackoverflow.com/questions/27268665/ios-healthkit-how-to-save-heart-rate-bpm-values-swift – Edison Aug 04 '16 at 21:31