I am trying to get the average heart rate per day from a user's device. I have the code below which prints "avgHeart" when called, but doesn't do anything else.
I am testing it on my own phone which definitely has heart rate data and has been authorised to read heart rate data.
func fortnightAvgHeartRate() {
print("avgHEART")
let calendar = Calendar.current
var interval = DateComponents()
// 14-day time interval
interval.day = 1
// Set the anchor date to Monday at 3:00 a.m.
let anchorDateTMP = calendar.date(byAdding: .day, value: -14, to: Date())!
let anchorDate = calendar.date(bySettingHour: 0, minute: 0, second: 0, of: anchorDateTMP)!
guard let quantityType = HKQuantityType.quantityType(forIdentifier: .heartRate) else {
fatalError("*** Unable to create a step count type ***")
}
// Create the query
let query = HKStatisticsCollectionQuery(quantityType: quantityType,
quantitySamplePredicate: nil,
options: .discreteAverage,
anchorDate: anchorDate,
intervalComponents: interval)
// Set the results handler
query.initialResultsHandler = {
query, results, error in
guard let statsCollection = results else {
// Perform proper error handling here
fatalError("*** An error occurred while calculating the statistics: \(error?.localizedDescription) ***")
}
let endDate = Date()
guard let startDate = calendar.date(byAdding: .day, value: -14, to: endDate) else {
fatalError("*** Unable to calculate the start date ***")
}
// Plot the weekly step counts over the past 3 months
statsCollection.enumerateStatistics(from: startDate, to: endDate) { [unowned self] statistics, stop in
if let quantity = statistics.sumQuantity() {
let date = statistics.startDate
let value = quantity.doubleValue(for: HKUnit.count())
let formatter = DateFormatter()
formatter.dateFormat = "ddMMMyyyy"
let dateResult = formatter.string(from: date)
print("HEART")
self.addToArray(date: dateResult, value: value, number: 5)
}
}
}
HKHealthStore().execute(query)
}
The called function is as follows:
var dateArray: [String] = []
var avgHeartRateDataArray: [Double] = []
func addToArray(date: String, value: Double, number: Double) {
// ...
if number == 5 {
avgHeartRateDataArray.append(value)
print("avgHeartRate: \(avgHeartRateDataArray)")
}
if dateArray.contains(date) {
// Do nothing
} else {
dateArray.append(date)
}
print("ARRAY1: \(dateArray)")
}
And in this, print("avgHeartRate: \(avgHeartRateDataArray)")
is not called at all.
Ideally I'd like the avgHeartRateData
array to be populated with the average heart rate for the past 14 days.
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
.