How to force a HKQuery to load the most recent steps counts?
HealthKit does not always have an up-to-date count of the user's steps and distance travelled. It imports these values, which actually come from CoreMotion.framework, periodically and in response to certain events. If a running application has an open HKObserverQuery
or HKStatisticsCollectionQuery
then HealthKit will stream the values to the client but otherwise the samples are just a snapshot from the last import.
So if you'd like to observe changes for a sample type, you should subscribe to updates using an HKObserverQuery
and then query HealthKit again for the latest values. A more efficient approach would be to use HKStatisticsCollectionQuery
, though, which has an update handler that will be invoked as the statistics for the samples matching the predicates change.
Finally, if you're only interested up-to-date step counts or distance travelled for at most the past 7 days then I recommend that you consider using CoreMotion.framework directly instead.
HealthKit Swift getting today's steps
Here is the right way using HKStatisticsCollectionQuery courtesy of the direction from the code above.
This is written in Swift 3 so you may have to convert some of the code back to 2.3 or 2 if not on 3 yet.
Swift 3
func retrieveStepCount(completion: (stepRetrieved: Double) -> Void) {
// Define the Step Quantity Type
let stepsCount = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)
// Get the start of the day
let date = Date()
let cal = Calendar(identifier: Calendar.Identifier.gregorian)
let newDate = cal.startOfDay(for: date)
// Set the Predicates & Interval
let predicate = HKQuery.predicateForSamples(withStart: newDate, end: Date(), options: .strictStartDate)
var interval = DateComponents()
interval.day = 1
// Perform the Query
let query = HKStatisticsCollectionQuery(quantityType: stepsCount!, quantitySamplePredicate: predicate, options: [.cumulativeSum], anchorDate: newDate as Date, intervalComponents:interval)
query.initialResultsHandler = { query, results, error in
if error != nil {
// Something went Wrong
return
}
if let myResults = results{
myResults.enumerateStatistics(from: self.yesterday, to: self.today) {
statistics, stop in
if let quantity = statistics.sumQuantity() {
let steps = quantity.doubleValue(for: HKUnit.count())
print("Steps = \(steps)")
completion(stepRetrieved: steps)
}
}
}
}
storage.execute(query)
}
Objective-C
HKQuantityType *type = [HKSampleType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
NSDate *today = [NSDate date];
NSDate *startOfDay = [[NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian] startOfDayForDate:today];
NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:startOfDay endDate:today options:HKQueryOptionStrictStartDate];
NSDateComponents *interval = [[NSDateComponents alloc] init];
interval.day = 1;
HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:type quantitySamplePredicate:predicate options:HKStatisticsOptionCumulativeSum anchorDate:startOfDay intervalComponents:interval];
query.initialResultsHandler = ^(HKStatisticsCollectionQuery * _Nonnull query, HKStatisticsCollection * _Nullable result, NSError * _Nullable error) {
if (error != nil) {
// TODO
} else {
[result enumerateStatisticsFromDate:startOfDay toDate:today withBlock:^(HKStatistics * _Nonnull result, BOOL * _Nonnull stop) {
HKQuantity *quantity = [result sumQuantity];
double steps = [quantity doubleValueForUnit:[HKUnit countUnit]];
NSLog(@"Steps : %f", steps);
}];
}
};
[self.storage executeQuery:query];
Get the latest (most recent) Step value from HealthKit (HKStatistics)
If you want to use just quantities, etc. Just use the HKSampleQuery
instead of the HKStatisticsQuery
.
After that, you just have to cast the result list of HKSample
to a [HKQuantitySample]
.
Now you have the start and end date but also the quantity
.
Getting all steps of today but truncating manually added steps, from Health kit using swift
This might not be the best solution, But I believe it will work. What you can do is get all the steps which were added manually using HKSampleQuery. here is an example.
func todayManuallyAddedStepsSteps(completion: (Double, NSError?) -> () )
{
let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount) // The type of data we are requesting
let date = NSDate()
let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let newDate = cal.startOfDayForDate(date)
let predicate = HKQuery.predicateForSamplesWithStartDate(newDate, endDate: NSDate(), options: .None) // Our search predicate which will fetch all steps taken today
// The actual HealthKit Query which will fetch all of the steps and add them up for us.
let query = HKSampleQuery(sampleType: type!, predicate: predicate, limit: 0, sortDescriptors: nil) { query, results, error in
var steps: Double = 0
if results?.count > 0
{
for result in results as! [HKQuantitySample]
{
print("Steps \(result.quantity.doubleValueForUnit(HKUnit.countUnit()))")
print()
// checking and adding manually added steps
if result.sourceRevision.source.name == "Health" {
// these are manually added steps
print(result.sourceRevision.source.name)
print("Steps \(result.quantity.doubleValueForUnit(HKUnit.countUnit()))")
steps += result.quantity.doubleValueForUnit(HKUnit.countUnit())
}
else{
// these are auto detected steps which we do not want from using HKSampleQuery
}
}
print(steps)
}
completion(steps, error)
}
executeQuery(query)
}
and then get the today total steps using HKStatisticsCollectionQuery like below
func TodayTotalSteps(completion: (stepRetrieved: Double) -> Void) {
let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount) // The type of data we are requesting
let calendar = NSCalendar.currentCalendar()
let interval = NSDateComponents()
interval.day = 1
let anchorComponents = calendar.components([.Day , .Month , .Year], fromDate: NSDate())
anchorComponents.hour = 0
let anchorDate = calendar.dateFromComponents(anchorComponents)
let stepsQuery = HKStatisticsCollectionQuery(quantityType: type!, quantitySamplePredicate: nil, options: .CumulativeSum, anchorDate: anchorDate!, intervalComponents: interval)
stepsQuery.initialResultsHandler = {query, results, error in
let endDate = NSDate()
let startDate = calendar.dateByAddingUnit(.Day, value: 0, toDate: endDate, options: [])
if let myResults = results{ myResults.enumerateStatisticsFromDate(startDate!, toDate: endDate) { statistics, stop in
if let quantity = statistics.sumQuantity(){
let date = statistics.startDate
let steps = quantity.doubleValueForUnit(HKUnit.countUnit())
print("\(date): steps = \(steps)")
completion(stepRetrieved: steps)
}
}
}
}
executeQuery(stepsQuery)
}
Now you can call these methods and subtract manually added steps like below
todayManuallyAddedSteps({ (steps , error) in
if error != nil{
print(error)
}
else{
// truncating manuall steps
TodayTotalSteps({ (stepRetrieved) in
// steps without manuall steps
print(Int(stepRetrieved - steps))
})
}
})
Getting yesterdays steps from HealthKit
This is the method I am using in my healthStore class
func TodayTotalSteps(completion: (stepRetrieved: Double) -> Void) {
let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount) // The type of data we are requesting
let calendar = NSCalendar.currentCalendar()
let interval = NSDateComponents()
interval.day = 1
let anchorComponents = calendar.components([.Day , .Month , .Year], fromDate: NSDate())
anchorComponents.hour = 0
let anchorDate = calendar.dateFromComponents(anchorComponents)
let stepsQuery = HKStatisticsCollectionQuery(quantityType: type!, quantitySamplePredicate: nil, options: .CumulativeSum, anchorDate: anchorDate!, intervalComponents: interval)
stepsQuery.initialResultsHandler = {query, results, error in
let endDate = NSDate()
var steps = 0.0
let startDate = calendar.dateByAddingUnit(.Day, value: 0, toDate: endDate, options: [])
if let myResults = results{ myResults.enumerateStatisticsFromDate(startDate!, toDate: endDate) { statistics, stop in
if let quantity = statistics.sumQuantity(){
let date = statistics.startDate
steps = quantity.doubleValueForUnit(HKUnit.countUnit())
// print("\(date): steps = \(steps)")
}
completion(stepRetrieved: steps)
}
} else {
completion(stepRetrieved: steps)
}
}
executeQuery(stepsQuery)
}
and here is How I am using it
func getStepsData() {
// I am sendng steps to my server thats why using this variable
var stepsToSend = 0
MyHealthStore.sharedHealthStore.todayManuallyAddedSteps({ (steps , error) in
if error != nil{
// handle error
}
else{
// truncating manuall steps
MyHealthStore.sharedHealthStore.TodayTotalSteps({ (stepRetrieved) in
stepsToSend = Int(stepRetrieved - steps)
})
}
})
}
and here is the function used above for manually added steps which we are truncating in order to get exact steps
func todayManuallyAddedSteps(completion: (Double, NSError?) -> () )
{
let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount) // The type of data we are requesting
let date = NSDate()
let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let newDate = cal.startOfDayForDate(date)
let predicate = HKQuery.predicateForSamplesWithStartDate(newDate, endDate: NSDate(), options: .None) // Our search predicate which will fetch all steps taken today
// The actual HealthKit Query which will fetch all of the steps and add them up for us.
let query = HKSampleQuery(sampleType: type!, predicate: predicate, limit: 0, sortDescriptors: nil) { query, results, error in
var steps: Double = 0
if results?.count > 0
{
for result in results as! [HKQuantitySample]
{
// checking and adding manually added steps
if result.sourceRevision.source.name == "Health" {
// these are manually added steps
steps += result.quantity.doubleValueForUnit(HKUnit.countUnit())
}
else{
// these are auto detected steps which we do not want from using HKSampleQuery
}
}
completion(steps, error)
} else {
completion(steps, error)
}
}
executeQuery(query)
}
I hope it helps. Let me know if you face any issue.
How to obtain Health data from the last 7 days
Try using HKStatisticsCollectionQuery
, which will do the date math for you and bucket the results automatically. Here's an example that should provide the step counts for the last 7 days:
let stepsQuantityType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
let now = Date()
let exactlySevenDaysAgo = Calendar.current.date(byAdding: DateComponents(day: -7), to: now)!
let startOfSevenDaysAgo = Calendar.current.startOfDay(for: exactlySevenDaysAgo)
let predicate = HKQuery.predicateForSamples(withStart: startOfSevenDaysAgo, end: now, options: .strictStartDate)
let query = HKStatisticsCollectionQuery.init(quantityType: stepsQuantityType,
quantitySamplePredicate: predicate,
options: .cumulativeSum,
anchorDate: startOfSevenDaysAgo,
intervalComponents: DateComponents(day: 1))
query.initialResultsHandler = { query, results, error in
guard let statsCollection = results else {
// Perform proper error handling here...
}
statsCollection.enumerateStatistics(from: startOfSevenDaysAgo, to: now) { statistics, stop in
if let quantity = statistics.sumQuantity() {
let stepValue = quantity.doubleValueForUnit(HKUnit.countUnit())
// ...
}
}
}
HealthKit not returning updated info
You are displaying only the quantity of the last sample.
Lets assume you have 100 samples.
You're updating your label 100 times with a different quantity value and what you are seeing is the quantity value of the last update.
To see the sum of all selected samples, you have to sum up all quantity values of all (relevant) samples.
Related Topics
Add "Edit in Excel" or "Edit Photo" Extension
How to Use Presentmodalviewcontroller to Create a Transparent View
How to Create a Simple Checkbox in iOS
iPhone Is Not Available. Please Reconnect the Device
Minimum and Maximum Date in Uidatepicker
Xcode - Error Creating Lldb Target
Change Button Background Color Using Swift Language
How to Go from Skscene to Uiviewcontroller by Code
How to Debug "Terminated Due to Memory Error"
Swiftui Datepicker Binding Optional Date, Valid Nil
iOS Autolayout with Uiscrollview: Why Does Content View of Scroll View Not Fill the Scroll View
Swift Performselector:Withobject:Afterdelay: Is Unavailable
How to Restrict Special Characters in Uitextfield Except Dot and Underscores