HKLiveWorkoutBuilder only reporting heart rate - No other measurements

I'm using Healthkit with the following H/W specs: Apple Watch, series 8, OS: 10.6.1 (21U580) iPhone 11 Pro, OS: 17.6.1 Mac Studio M1 Xcode ver: 16.0 (16A242d)

I am trying to get Apple Watch to report heart rate, HRV, respiratory rate, and body temperature using Healthkit's HKLiveWorkoutBuilder implementing HKLiveWorkoutBuilderDelegate's workoutBuilder method. However, the only reported value that is found from the workoutBuilder method's collectedTypes (a Set of HKSampleType objects) is HKQuantityTypeIdentifierHeartRate. Nothing for HRV, respiratory rate, or body temperature. All entitlements are set up, the plist filled in, and capabilities in place. Not sure why only the heart rate is reported from the watch but nothing else.

I've scoured StackOverflow, Apple developer forums, even ChatGPT but none of the solutions work.

Any help most appreciate!

The model code is:

import Foundation
import HealthKit

class WatchModel: NSObject, HKLiveWorkoutBuilderDelegate, HKWorkoutSessionDelegate {
        
    private let healthStore = HKHealthStore()
    private var workoutSession: HKWorkoutSession!
    private var workoutBuilder: HKLiveWorkoutBuilder!
    
    override init() {
        super.init()
        requestAuthorization()
        startWorkoutSession()
    }

    private func requestAuthorization() {
        let heartRateType = HKQuantityType.quantityType(forIdentifier: .heartRate)!
        let respiratoryRateType = HKQuantityType.quantityType(forIdentifier: .respiratoryRate)!
        let HRVRateType = HKQuantityType.quantityType(forIdentifier: .heartRateVariabilitySDNN)!
        let temperatureRateType = HKQuantityType.quantityType(forIdentifier: .bodyTemperature)!
        
        let healthDataTypes: Set = [heartRateType, respiratoryRateType, HRVRateType, temperatureRateType]
        
        healthStore.requestAuthorization(toShare: healthDataTypes, read: healthDataTypes) { (success, error) in
            if !success {
                print("Authorization failed")
            }
        }
    }
    
    func workoutSession(_ workoutSession: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date) {
    }
    
    func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: any Error) {
    }
    
    func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) {
    }
    
    func startWorkoutSession() {
        let configuration = HKWorkoutConfiguration()
        configuration.activityType = .other
        configuration.locationType = .indoor
        
        do {
            workoutSession = try HKWorkoutSession(healthStore: healthStore, configuration: configuration)
            workoutBuilder = workoutSession.associatedWorkoutBuilder()
            workoutBuilder.delegate = self
            workoutBuilder.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore, workoutConfiguration: configuration)
            let dataSource = HKLiveWorkoutDataSource(healthStore: healthStore, workoutConfiguration: configuration)
            let respiratoryRate = HKQuantityType(.respiratoryRate)
            dataSource.enableCollection(for: respiratoryRate, predicate: nil)
            let bodyTemp = HKQuantityType(.bodyTemperature)
            dataSource.enableCollection(for: bodyTemp, predicate: nil)
            let hrv = HKQuantityType(.heartRateVariabilitySDNN)
            dataSource.enableCollection(for: hrv, predicate: nil)
            workoutSession.delegate = self
            workoutSession.startActivity(with: Date())
            workoutBuilder.beginCollection(withStart: Date(), completion: { (success, error) in
                if let error = error {
                    print("Error starting collection: \(error.localizedDescription)")
                }
            })
            
        } catch {
            print("Failed to start workout session: \(error.localizedDescription)")
        }
    }
    
    func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, didCollectDataOf collectedTypes: Set<HKSampleType>) {

        print("collected types: \(collectedTypes)")
        for type in collectedTypes {
            if let quantityType = type as? HKQuantityType {
                
                if quantityType == HKQuantityType.quantityType(forIdentifier: .heartRate) {
                    if let heartRateQuantity = workoutBuilder.statistics(for: quantityType)?.mostRecentQuantity() {
                        let heartRateUnit = HKUnit(from: "count/min")
                        let heartRateValue = heartRateQuantity.doubleValue(for: heartRateUnit)
                        print("heart rate: \(heartRateValue)")
                    }
                }
                
                if quantityType == HKQuantityType.quantityType(forIdentifier: .heartRateVariabilitySDNN) {
                    if let hrvQuantity = workoutBuilder.statistics(for: quantityType)?.mostRecentQuantity() {
                        let hrvUnit = HKUnit.secondUnit(with: .milli)
                        let hrvValue = hrvQuantity.doubleValue(for: hrvUnit)
                        print("HRV: \(hrvValue)")
                    }
                }
                
                if quantityType == HKQuantityType.quantityType(forIdentifier: .bodyTemperature) {
                    if let bodyTempQuantity = workoutBuilder.statistics(for: quantityType)?.mostRecentQuantity() {
                        let tempUnit = HKUnit.degreeCelsius()
                        let tempValue = bodyTempQuantity.doubleValue(for: tempUnit)
                        print("body temp: \(tempValue)")
                    }
                }
                
                if quantityType == HKQuantityType.quantityType(forIdentifier: .respiratoryRate) {
                    if let respRateQuantity = workoutBuilder.statistics(for: quantityType)?.mostRecentQuantity() {
                        let respRateUnit = HKUnit(from: "count/min")
                        let respRateValue = respRateQuantity.doubleValue(for: respRateUnit)
                        print("breathing: \(respRateValue)")
                    }
                }
            }
        }
    }
}
Answered by DTS Engineer in 804690022

This is because HRV, respiratory rate, and body temperature are not workout metrics, and the workout builder doesn’t gather and report them.

I can't find a documentation listing the workout metrics though. If that bothers you, you might consider filing a feedback report and share your report ID here.

To retrieve the HRV, respiratory rate, and body temperature, consider using HKSampleQuery (or other HealthKit query classes), with an appropriate time frame, to query from the HealthKit store.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

This is because HRV, respiratory rate, and body temperature are not workout metrics, and the workout builder doesn’t gather and report them.

I can't find a documentation listing the workout metrics though. If that bothers you, you might consider filing a feedback report and share your report ID here.

To retrieve the HRV, respiratory rate, and body temperature, consider using HKSampleQuery (or other HealthKit query classes), with an appropriate time frame, to query from the HealthKit store.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

@DTS Engineer (Ziqiao),

Thank you for your quick response on my question. I've used an HKSampleQuery as you've suggested which retrieves the HRV. However when running the query, no results are returned for respiratory rate or body temperature. The following code shows a function with various calls (please comment/uncomment as needed):


    func fetchData() {
        let bodyTemperatureType = HKQuantityType.quantityType(forIdentifier: .respiratoryRate)!
        
        let mostRecentPredicate = HKQuery.predicateForSamples(withStart: Date.distantPast, end: Date(), options: .strictEndDate)
        
        let sampleQuery = HKSampleQuery(sampleType: bodyTemperatureType, predicate: mostRecentPredicate, limit: 1, sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)]) { (query, samples, error) in
            
            guard let samples = samples as? [HKQuantitySample], let sample = samples.first else {
                print("No data available or error: \(String(describing: error))")
                return
            }
            
            // Doesn't work!
            let respiratoryRate = sample.quantity.doubleValue(for: HKUnit(from: "count/min"))
            // Doesn't work!
            //let bodyTemperature = sample.quantity.doubleValue(for: HKUnit.degreeCelsius())
            
            // This works
            //let hrv = sample.quantity.doubleValue(for: HKUnit.secondUnit(with: .milli))

            //print("Body Temperature: \(hrv)°C")
            //print("HRV: \(hrv)")
            print("Resp rate: \(respiratoryRate)")
        }
        
        // Execute the query
        healthStore.execute(sampleQuery)
    }

I see no documentation from Apple why only HRV can be retrieved from an HKSampleQuery but not body temperature or respiratory rate.

Any ideas why this is?

Accepted Answer

An Apple Watch only collects .respiratoryRate and .appleSleepingWristTemperature samples during sleep. If you don’t wear your Apple Watch when sleeping, it won't record samples of these types.

.bodyTemperature samples are always entered by a user or generated by a third party app/device. An Apple Watch doesn't record body temperature.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Ziqiao,

As per your email for following up on any additional questions, HealthKit only collects .respiratoryRate during sleep. However, there must be a way in which the developer can record this by specifying that the user is asleep.

The reason why I ask is because I am writing a health and wellness app for my PhD where I am using music therapy for relaxation and utilizing machine learning to detect whether or not the soundscape being played is having any positive effect on the participant. Respiratory rate would be a very useful biosensing metric for my research.

Is there a way in which I can retrieve the .respiratoryRate in code on the Apple Watch as the user is "asleep"?

Sincerely,

Arron

As far as I know, an Apple Watch only collects .respiratoryRate samples during sleep because measuring respiratory rate requires detecting subtle physical motions of the wrist and those motions are only detectable by Apple Watch when the body is asleep. I don't see any way to tell an Apple Watch that the body is asleep, while it is not, to have the watch collect the samples.

HealthKit does allow third party apps to create .respiratoryRate samples and query historical ones though. The general process of creating a HealthKit sample is covered in Saving data to HealthKit. So you can probably consider measuring the respiratory rate with your own way, and then writing your own samples to HealthKit.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Thanks again Ziqiao, that helps!

Arron

HKLiveWorkoutBuilder only reporting heart rate - No other measurements
 
 
Q