TruMan1
TruMan1

Reputation: 36158

How to properly get heart rate from WatchKit?

I am trying to simply display the last recorded heart rate from the Apple Watch. Below is what I am trying, but the results variable in the completion handler of updateHeartRate get millions of records and kills the app (almost seems like corrupt data or doing something incorrectly):

class InterfaceController: WKInterfaceController {

    @IBOutlet var heartLabel: WKInterfaceLabel!

    let heartRateUnit = HKUnit.countUnit().unitDividedByUnit(HKUnit.minuteUnit())
    let heartRateQuantityType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)

    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
    }

    override func willActivate() {
        super.willActivate()

        guard HKHealthStore.isHealthDataAvailable() else {
            heartLabel.setText("Not available")
            return
        }

        requestHealthKitAuthorization()
        queryHeartRateSample()
    }

    @IBAction func workoutMenuTapped() {

    }
}

private extension InterfaceController {

    func requestHealthKitAuthorization() {
        guard let heartRateQuantityType = heartRateQuantityType else {
            displayNotAllowed()
            return
        }

        let dataTypes = Set(arrayLiteral: heartRateQuantityType)

        HKHealthStore.sharedInstance?.requestAuthorizationToShareTypes(nil, readTypes: dataTypes) {
            [unowned self] success, error in

            guard success else {
                self.displayNotAllowed()
                return
            }

            self.queryHeartRateSample()
        }
    }

    func displayNotAllowed() {
        heartLabel.setText("Not allowed")
    }

    func queryHeartRateSample() {
        guard let heartRateQuery = getHeartRateQuery() else { return }
        HKHealthStore.sharedInstance?.executeQuery(heartRateQuery)
    }

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

        dispatch_async(dispatch_get_main_queue()) {
            guard let sample = heartRateSamples.first else { return }
            let value = sample.quantity.doubleValueForUnit(self.heartRateUnit)
            self.heartLabel.setText(String(UInt16(value)))
        }
    }

    func getHeartRateQuery() -> HKQuery? {
        guard let heartRateQuantityType = heartRateQuantityType else {
            displayNotAllowed()
            return nil
        }

        let heartRateQuery = HKSampleQuery(sampleType: heartRateQuantityType,
            predicate: nil,
            limit: 100,
            sortDescriptors: nil) {
                [unowned self] query, results, error in
                guard let results = results as? [HKQuantitySample] else { return }
                self.updateHeartRate(results)
        }

        return heartRateQuery
    }

}

@available(iOS 8.0, *)
public extension HKHealthStore {

    class var sharedInstance: HKHealthStore? {
        if !HKHealthStore.isHealthDataAvailable() {
            return nil
        }

        struct Singleton {
            static let instance = HKHealthStore()
        }

        return Singleton.instance
    }

}

It does properly ask me to grant permissions, but still the update never gets triggered. Am I doing something wrong?

Upvotes: 1

Views: 2220

Answers (1)

Victor Sigler
Victor Sigler

Reputation: 23449

I prefer to use HKAnchoredObjectQuery to search only for new data added to HealthKit it avoid query all the data, so you can get the latest heart rate values in the following way once you have granted the permissions:

private let healthStore = HKHealthStore()

private let heartRateType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)!
private let heartRateUnit = HKUnit(fromString: "count/min")

/**
 Start to measure the heart rate.
 */
func startToMeasure() {
    self.healthStore.executeQuery(self.createStreamingQuery()!)
}


/**
Create a query to receive new heart rate data from the HealthKit store.

- returns: The created query
*/
private func createStreamingQuery() -> HKQuery {
    let predicate = HKQuery.predicateForSamplesWithStartDate(NSDate(), endDate: nil, options: .None)

    let query = HKAnchoredObjectQuery(type: heartRateType, predicate: predicate, anchor: nil, limit: Int(HKObjectQueryNoLimit)) {
        (query, samples, deletedObjects, anchor, error) -> Void in
        self.formatSamples(samples)
    }

    query.updateHandler = { (query, samples, deletedObjects, anchor, error) -> Void in
        self.formatSamples(samples)
    }

    return query
}

/**
 Format the samples received from HealthKit.

 - parameter samples: Some samples
 */
private func formatSamples(samples: [HKSample]?) {
    guard let samples = samples as? [HKQuantitySample] else { return }
    guard let quantity = samples.last?.quantity else { return }

    let value = Int(quantity.doubleValueForUnit(heartRateUnit))
    print("HeartRate: \(value)") 
}

The above code need to be integrated in some class or UIViewController you have. I have used in the above code a predicate to filter by date by you can remove it in case of not be relevant.

I hope this help you.

Upvotes: 3

Related Questions