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.
Related
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
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.
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:).
In my current project, I need to sync with HealthKit samples with my application. I am fetching sample data from HealthKit and writing some app generated samples back to HealthKit. For fetching I'm using the following function:-
private func readHealthKitSample(sampleType:HKSampleType, limit: Int, startDate: NSDate, endDate: NSDate, completion: (([HKSample]?, NSError!) -> Void)!){
let mostRecentPredicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate:endDate, options: .None)
// 2. Build the sort descriptor to return the samples in descending order
let sortDescriptor = NSSortDescriptor(key:HKSampleSortIdentifierStartDate, ascending: false)
// 3. we want to limit the number of samples returned by the query to just 1 (the most recent)
let limit = limit
// 4. Build samples query
let sampleQuery = HKSampleQuery(sampleType: sampleType, predicate: mostRecentPredicate, limit: limit, sortDescriptors: [sortDescriptor])
{ (sampleQuery, results, error ) -> Void in
if let error = error {
self.Logger.error("HealthKit Sample Data Fetch Error: \(error.localizedDescription)")
completion(nil , error)
return;
} else {
// self.Logger.debug("HealthKit Sample Data Fetch SUCCESS: \(results)")
}
// Execute the completion closure
if completion != nil {
completion(results,nil)
}
}
// 5. Execute the Query
self.healthKitStore.executeQuery(sampleQuery)
}
My app requires not to consider the samples written by itself to HealthKit Store. So, is there a way to filter sample data in a such way that I can avoid receiving them written by my application and consider only those samples written by other apps?
You can use HKSource to filter out your own app and NSCompoundPredicate to combine this with your existing predicate filter:
let mostRecentPredicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate:endDate, options: .None)
let myAppPredicate = HKQuery.predicateForObjectsFromSource(HKSource.defaultSource()) // This would retrieve only my app's data
let notMyAppPredicate = NSCompoundPredicate(notPredicateWithSubpredicate: myAppPredicate) // This will retrieve everything but my app's data
let queryPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [mostRecentPredicate, notMyAppPredicate])
let sampleQuery = HKSampleQuery(sampleType: sampleType, predicate: queryPredicate, limit: limit, sortDescriptors: [sortDescriptor]) {
// Process results here...
}
Swift 5+:
let mostRecentPredicate = HKQuery.predicateForSamples(withStart: startDate, end:endDate, options: [])
let myAppPredicate = HKQuery.predicateForObjects(from: HKSource.default()) // This would retrieve only my app's data
let notMyAppPredicate = NSCompoundPredicate(notPredicateWithSubpredicate: myAppPredicate) // This will retrieve everything but my app's data
let queryPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [mostRecentPredicate, notMyAppPredicate])
let sampleQuery = HKSampleQuery(sampleType: sampleType, predicate: queryPredicate, limit: limit, sortDescriptors: [sortDescriptor]) {
// Process results here...
}
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.