Swift HealthKit Trigger Heart Rate Sample - swift

I'm able to read average heart rate data from HealthKit for a period of time (duration of workout) with the below code.
However, when I look in the Apple Health app, it shows that the heart rate samples from the Apple Watch are taken very infrequently (every 15-30 min). This makes my average HR read for the period quite inaccurate.
How can I trigger HealthKit to sample (gather) Heart Rate data from the watch and write it to HealthKit?
let endDate = (calendar as NSCalendar).date(byAdding: NSCalendar.Unit.minute, value: duration, to: startDate, options: NSCalendar.Options())
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: HKQueryOptions())
let squery = HKStatisticsQuery(quantityType: heartRateType, quantitySamplePredicate: predicate, options: HKStatisticsOptions.discreteAverage, completionHandler: { (qurt, result, errval) -> Void in
guard errval == nil else { print("error"); completion(0.0, errval); return }
var averageHeartRate : Double = 0.0
if (result != nil && result!.averageQuantity() != nil) {
let quantity : HKQuantity = result!.averageQuantity()!
averageHeartRate = quantity.doubleValue(for: HKUnit.count().unitDivided(by: HKUnit.minute()))
}
})

To get more frequent heart rate data from Apple Watch, the user must be running an app on their watch that is tracking an HKWorkoutSession. If your app has a paired watchOS app that tracks workouts, you can start a workout from the iPhone by using startWatchApp(with workoutConfiguration:completion:).

Related

Why is CloudKit on Apple Watch super slow?

I am using CloudKit to store very simple workout data. The quantity is negligible.
I am using the same code to interact with CloudKit for the iOS app as well as the watchOS app. This is the code I use for loading data:
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: recordType.rawValue, predicate: predicate)
let queryOperation = CKQueryOperation(query: query)
var results: [CKRecord] = []
queryOperation.recordFetchedBlock = { (record: CKRecord ) in
results.append(record)
}
queryOperation.queryCompletionBlock = { (cursor: CKQueryOperation.Cursor?, error: Error?) in
if let error = error {
print("Getting all " + recordType.rawValue + " records with CloudKit was unsuccessful", error)
response(false, nil)
return
}
if cursor == nil {
response(true, results)
return
}
let nextOperation = CKQueryOperation(cursor: cursor!)
nextOperation.recordFetchedBlock = queryOperation.recordFetchedBlock
nextOperation.queryCompletionBlock = queryOperation.queryCompletionBlock
privateDatabase.add(nextOperation)
}
privateDatabase.add(queryOperation)
On iOS the loading is almost instant, on watchOS this can take minutes which is basically unusable. Sporadically the loading speed on watchOS can be decent.
What could be the cause?
Concept
qualityOfService is set to default when you don't assign a configuration.
Assume the watch is low on battery then system decides whether to process the operation immediately or later.
So setting it explicitly might help the system determine how you would like the operation to be handled.
Code
Can you try setting the configuration as follows:
let configuration = CKOperation.Configuration()
configuration.qualityOfService = .userInitiated
queryOperation.configuration = configuration
queryOperation.queuePriority = .veryHigh //Use this wisely, marking everything as very high priority doesn't help

Health Kit steps by movement for the day separated by device type

How to get data like in image below in Swift, im calculating steps per day and steps per hour but not sure how to get like below. I would like to get by device(watch, phone) steps per movement group not by a time interval?
I used HKSampleQuery
func getStepCountForTodaySegmentedByMovement( complete: #escaping ([HKCumulativeQuantitySample]) -> () ) {
let healthStore = HKHealthStore()
guard let stepsQuantityType = HKQuantityType.quantityType(forIdentifier: .stepCount) else {
fatalError("*** Unable to create a step count type ***")
}
let predicate = HKQuery.predicateForSamples(withStart: startOfDay, end: .distantFuture, options: .strictEndDate)
let query = HKSampleQuery(sampleType: stepsQuantityType,
predicate: predicate,
limit: HKObjectQueryNoLimit,
sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: true)]) {
(query, results, error) in
complete(results as? [HKCumulativeQuantitySample] ?? [])
}
healthStore.execute(query)
}
Sample query works if you want individual samples like what the Health app displays. Related point, in case it's useful, you can also get the stats separated by source if you pass HKStatisticsOptions separateBySource

Contents of data returned by HKStatisticsQuery

I'm using following code to get burned energy for today:
func getTodaysSummary(for type:HKQuantityType!, unit u:HKUnit!, completion: #escaping (Double) -> Void) {
let stepsQuantityType = type
let now = Date()
let startOfDay = Calendar.current.startOfDay(for: now)
let predicate = HKQuery.predicateForSamples(withStart: startOfDay, end: now, options: .strictStartDate)
let query = HKStatisticsQuery(quantityType: stepsQuantityType!, quantitySamplePredicate: predicate, options: .cumulativeSum) { (_, result, error) in
var resultCount = 0.0
guard let result = result else {
completion(resultCount)
return
}
if let sum = result.sumQuantity() {
resultCount = sum.doubleValue(for: u)
}
DispatchQueue.main.async {
completion(resultCount)
}
}
healthStore.execute(query)
}
...
self.getTodaysSummary(for: HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned), unit: HKUnit.kilocalorie(), completion: { (energyBurned) in
print("\(energyBurned)")
})
Now I need to understand, what data does it return? In different sources like apple documentation I read this data should contain all activity including walking, swimming etc.
But in my case it returns only the number 680 (I added this value manually via + button on Activity Energy screen of Health app)
It definetly doesn't include steps I've made during the day.
So the question, do I need to calculate those calories separately?
Also there are iWatch rings that also seem like not returning by HKStatisticsQuery requests. Should I calculate them separately as well to calculate total burned energy?
The iPhone does not automatically compute Active Energy. The user must have an Apple Watch in order to get Active and Basal Energy samples automatically written to HealthKit.

Getting instantaneous heart beat data swift

I'm trying to get the instance of each heart beat from an Apple Watch. Currently I am able to get the heart rate in beats per minute but can't get the times of each heart beat. It seems to send the heart rate data at random time intervals making it not possible to use the time of each sample to get heart beat times.
It doesn't matter what the units are as long as I can get it to update when it receives a new beat rather than every few beats as it is currently doing. Or if I can get it to give me the time of each beat rather than beats per time. Is there someway to force updateHandler to be called when a heart beat comes in? I thought that was the point of using the HKAnchoredObjectQuery.
func startRecordingHeartRate() {
let heartRateSample = HKSampleType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)
let heartRateUnit = HKUnit.count().unitDivided(by: HKUnit.minute())
let datePredicate = HKQuery.predicateForSamples(withStart: Date(), end: nil,
options: .strictStartDate)
let anchor: HKQueryAnchor? = nil
let updateHandler: (HKAnchoredObjectQuery, [HKSample]?, [HKDeletedObject]?, HKQueryAnchor?, Error?) -> () = {
query, newResult, deleted, newAnchor, error in
if let samples = newResult as? [HKQuantitySample] {
guard samples.count > 0 else {
return
}
for sample in samples {
let doubleSample = sample.quantity.doubleValue(for: heartRateUnit)
let timeSinceStart = sample.endDate.timeIntervalSince(self.startTime)
self.hkHeartRate[0].append(doubleSample)
self.hkHeartRate[1].append(Double(timeSinceStart))
}
self.updateHeartRateLabel()
self.processData.heartRateArray = self.hkHeartRate
self.processData.checkData()
}
}
let heartRateQuery = HKAnchoredObjectQuery(type: heartRateSample!,
predicate: datePredicate,
anchor: anchor,
limit: Int(HKObjectQueryNoLimit),
resultsHandler: updateHandler)
heartRateQuery.updateHandler = updateHandler
healthStore.execute(heartRateQuery)
}
func updateHeartRateLabel() {
let endIndex = self.hkHeartRate[0].endIndex
let lastHeartRate = self.hkHeartRate[0][endIndex-1]
self.heartRateLabel.setText(self.numberFormatter.string(from:NSNumber(value: lastHeartRate)) ?? "")
}
There is no way to observe individual heart beat events on Apple Watch. The averaged heart rate quantity samples are the only representation of heart rate available to you. If you want to increase the frequency of heart rate measurements, your app should start an HKWorkoutSession.

HealthKit fetch data between interval

I have a little problem grasping HealthKit. I want to get heart rate from HealthKit with specific time.
I have done this in the past (until I noticed that I couldn't fetch data when the phone was locked)
func retrieveMostRecentHeartRateSample(completionHandler: (sample: HKQuantitySample) -> Void) {
let sampleType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)
let predicate = HKQuery.predicateForSamplesWithStartDate(NSDate.distantPast() as! NSDate, endDate: NSDate(), options: HKQueryOptions.None)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)
let query = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: 1, sortDescriptors: [sortDescriptor])
{ (query, results, error) in
if error != nil {
println("An error has occured with the following description: \(error.localizedDescription)")
} else {
let mostRecentSample = results[0] as! HKQuantitySample
completionHandler(sample: mostRecentSample)
}
}
healthKitStore.executeQuery(query)
}
var observeQuery: HKObserverQuery!
func startObservingForHeartRateSamples() {
println("startObservingForHeartRateSamples")
let sampleType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)
if observeQuery != nil {
healthKitStore.stopQuery(observeQuery)
}
observeQuery = HKObserverQuery(sampleType: sampleType, predicate: nil) {
(query, completionHandler, error) in
if error != nil {
println("An error has occured with the following description: \(error.localizedDescription)")
} else {
self.retrieveMostRecentHeartRateSample {
(sample) in
dispatch_async(dispatch_get_main_queue()) {
let result = sample
let quantity = result.quantity
let count = quantity.doubleValueForUnit(HKUnit(fromString: "count/min"))
println("sample: \(count)")
heartChartDelegate?.updateChartWith(count)
}
}
}
}
healthKitStore.executeQuery(observeQuery)
}
This code will fetch the latest sample every time it is a change in HealthKit. But as I said earlier, it won't update when the phone is locked. I tried using:
self.healthKitStore.enableBackgroundDeliveryForType(HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate), frequency: HKUpdateFrequency.Immediate) { (success, error) in
if success{
println("success")
} else {
println("fail")
}
}
But this didn't work and as I found out there was a bug that Apple said it wasn't working as they wanted. Guess it is some security-thing.
But then I thought, maybe I can request samples between a startTime and endTime. For example i have EndTime(2015-05-31 10:34:45 +0000) and StartTime(2015-05-31 10:34:35 +0000).
So my question is how can I get heart rate samples between these two times.
I guess I must do it in the
HKQuery.predicateForSamplesWithStartDate(myStartTime, endDate: myEndTime, options: HKQueryOptions.None)
But when I tried it didn't find anything. Maybe I got this all wrong...
I am using a heart rate monitor on my chest and I know that I get some values in healthKit within the start and end time.
Edit:
Ok I tried it and it is working some times, not always. Someone has an idea?
func fetchHeartRates(endTime: NSDate, startTime: NSDate){
let sampleType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)
let predicate = HKQuery.predicateForSamplesWithStartDate(startTime, endDate: endTime, options: HKQueryOptions.None)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)
let query = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: 100, sortDescriptors: [sortDescriptor])
{ (query, results, error) in
if error != nil {
println("An error has occured with the following description: \(error.localizedDescription)")
} else {
for r in results{
let result = r as! HKQuantitySample
let quantity = result.quantity
let count = quantity.doubleValueForUnit(HKUnit(fromString: "count/min"))
println("sample: \(count) : \(result)")
}
}
}
healthKitStore.executeQuery(query)
}
Edit 2:
It was working but I couldn't call it the way I did. So I fetched it a couple of seconds later and it worked fine :)
...But as I said earlier, it won't update when the phone is locked...Guess it is some security-thing.
You are correct.
From HealthKit Framework Reference:
Because the HealthKit store is encrypted, your app cannot read data from the store when the phone is locked. This means your app may not be able to access the store when it is launched in the background. However, apps can still write data to the store, even when the phone is locked. The store temporarily caches the data and saves it to the encrypted store as soon as the phone is unlocked.
If you want your app to be alerted when there are new results, you should review Managing Background Delivery:
enableBackgroundDeliveryForType:frequency:withCompletion:
Call this method to register your app for background updates. HealthKit wakes your app whenever new samples of the specified type are saved to the store. Your app is called at most once per time period defined by the specified frequency.