Dispatch Group not notifying? [duplicate] - swift

This question already has answers here:
How to notify a queue in Swift (GCD)
(2 answers)
Closed 4 years ago.
I have this identical code in another app and it works without fail, now in this app the dispatch group is not notifying and my handler isn't getting called? I can't figure out the difference between the 2 apps since the code is identical?
func getHeartRateMaxFromWorkouts(workouts: [HKWorkout], handler: #escaping (careerMaxHeartRatePerWorkoutAsCustomHistoricalSample, careerAverageHeartRatePerWorkoutAsCustomHistoricalSample) -> Void) {
let workoutsReversed = workouts.reversed()
guard let heartRateType:HKQuantityType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate) else { return }
let heartRateUnit:HKUnit = HKUnit(from: "count/min")
var heartRateMaxArrayAsCustomHistoricalSample = [CustomHistoricalSample]()
var heartRateAvgArrayAsCustomHistoricalSample = [CustomHistoricalSample]()
//DispatchGroup needed since making async call per workout and need notified when all calls are done
let dispatchGroup = DispatchGroup()
for workout in workoutsReversed {
//predicate
let startDate = workout.startDate
let endDate = workout.endDate
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
//descriptor
let sortDescriptors = [
NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: true) //Changed this to false so that HRR and MCS would calculate, always check this if not getting these values
]
dispatchGroup.enter()
let heartRateQuery = HKSampleQuery(sampleType: heartRateType,
predicate: predicate,
limit: (HKObjectQueryNoLimit),
sortDescriptors: sortDescriptors)
{ (query:HKSampleQuery, results:[HKSample]?, error:Error?) -> Void in
guard error == nil else { print("get heart rate error"); return }
guard let unwrappedResults = results as? [HKQuantitySample] else { print("get heart rate error"); return}
let heartRatesAsDouble = unwrappedResults.map {$0.quantity.doubleValue(for: heartRateUnit)}
guard let max = heartRatesAsDouble.max() else { return }
let maxAsCustomHistoricalSample = CustomHistoricalSample(value: max, date: workout.startDate)
heartRateMaxArrayAsCustomHistoricalSample.append(maxAsCustomHistoricalSample)
let average = heartRatesAsDouble.average
let averageAsCustomHistoricalSample = CustomHistoricalSample(value: average, date: workout.startDate)
heartRateAvgArrayAsCustomHistoricalSample.append(averageAsCustomHistoricalSample)
dispatchGroup.leave()
}
healthStore.execute(heartRateQuery)
} //End of for workout loop
dispatchGroup.notify(queue: .main) {
//Need to sort by date since the dates come back jumbled
let sortedReversedHeartRateMaxArrayAsCustomHistoricalSampple = heartRateMaxArrayAsCustomHistoricalSample.sorted { $0.date > $1.date }.reversed() as [CustomHistoricalSample]
let sortedReversedHeartRateAverageArrayAsCustomHistoricalSampple = heartRateAvgArrayAsCustomHistoricalSample.sorted { $0.date > $1.date }.reversed() as [CustomHistoricalSample]
print("handler called = \(sortedReversedHeartRateMaxArrayAsCustomHistoricalSampple.count)")
handler(sortedReversedHeartRateMaxArrayAsCustomHistoricalSampple, sortedReversedHeartRateAverageArrayAsCustomHistoricalSampple)
}
} //End getHeartRateMaxFromWorkouts

It's a good practice to make leave top of callback
guard error == nil else { print("get heart rate error"); dispatchGroup.leave() return ; }
guard let unwrappedResults = results as? [HKQuantitySample] else { print("get heart rate error"); dispatchGroup.leave(); return}
let heartRatesAsDouble = unwrappedResults.map {$0.quantity.doubleValue(for: heartRateUnit)}
guard let max = heartRatesAsDouble.max() else { dispatchGroup.leave(); return }
let maxAsCustomHistoricalSample = CustomHistoricalSample(value: max, date: workout.startDate)
heartRateMaxArrayAsCustomHistoricalSample.append(maxAsCustomHistoricalSample)
let average = heartRatesAsDouble.average
let averageAsCustomHistoricalSample = CustomHistoricalSample(value: average, date: workout.startDate)
dispatchGroup.leave()
heartRateAvgArrayAsCustomHistoricalSample.append(averageAsCustomHistoricalSample)

Related

Swift function execution order

I'm trying to load sleep data from healthkit.
after retrieving some data, I converted it into Double type to use chart, and reflected them on setChart function.
But here's some problem.
To successfully show on chart, my function should executed in order.
Ideal order is : retrieve data, convert data -> setchart
but the order is not kept.
I read some articles about dispatchqueue, but still I can't solve the problem.
What should I do to solve this problem? \
override func viewDidLoad() {
super.viewDidLoad()
//선행작업
DispatchQueue.global(qos: .userInteractive).sync {
print("1")
self.retrieveSleepAnalysis()
}
DispatchQueue.global(qos: .default).sync {
print("2")
self.recentSleepStartHourDouble = self.stringTimeToDouble(stringTime: self.recentSleepStartHour)
self.wakeUp = self.wakeupTimeCalcuation(start: self.recentSleepStartHourDouble, end: self.resultToSleepAmount)
}
DispatchQueue.main.async {
// self.recentSleepStartHourDouble = self.stringTimeToDouble(stringTime: self.recentSleepStartHour)
// self.wakeUp = self.wakeupTimeCalcuation(start: self.recentSleepStartHourDouble, end: self.resultToSleepAmount)
self.setChart(days: self.recentSleepDate.reversed(), sleepTimes: self.recentSleepStartHourDouble.reversed(), wakeupTimes:
public func retrieveSleepAnalysis() {
// first, we define the object type we want
if let sleepType = HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis) {
// print(sleepType)
// Use a sortDescriptor to get the recent data first
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
// we create our query with a block completion to execute
let query = HKSampleQuery(sampleType: sleepType, predicate: nil, limit: 7, sortDescriptors: [sortDescriptor]) { (query, tmpResult, error) -> Void in
if error != nil {
// something happened
return
}
if let result = tmpResult {
for item in result {
if let sample = item as? HKCategorySample {
let value = (sample.value == HKCategoryValueSleepAnalysis.inBed.rawValue) ? "InBed" : "Asleep"
print("Healthkit sleep: \(sample.startDate) \(sample.endDate) - value: \(value)")
let sleepHour = Calendar.current.component(.hour, from: sample.startDate)
print("현지시각으로 잠든시간 \(sleepHour)")
if sleepHour < 19 && sleepHour > 12{
print("낮잠")
}
else{
self.resultToSleepAmount.append(CFDateGetTimeIntervalSinceDate(sample.endDate as CFDate, sample.startDate as CFDate)/3600)
let myDateFormatter = DateFormatter()
myDateFormatter.dateFormat = "MM / dd"
myDateFormatter.locale = Locale(identifier: "ko_KR")
let sleepDate = myDateFormatter.string(from: sample.startDate)
self.recentSleepDate.append(sleepDate)
let myHourFormatter = DateFormatter()
myHourFormatter.dateFormat = "HHmm"
myHourFormatter.locale = Locale(identifier: "ko_KR")
let sleepStartHour = myHourFormatter.string(from: sample.startDate)
self.recentSleepStartHour.append(sleepStartHour)
print("잠든 시간은 몇시몇분? \(sleepStartHour)")
let sleepEndHour = myHourFormatter.string(from: sample.endDate)
self.recentSleepEndHour.append(sleepEndHour)
}
}
print("total amount of sleep time : \(self.resultToSleepAmount), 현재 크기는 : \(self.resultToSleepAmount.count)")
}
}
}
// finally, we execute our query
healthStore.execute(query)
}

Swift HealthKit HKStatisticsCollectionQuery statisticsUpdateHandler not always called

I have a test project where I get the total number of falls for a user for each day over the course of the week. The initialResultsHandler works perfectly every time, however the statisticsUpdateHandler doesn't always fire off. If you start the app, then go to the health app and insert falls manually, switch back to the test app you should see the total for today update. In reality this works for about the first 3-6 times. After that the statisticsUpdateHandler doesn't get called anymore.
What's also odd is that if you delete data and then go back to the test app, or add data from a time earlier than now, the statisticsUpdateHandler gets called. This leads me to think that it has something to do with the statisticsUpdateHandler end date.
Apples documentation is pretty clear however I’m afraid they might be leaving something out.
If this property is set to nil, the statistics collection query will automatically stop as soon as it has finished calculating the initial results. If this property is not nil, the query behaves similarly to the observer query. It continues to run, monitoring the HealthKit store. If any new, matching samples are saved to the store—or if any of the existing matching samples are deleted from the store—the query executes the update handler on a background queue.
Is there any reason that statisticsUpdateHandler might not be called? I have included a test project below.
struct Falls: Identifiable{
let id = UUID()
let date: Date
let value: Int
var formattedDate: String{
let formatter = DateFormatter()
formatter.setLocalizedDateFormatFromTemplate("MM/dd/yyyy")
return formatter.string(from: date)
}
}
struct ContentView: View {
#StateObject var manager = HealthKitManager()
var body: some View {
NavigationView{
List{
Text("Updates: \(manager.updates)")
ForEach(manager.falls){ falls in
HStack{
Text(falls.value.description)
Text(falls.formattedDate)
}
}
}
.overlay(
ProgressView()
.scaleEffect(1.5)
.opacity(manager.isLoading ? 1 : 0)
)
.navigationTitle("Falls")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
class HealthKitManager: ObservableObject{
let healthStore = HKHealthStore()
let fallType = HKQuantityType.quantityType(forIdentifier: .numberOfTimesFallen)!
#Published var isLoading = false
#Published var falls = [Falls]()
#Published var updates = 0
init() {
let healthKitTypesToRead: Set<HKSampleType> = [fallType]
healthStore.requestAuthorization(toShare: nil, read: healthKitTypesToRead) { (success, error) in
if let error = error{
print("Error: \(error)")
} else if success{
self.startQuery()
}
}
}
func startQuery(){
let now = Date()
let cal = Calendar.current
let sevenDaysAgo = cal.date(byAdding: .day, value: -7, to: now)!
let startDate = cal.startOfDay(for: sevenDaysAgo)
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: now, options: [.strictStartDate, .strictEndDate])
var interval = DateComponents()
interval.day = 1
// start from midnight
let anchorDate = cal.startOfDay(for: now)
let query = HKStatisticsCollectionQuery(
quantityType: fallType,
quantitySamplePredicate: predicate,
options: .cumulativeSum,
anchorDate: anchorDate,
intervalComponents: interval
)
query.initialResultsHandler = { query, collection, error in
guard let collection = collection else {
print("No collection")
DispatchQueue.main.async{
self.isLoading = false
}
return
}
collection.enumerateStatistics(from: startDate, to: Date()){ (result, stop) in
guard let sumQuantity = result.sumQuantity() else {
return
}
let totalFallsForADay = Int(sumQuantity.doubleValue(for: .count()))
let falls = Falls(date: result.startDate, value: totalFallsForADay)
print(falls.value, falls.formattedDate)
DispatchQueue.main.async{
self.falls.insert(falls, at: 0)
}
}
print("initialResultsHandler done")
DispatchQueue.main.async{
self.isLoading = false
}
}
query.statisticsUpdateHandler = { query, statistics, collection, error in
print("In statisticsUpdateHandler...")
guard let collection = collection else {
print("No collection")
DispatchQueue.main.async{
self.isLoading = false
}
return
}
DispatchQueue.main.async{
self.isLoading = true
self.updates += 1
self.falls.removeAll(keepingCapacity: true)
}
collection.enumerateStatistics(from: startDate, to: Date()){ (result, stop) in
guard let sumQuantity = result.sumQuantity() else {
return
}
let totalFallsForADay = Int(sumQuantity.doubleValue(for: .count()))
let falls = Falls(date: result.startDate, value: totalFallsForADay)
print(falls.value, falls.formattedDate)
print("\n\n")
DispatchQueue.main.async{
self.falls.insert(falls, at: 0)
}
}
print("statisticsUpdateHandler done")
DispatchQueue.main.async{
self.isLoading = false
}
}
isLoading = true
healthStore.execute(query)
}
}
I was so focused on the statisticsUpdateHandler and the start and end time that I didn't pay attention to the query itself. It turns out that the predicate was the issue. By giving it an end date, it was never looking for samples outside the the initial predicate end date.
Changing the predicate to this solved the issue:
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: nil, options: [.strictStartDate])

Step Count always "0" using healthKit in swift

I am trying to get last 7 days step count but it is always coming zero. But when I open the health app in iPhone then it is more than 3000 steps. Even I also added property Privacy - Health Share Usage Description and Privacy - Health Update Usage Description in .plist file.
Here is my code
var healthScore = HKHealthStore()
override func viewDidLoad() {
super.viewDidLoad()
// Access Step Count
let healthKitTypes: Set = [ HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)! ]
// Check for Authorization
healthScore.requestAuthorization(toShare: healthKitTypes, read: healthKitTypes) { (bool, error) in
if (bool) {
// Authorization Successful
self.getSteps { (result) in
DispatchQueue.main.async {
let stepCount = String(Int(result))
self.stepLbl.text = String(stepCount)
}
}
}
}
}
func getSteps(completion: #escaping (Double) -> Void){
let stepsQuantityType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
let now = Date()
let exactlySevenDaysAgo = Calendar.current.date(byAdding: DateComponents(day: -7), to: now)!
let predicate = HKQuery.predicateForSamples(withStart: exactlySevenDaysAgo, 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 {
print("\(String(describing: error?.localizedDescription)) ")
completion(resultCount)
return
}
if let sum = result.sumQuantity() {
resultCount = sum.doubleValue(for: HKUnit.count())
}
DispatchQueue.main.async {
completion(resultCount)
}
}
healthScore.execute(query)
}
In console I checked the dates are also correct now here's the screenshot

How do I get this the currentStandHour value in Apple Watch iOS?

I want to retrieve the value that indicates whether or not the user has stood this hour. I also want to be able to retrieve the StandHours count for the day.
Here are the Apple links that I've been trying to understand in order get the value from HealthKit. I provide these links to help provide understanding for what I'm looking for and also to help you answer my question.
appleStandHour type property: https://developer.apple.com/documentation/healthkit/hkcategorytypeidentifier/1615539-applestandhour
HealthKit category type identifier: https://developer.apple.com/documentation/healthkit/hkcategorytypeidentifier
HealthKit constants: https://developer.apple.com/documentation/healthkit/healthkit_constants
Bruno's answer is only half of the answer. For example, his standUnit variable is how he pulls the # of hours that the user has stood today. I tested it. Also, I made the assumption that it had to be pulled from within the scope of the summaries variable.
I have found another question on StackOverflow that might provide some clues. I think they managed to pull a value via a HKCategoryTypeIdentifier: Watch os 2.0 beta: access heart beat rate
Here's my attempted code as far as I have been able to get:
import UIKit
import HealthKit
import HealthKitUI
class ViewController: UIViewController {
let hkStoreOnVC : HKHealthStore = HKHealthStore()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
authorizeHealthKit()
hkTest()
hkTest2()
}
func authorizeHealthKit() { //-> Bool {
print("health kit authorize?")
let healthStore = HKHealthStore()
let objectTypes: Set<HKObjectType> = [
HKObjectType.activitySummaryType()
]
healthStore.requestAuthorization(toShare: nil, read: objectTypes) { (success, error) in
// Authorization request finished, hopefully the user allowed access!
print("health kit authorized")
}
}
func hkTest() {
print("health kit test.")
let calendar = Calendar.autoupdatingCurrent
var dateComponents = calendar.dateComponents(
[ .year, .month, .day ],
from: Date()
)
// This line is required to make the whole thing work
dateComponents.calendar = calendar
let predicate = HKQuery.predicateForActivitySummary(with: dateComponents)
//----------------------
let query = HKActivitySummaryQuery(predicate: predicate) { (query, summaries, error) in
print("query")
guard let summaries = summaries, summaries.count > 0
else {
print("no summaries")
return
}
// Handle data
for thisSummary in summaries {
// print("for each summary")
let standUnit = HKUnit.count()
let standHours = thisSummary.appleStandHours.doubleValue(for: standUnit)
print("stand hours \(standHours)")
}//end for
} //end query
}
func hkTest2() {
var isEnabled = true
print ("authorize health kit" )
if HKHealthStore.isHealthDataAvailable() {
let stepsCount = NSSet(objects: HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount ) )
for thisValue in stepsCount {
// thisValue.
print("thisValue: \(thisValue)")
}
print(" authorize HK - steps count \(stepsCount) ")
}
// Create the date components for the predicate
guard let calendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian) else {
fatalError("*** This should never fail. ***")
}
let endDate = NSDate()
guard let startDate = calendar.date(byAdding: .day, value: -7, to: endDate as Date, options: []) else {
fatalError("*** unable to calculate the start date ***")
}
let units: NSCalendar.Unit = [.day, .month, .year, .era]
var startDateComponents = calendar.components(units, from: startDate)
startDateComponents.calendar = calendar as Calendar
var endDateComponents = calendar.components(units, from: endDate as Date)
endDateComponents.calendar = calendar as Calendar
// Create the predicate for the query
let summariesWithinRange = HKQuery.predicate(forActivitySummariesBetweenStart: startDateComponents, end: endDateComponents)
// Build the query
let query = HKActivitySummaryQuery(predicate: summariesWithinRange) { (query, summaries, error) -> Void in
guard let activitySummaries = summaries else {
guard let queryError = error else {
fatalError("*** Did not return a valid error object. ***")
}
// Handle the error here...
return
}
for thisSummary in activitySummaries {
// print("for each summary")
let standUnit = HKUnit.count()
let standHours = thisSummary.appleStandHours.doubleValue(for: standUnit)
// let stoodThisHourMaybe = thisSummary.appleStandHours.categ //doubleValue(for: standUnit)
//\(thisSummary.description) //stand unit _\(standUnit)_
print("day#\(thisSummary.dateComponents(for: calendar as Calendar).day) stand hours \(standHours) ")
}//end for
// Do something with the summaries here...
for thisItem in activitySummaries {
//thisItem.appleStandHours
print("abc \( thisItem.appleStandHours ) " )
}//end for
}
// Run the query
let hkStore : HKHealthStore = HKHealthStore()
hkStore.execute(query)
//***
let aStandHour = HKCategoryType.categoryType(forIdentifier: .appleStandHour)
// This is the type you want updates on. It can be any health kit type, including heart rate.
// let distanceType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.distanceWalkingRunning)
// Match samples with a start date after the workout start
// let predicate = .predicat //( , endDate: nil, options: .None)
// let theDate : Date =
let thepredicate = HKQuery.predicateForCategorySamples(with: .greaterThanOrEqualTo, value: 0) //.predicateForSamplesWithStartDate(startDate , endDate: nil, options: .None)
// predicate
// let predicate = . //(theDate , endDate: nil, options: .None)
let hka : HKQueryAnchor = HKQueryAnchor(fromValue: 0)
let sHourQuery = HKAnchoredObjectQuery(type: aStandHour!, predicate: thepredicate, anchor: hka, limit: 0, resultsHandler: { ( query, samples, deletedObjects, anchor, error) -> Void in
// Handle when the query first returns results
// TODO: do whatever you want with samples (note you are not on the main thread)
print("getting here A?")
// for thisSample in samples! {
// print("A smpLType \(thisSample.sampleType) thisSample \(thisSample)")
// }
})
// This is called each time a new value is entered into HealthKit (samples may be batched together for efficiency)
sHourQuery.updateHandler = { (query, samples, deletedObjects, anchor, error) -> Void in
// Handle update notifications after the query has initially run
// TODO: do whatever you want with samples (note you are not on the main thread)
print("getting here B?")
for thisSample in samples! {
print("B smpLType \(thisSample.sampleType) thisSample \(thisSample)")
}
}
// Start the query
self.hkStoreOnVC.execute(sHourQuery)
//***
}//end func
func myCompletionHandler(bTest: Bool ) {
print("my completion handler")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}//end viewController Class
Here's the code output - the log never prints to "getting here b?":
health kit authorize?
health kit test.
authorize health kit
health kit authorized
thisValue: HKQuantityTypeIdentifierStepCount
authorize HK - steps count {(
HKQuantityTypeIdentifierStepCount
)}
2017-11-04 19:18:30.100562-0500 watchapptest[25048:4695625] refreshPreferences: HangTracerEnabled: 0
2017-11-04 19:18:30.100600-0500 watchapptest[25048:4695625] refreshPreferences: HangTracerDuration: 500
2017-11-04 19:18:30.100615-0500 watchapptest[25048:4695625] refreshPreferences: ActivationLoggingEnabled: 0 ActivationLoggingTaskedOffByDA:0
getting here A?
day#Optional(28) stand hours 14.0
day#Optional(29) stand hours 14.0
day#Optional(30) stand hours 14.0
day#Optional(31) stand hours 14.0
day#Optional(1) stand hours 16.0
day#Optional(2) stand hours 13.0
day#Optional(3) stand hours 15.0
day#Optional(4) stand hours 13.0
abc 14 count
abc 14 count
abc 14 count
abc 14 count
abc 16 count
abc 13 count
abc 15 count
abc 13 count
I am new to HealthKit, so there probably is a nicer way to do this. But this seems to work for me. I check the actually standing minutes and call the completion handler with minutes > 0.
private let store = HKHealthStore()
func askPermission() {
let standType = HKQuantityType.quantityType(forIdentifier: .appleStandTime)!
store.requestAuthorization(toShare: [], read: [standType], completion: { (success, error) in
self.didStandThisHour { (didStand) in
print("Did stand this hour: \(didStand)")
}
})
}
func didStandThisHour(_ didStand: #escaping (Bool) -> ()) {
let store = HKHealthStore()
let calendar = Calendar.autoupdatingCurrent
let dateComponents = calendar.dateComponents([.year, .month, .day, .hour], from: Date())
let endDate = Date()
let startDate = calendar.date(from: dateComponents)!
let standTime = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.appleStandTime)!
var interval = DateComponents()
interval.hour = 1
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
let query = HKStatisticsCollectionQuery(quantityType: standTime, quantitySamplePredicate: predicate, options: [.cumulativeSum], anchorDate: startDate, intervalComponents:interval)
query.initialResultsHandler = { query, results, error in
guard error == nil, let myResults = results else {
fatalError("Something is wrong with HealthKit link")
}
myResults.enumerateStatistics(from: startDate, to: endDate, with: { (statistics, stop) in
guard let quantity = statistics.sumQuantity() else {
didStand(false)
return
}
let minutes = quantity.doubleValue(for: .minute())
didStand(minutes > 0)
})
}
store.execute(query)
}
Ok, if you want to retrieve today's activity ring info (including stand hours) you first request user authorization for the object type you want to retrieve:
let healthStore = HKHealthStore()
let objectTypes: Set<HKObjectType> = [
HKObjectType.activitySummaryType()
]
healthStore.requestAuthorization(toShare: nil, read: objectTypes) { (success, error) in
// Authorization request finished, hopefully the user allowed access!
}
Then you can use this predicate to retrieve today's date:
let calendar = Calendar.autoupdatingCurrent
var dateComponents = calendar.dateComponents(
[ .year, .month, .day ],
from: Date()
)
// This line is required to make the whole thing work
dateComponents.calendar = calendar
let predicate = HKQuery.predicateForActivitySummary(with: dateComponents)
Create a query...
let query = HKActivitySummaryQuery(predicate: predicate) { (query, summaries, error) in
guard let summaries = summaries, summaries.count > 0
else {
return
}
// Handle data
}
The data you'll receive is of type HKActivitySummary and you can retrieve, for example:
let sandUnit = HKUnit.count()
let standHours = summary.appleStandHours.doubleValue(for: standUnit)

How to query Healthkit for average heart rate with Swift

I need to query HKStatistics for average heart rate, with Swift 2.2. I've learned from research the parameter I need is HKStatisticsOptionDiscreteAverage
I have code for the workout session. How can I add the function to this code below measuring heart-rate to return the heart rate average of the workout session with HKStatisticsOptionDiscreteAverage?
func createHeartRateStreamingQuery(workoutStartDate: NSDate) -> HKQuery? {
// adding predicate will not work
// let predicate = HKQuery.predicateForSamplesWithStartDate(workoutStartDate, endDate: nil, options: HKQueryOptions.None)
guard let quantityType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate) else { return nil }
let heartRateQuery = HKAnchoredObjectQuery(type: quantityType, predicate: nil, anchor: anchor, limit: Int(HKObjectQueryNoLimit)) { (query, sampleObjects, deletedObjects, newAnchor, error) -> Void in
guard let newAnchor = newAnchor else {return}
self.anchor = newAnchor
self.updateHeartRate(sampleObjects)
}
heartRateQuery.updateHandler = {(query, samples, deleteObjects, newAnchor, error) -> Void in
self.anchor = newAnchor!
self.updateHeartRate(samples)
}
return heartRateQuery
}
func updateHeartRate(samples: [HKSample]?) {
guard let heartRateSamples = samples as? [HKQuantitySample] else {return}
dispatch_async(dispatch_get_main_queue()) {
guard let sample = heartRateSamples.first else{return}
let value = sample.quantity.doubleValueForUnit(self.heartRateUnit)
self.label.setText(String(UInt16(value)))
// retrieve source from sample
let name = sample.sourceRevision.source.name
self.updateDeviceName(name)
self.animateHeart()
}
}
func getAVGHeartRate() {
var typeHeart = HKQuantityType.quantityType(forIdentifier: .heartRate)
var startDate = Date() - 7 * 24 * 60 * 60 // start date is a week
var predicate: NSPredicate? = HKQuery.predicateForSamples(withStart: startDate, end: Date(), options: HKQueryOptions.strictEndDate)
var squery = HKStatisticsQuery(quantityType: typeHeart!, quantitySamplePredicate: predicate, options: .discreteAverage, completionHandler: {(query: HKStatisticsQuery,result: HKStatistics?, error: Error?) -> Void in
DispatchQueue.main.async(execute: {() -> Void in
var quantity: HKQuantity? = result?.averageQuantity()
var beats: Double? = quantity?.doubleValue(for: HKUnit.count().unitDivided(by: HKUnit.minute()))
print("got: \(String(format: "%.f", beats!))")
})
})
healthStore.execute(squery)
}
This is the Swift 3 version :)
this is objective-c example of getting the average heart BPM:
HKQuantityType *typeHeart =[HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:endDate options:HKQueryOptionStrictStartDate];
HKStatisticsQuery *squery = [[HKStatisticsQuery alloc] initWithQuantityType:typeHeart quantitySamplePredicate:predicate options:HKStatisticsOptionDiscreteAverage completionHandler:^(HKStatisticsQuery *query, HKStatistics *result, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
HKQuantity *quantity = result.averageQuantity;
double beats = [quantity doubleValueForUnit:[[HKUnit countUnit] unitDividedByUnit:[HKUnit minuteUnit]]];
NSLog(#"got: %#", [NSString stringWithFormat:#"%.f",beats]) ;
}
);
}];
[self.healthStore executeQuery:squery];