Developing a mirrored watch/iPhone app that needs to see the beat to beat timing. This video from apple claims to show how to do this: https://developer.apple.com/videos/play/wwdc2019/218/?time=1513 at 28.26.
the answer provided here Apple Health initHeartBeatSeries , how to get HKHeartbeatSeriesSample? using HKSampleQuery works but gives a one shot answer. I want a continuous update whilst the app is running.
When I try a HKAnchoredObjectQuery it fails with: "*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid data types 'HKDataTypeIdentifierHeartbeatSeries' for _HKWorkoutComparisonFilter.workoutType: Error Domain=com.apple.healthkit Code=3 "'HKDataTypeIdentifierHeartbeatSeries' not found in allowed data types classes (HKWorkoutType)" UserInfo={NSLocalizedDescription='HKDataTypeIdentifierHeartbeatSeries' not found in allowed data types classes (HKWorkoutType)}'"
My code based somewhat on apples documentation is:
// this should get initial batch of samples and then have update called as they come in
func getBeatSamples( startDate: Date ) -> Void {
let heartbeatSeriesSampleType = HKSeriesType.heartbeat()
var myAnchor : HKQueryAnchor? = HKQueryAnchor(fromValue: 0)
let todayPredicate = HKQuery.predicateForSamples(withStart: startDate, end: Date(), options: [])
let cyclingPredicate = HKQuery.predicateForWorkouts(with: .cycling)
let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [todayPredicate, cyclingPredicate])
let updateHandler: (HKAnchoredObjectQuery, [HKSample]?, [HKDeletedObject]?, HKQueryAnchor?, Error?) -> () = { (query, samplesOrNil, deletedObjectsOrNil, newAnchor, errorOrNil) in
guard errorOrNil == nil else {
// error
return
}
guard let samples = samplesOrNil, let deletedObjects = deletedObjectsOrNil else {
// Properly handle the error.
return
}
myAnchor = newAnchor
//for beatSample in samples {
// Process the new heart rate samples.
if let heartbeatSeriesSample = samples.first as? HKHeartbeatSeriesSample {
let queryBeats = HKHeartbeatSeriesQuery(heartbeatSeries: heartbeatSeriesSample) { (query, timeSinceSeriesStart, precededByGap, done, error) in
guard error == nil else {
// error
return
}
print(timeSinceSeriesStart)
}
self.healthStore.execute(queryBeats)
}
//}
// Copilot version
if let coSamples = samplesOrNil as? [HKHeartbeatSeriesSample]
{
for sample in coSamples {
print("coSample: \(sample)")
}
} else {
// handle error
}
for deletedStepCountSamples in deletedObjects {
// Process the deleted step count samples.
}
// The results come back on an anonymous background queue.
// Dispatch to the main queue before modifying the UI.
DispatchQueue.main.async {
// Update the UI here.
}
}
//***** fails on this line
let query = HKAnchoredObjectQuery(type: HKSeriesType.heartbeat(),
predicate: compoundPredicate,
anchor: myAnchor,
limit: HKObjectQueryNoLimit,
resultsHandler: updateHandler)
query.updateHandler = updateHandler
healthStore.execute(query)
}
The readData setup for the workout is:
let typesToRead: Set<HKObjectType> = [
HKQuantityType(.heartRate),
// Beat addition
HKSeriesType.heartbeat(),
HKQuantityType.quantityType(forIdentifier: .heartRateVariabilitySDNN)!,
// end beat addition
HKQuantityType(.activeEnergyBurned),
HKQuantityType(.distanceWalkingRunning),
HKQuantityType(.cyclingSpeed),
HKQuantityType(.cyclingPower),
HKQuantityType(.cyclingCadence),
HKQuantityType(.distanceCycling),
HKQuantityType(.dietaryWater),
HKQuantityType.workoutType(),
HKObjectType.activitySummaryType()
]