iOS Healthkit How to Save Heart Rate (Bpm) Values? Swift

HealthKit: Daily Heart Rate Average

The main problem is that you're asking for statistics.sumQuantity(), which will always be nil for this query. Instead, you should ask for statistics.averageQuantity().

Also, quantity.doubleValue(for: HKUnit.count()) will throw an error, because heart rate is not stored in counts, but in counts per unit of time. To get beats per minute, use HKUnit.count().unitDivided(by: HKUnit.minute()).

This will get your code working, but you also really should limit your query for the dates you need. Don't run an unbounded query and then limit the results to fit your date frame, set the predicate to get only the statistics you need.

Here is an example that incorporates everything I said above:

func printFortnightAvgHeartRate() {
let calendar = Calendar.current

let heartRateType = HKQuantityType.quantityType(forIdentifier: .heartRate)!

// Start 14 days back, end with today
let endDate = Date()
let startDate = calendar.date(byAdding: .day, value: -14, to: endDate)!

let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: [])

// Set the anchor to exactly midnight
let anchorDate = calendar.date(bySettingHour: 0, minute: 0, second: 0, of: Date())!

// Generate daily statistics
var interval = DateComponents()
interval.day = 1

// Create the query
let query = HKStatisticsCollectionQuery(quantityType: heartRateType,
quantitySamplePredicate: predicate,
options: .discreteAverage,
anchorDate: anchorDate,
intervalComponents: interval)

// Set the results handler
query.initialResultsHandler = { query, results, error in
guard let statsCollection = results else { return }

for statistics in statsCollection.statistics() {
guard let quantity = statistics.averageQuantity() else { continue }

let beatsPerMinuteUnit = HKUnit.count().unitDivided(by: HKUnit.minute())
let value = quantity.doubleValue(for: beatsPerMinuteUnit)

let df = DateFormatter()
df.dateStyle = .medium
df.timeStyle = .none
print("On \(df.string(from: statistics.startDate)) the average heart rate was \(value) beats per minute")
}
}

HKHealthStore().execute(query)
}

And as a side note, in your addToArray function you seem to be using the number argument to differentiate between types of data. At least use Int for that, not Double, but ideally this should be an enum.

Query to Healthstore for Resting Heart Rate not returning any values

Turns out restingHeartRate has its own permission that is required (in addition to heartRate):

 private func requestAccessToHealthKit() {
let healthStore = HKHealthStore()

let allTypes = Set([HKObjectType.quantityType(forIdentifier: .heartRate)!,
HKObjectType.quantityType(forIdentifier: .restingHeartRate)!,

healthStore.requestAuthorization(toShare: allTypes, read: allTypes) { (success, error) in
if !success {

}

}
}

Getting new heart rate data live from Health App?

You can use an HKAnchoredObjectQuery to create a query that returns an initial set of data and then updates to the data set.

Unfortunately, you can't provide a sort descriptor to the HKAnchoredObjectQuery, so you need to sort the data after you receive it if you don't want ascending order.

Here is a model object I created so that I could test in SwiftUI.

It creates an HKAnchoredQuery and sets an update handler function. The update handler converts the HealthKit results into my HeartRateEntry struct (This is so I could easily display the data in a SwiftUI list). The array is then sorted by descending date.

The update function stores the newAnchor that was received so that only changes are delivered in the future.

While testing I found that running the heart rate app on my watch, moving my test app into the background and then swapping back to it triggered the new heart rate data more quickly than just waiting for the new data to be delivered.

import Foundation
import HealthKit

struct HeartRateEntry: Hashable, Identifiable {
var heartRate: Double
var date: Date
var id = UUID()
}

class HeartHistoryModel: ObservableObject {

@Published var heartData: [HeartRateEntry] = []
var healthStore: HKHealthStore
var queryAnchor: HKQueryAnchor?
var query: HKAnchoredObjectQuery?

init() {
if HKHealthStore.isHealthDataAvailable() {
healthStore = HKHealthStore()
} else {
fatalError("Health data not available")

}

self.requestAuthorization { authorised in
if authorised {
self.setupQuery()
}
}
}

func requestAuthorization(completion: @escaping (Bool) -> Void){
let heartBeat = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!

self.healthStore.requestAuthorization(toShare: [], read: [heartBeat]) { (success, error) in completion(success)
}
}

func setupQuery() {
guard let sampleType = HKObjectType.quantityType(forIdentifier: .heartRate) else {
return
}

let startDate = Calendar.current.date(byAdding: .month, value: -1, to: Date())

let predicate = HKQuery.predicateForSamples(withStart: startDate, end: .distantFuture, options: .strictEndDate)

self.query = HKAnchoredObjectQuery(type: sampleType, predicate: predicate, anchor: queryAnchor, limit: HKObjectQueryNoLimit, resultsHandler: self.updateHandler)

self.query!.updateHandler = self.updateHandler

healthStore.execute(self.query!)
}

func updateHandler(query: HKAnchoredObjectQuery, newSamples: [HKSample]?, deleteSamples: [HKDeletedObject]?, newAnchor: HKQueryAnchor?, error: Error?) {
if let error = error {
print("Health query error \(error)")
} else {
let unit = HKUnit(from: "count/min")
if let newSamples = newSamples as? [HKQuantitySample], !newSamples.isEmpty {
print("Received \(newSamples.count) new samples")
DispatchQueue.main.async {

var currentData = self.heartData

currentData.append(contentsOf: newSamples.map { HeartRateEntry(heartRate: $0.quantity.doubleValue(for: unit), date: $0.startDate)
})

self.heartData = currentData.sorted(by: { $0.date > $1.date })
}
}

self.queryAnchor = newAnchor
}


}
}

How to get Heart Rate Data near by Real Time from Health Kit in iOS?

Here is my own analysis regarding get nearby real-time Heart Rate.

1. If you are accessing Health Kit data using iPhone app, In this scenario, Health Kit DB NOT frequently updated/refreshed. So, your app not able to get real-time latest updated data through iPhone app.

2. Using a watch app, you can access near real-time data through Health Kit DB. Watch app is able to get the real-time latest updated Health Kit Data.

3. You need to transfer data from watch to iPhone app. Here is a code for your reference. You can write code as per your requirement. You just need to access Heart Rate through HKQuery

let defaultSession = WCSession.default

let healthStore = HKHealthStore()

var currentHeartRateSample : [HKSample]?

var currentHeartLastSample : HKSample?

var currentHeartRateBPM = Double()

//Get Heart Rate from Health Kit

func getCurrentHeartRateData(){

let calendar = Calendar.current
let components = calendar.dateComponents([.year, .month, .day], from: Date())
let startDate : Date = calendar.date(from: components)!
let endDate : Date = calendar.date(byAdding: Calendar.Component.day, value: 1, to: startDate as Date)!

let sampleType : HKSampleType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!
let predicate : NSPredicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: [])
let anchor: HKQueryAnchor = HKQueryAnchor(fromValue: 0)

let anchoredQuery = HKAnchoredObjectQuery(type: sampleType, predicate: predicate, anchor: anchor, limit: HKObjectQueryNoLimit) { (query, samples, deletedObjects, anchor, error ) in

if samples != nil {

self.collectCurrentHeartRateSample(currentSampleTyple: samples!, deleted: deletedObjects!)

}

}

anchoredQuery.updateHandler = { (query, samples, deletedObjects, anchor, error) -> Void in
self.collectCurrentHeartRateSample(currentSampleTyple: samples!, deleted: deletedObjects!)
}

self.healthStore.execute(anchoredQuery)

}

//Retrived necessary parameter from HK Sample
func collectCurrentHeartRateSample(currentSampleTyple : [HKSample]?, deleted : [HKDeletedObject]?){

self.currentHeartRateSample = currentSampleTyple

//Get Last Sample of Heart Rate
self.currentHeartLastSample = self.currentHeartRateSample?.last

if self.currentHeartLastSample != nil {

let lastHeartRateSample = self.currentHeartLastSample as! HKQuantitySample

self.currentHeartRateBPM = lastHeartRateSample.quantity.doubleValue(for: HKUnit(from: "count/min"))
let heartRateStartDate = lastHeartRateSample.startDate
let heartRateEndDate = lastHeartRateSample.endDate

//Send Heart Rate Data Using Send Messge

DispatchQueue.main.async {

let message = [
"HeartRateBPM" : "\(self.currentHeartRateBPM)",
"HeartRateStartDate" : "\(heartRateStartDate)",
"HeartRateEndDate" : "\(heartRateEndDate)"
]

//Transfer data from watch to iPhone
self.defaultSession.sendMessage(message, replyHandler:nil, errorHandler: { (error) in
print("Error in send message : \(error)")
})

}

}

}


Related Topics



Leave a reply



Submit