Why HKSample array always have 1 value for a HKAnchoredObjectQuery with no limits,no predicate, no anchor? - watchos-2

I am trying to understand how HKAnchoredObjectQuery works. Once the workout started and workout session state changes to running, I call the following function to execute the query and get the Heart Beat Value.
func createHeartRateStreamingQuery() {
guard let quantityType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate) else { return nil }
var heartRateQuery : HKAnchoredObjectQuery? = HKAnchoredObjectQuery(type: quantityType, predicate: nil, anchor: nil, limit: Int(HKObjectQueryNoLimit)) { (query, sampleObjects, deletedObjects, newAnchor, error) -> Void in
}
heartRateQuery!.updateHandler = {(query, samples, deleteObjects, newAnchor, error) -> Void in
{
//Samples only have 1 entry which is the most recent reading.
}
}
self.healthStore.executeQuery(heartRateQuery!)
}
HeartRateQuery's update handler is called every 2 to 3 seconds and samples variable in the completion handler is having only 1 reading of the Heart Rate which is the most current reading. Shouldn't it have all the readings of Heart Rate since the workout started since I have not set any limits, predicate or anchor on the query?

The behavior you are seeing is expected. The updateHandler is only called with samples that are new since the handler was last invoked. If you want to keep track of the samples recorded during the workout then you should add them to an array each time the handler is called.
Note that because you are not using a predicate, the initial results block will include all heart rate samples that are currently available in HealthKit, not just the samples recorded during the workout session. You should probably constrain the query with a date predicate to only get the samples you are interested in.

Related

How to get steps count on hourly basis from HealthKit Swift 4

I need to plot graph for steps taken by user on hourly basis on any specific date. But if the user's steps start today at 3:58 pm and end today at 4:10 pm then I am getting just one HKStatistics object for this period of time. I am not able to break this data into two samples as I need to get steps taken in the 3-4 pm slot and the 4-5 pm slot.
static func getSteps(date: Date, duration: DateComponents, completion: #escaping ([HKSample]) -> Void) {
let quantityType : Set = [HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!]
let stepsQuantityType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
let startOfDay = Calendar.current.startOfDay(for: date)
if let endOfDay = Calendar.current.date(byAdding: duration, to: startOfDay) {
var interval = DateComponents()
interval.hour = 1
let predicate = HKQuery.predicateForSamples(withStart: startOfDay, end: endOfDay, options: .strictStartDate)
let query = HKSampleQuery.init(sampleType:stepsQuantityType,
predicate: predicate,
limit: HKObjectQueryNoLimit,
sortDescriptors: nil,
resultsHandler: { (query, results, error) in
guard let result = results else {
return
}
// print("result healthkit",result.description)
//print("Total count:",)
completion(result)
})
healthStore.execute(query)
}
}
Don't use HKSampleQuery for charting quantity types. HKStatisticsCollectionQuery is designed for this purpose and will split samples that fall into separate regions of your chart for you. See the documentation for examples of how to build the query and use its results.
You're correct, you can't split the sample. That's the all the information that's available. Steps are not stored step-by-step; they're aggregated into blocks to reduce power and storage requirements (mostly power; it's easier to accumulate a value in hardware and periodically read it than to query the real time clock every single time a step is detected).
In order to do what you're discussing, you'll need to average the steps over the period. So if there were 100 steps over the period 3:58p to 4:07p, that averages 10 steps/minute, and you would allocate 20 steps to the 3p-4p block and 80 steps to the 4p-5p block. That's the best information you have.

How to query for samples from health kit that have an HKDevice

I want to query samples from HealthKit but in order to prevent inaccurate or manipulated data I don't want samples that were written to health by other apps. Does anyone have any idea what predicate I can use to filter out data from all apps or to only allow data from devices? Thanks in advance.
Edit: I've realized that apps can save data to health with an HKDevice included. So filtering out samples that don't have devices won't work.
If what you want to do is exclude manually entered data, see this answer: Ignore manual entries from Apple Health app as Data Source
Samples that were added to HealthKit by the user via Health will have the HKMetadataKeyWasUserEntered key.
You can filter out the results that are not stored by apple in your query instead of iterating all the results.
first, you need to get all the sources for your desired type.
let query = HKSourceQuery(sampleType: type,
samplePredicate: predicate) {
query, sources, error in
// error handling ...
// create a list of your desired sources
let desiredSources = sources?.filter {
!$0.bundleIdentifier.starts(with: "com.apple.health")
}
// now use that list as a predicate for your query
let sourcePredicate = HKQuery.predicateForObjects(from: desiredSources!)
// use this predicate to query for data
}
you can also combine other predicates using NSCompoundPredicate
I'm still open to suggestions and alternate solutions but here is my work-around since I was unable to figure out how to use a predicate to get the job done.
let datePredicate = HKQuery.predicateForSamples(withStart:Date(), end: nil, options: [])
let sampleQuery = HKAnchoredObjectQuery(type: sampleType,
predicate: predicate,
anchor: nil,
limit: Int(HKObjectQueryNoLimit)) { query,
samples,
deletedObjects,
anchor,
error in
if let error = error {
print("Error performing sample query: \(error.localizedDescription)")
return
}
guard let samples = samples as? [HKQuantitySample] else { return }
// this line filters out all samples that do not have a device
let samplesFromDevices = samples.filter {
$0.device != nil && $0.sourceRevision.source.bundleIdentifier.hasPrefix("com.apple.health")
}
doStuffWithMySamples(samplesFromDevices)
}
As you can see, I just filter the data once it comes through rather than doing it before-hand.
Edit: Seems like the sources listed in health are separated into apps and actual devices. Not 100% sure how they do this but it seems like the sources under the device section all have a bundle identifier prefixed with com.apple.health. Hopefully this works.

Setting up an HKAnchoredObjectQuery so that I only receive updates since the last time I queried?

I'm trying to set up an HKAnchoredObjectQuery that will only deliver results from the last time I made this query, but I can't get my head around the logic in setting up my HKQueryAnchor and how I persist it? In Apple's sample code they do not show the initial declaration for the HKQueryAnchor. Do I need to store locally the date of the last sample I downloaded and construct an anchor from that date? This code below returns every sample in HealthKit.
func updateWorkouts(completionHandler: #escaping () -> Void) {
var anchor: HKQueryAnchor?
let sampleType = HKObjectType.workoutType()
let workoutPredicate = HKQuery.predicateForWorkouts(with: .hockey)
let sourcePredicate = HKQuery.predicateForObjects(from: HKSource.default()) //limit query to only this app
let compound = NSCompoundPredicate(andPredicateWithSubpredicates: [workoutPredicate, sourcePredicate])
let anchoredQuery = HKAnchoredObjectQuery(type: sampleType, predicate: compound, anchor: anchor, limit: HKObjectQueryNoLimit) { [unowned self] query, newSamples, deletedSamples, newAnchor, error in
self.handleNewWorkouts(newWorkoutsAsSamples: newSamples!, deleted: deletedSamples!)
anchor = newAnchor
completionHandler()
}
healthStore.execute(anchoredQuery)
}
When initializing an HKAnchoredObjectQuery, you are expected to either provide nil or an anchor object that you received from a query that you executed previously. You cannot directly construct an HKQueryAnchor yourself. To persist an anchor between application launches, you can encode it in persistent storage using NSKeyedArchiver. It is common to store the resulting encoded NSData in NSUserDefaults.

Use ios health kit to get swimmingStrokesCount

I'm trying to get swimming strokes count from health kit. I'm starting workout of type swimming and then try to access swimming strokes with code:
let swimmingStrokesCountQuery = HKAnchoredObjectQuery(type: swimmingStrokeCountType!, predicate: nil, anchor: nil, limit: Int(HKObjectQueryNoLimit), resultsHandler: { (query, sampleObjects, deletedObjects, newAnchor, error) in
self.updateStrokes(samples: sampleObjects)
})
swimmingStrokesCountQuery.updateHandler = {(query, samples, deleteObjects, newAnchor, error) in
self.updateStrokes(samples: samples)
}
healthStore.execute(swimmingStrokesCountQuery)
But I don't get any samples. When I try to get steps with running workout type it works with similar code. Is it possible to get swimmingStrokeCount or watch simply does not provide it and I should implement stroke count by myself? Thanks

Data from HealthKit is initially missing, then comes in when I try again - how to fix?

I have an iOS app that I'm developing in Swift, and as part of the app it gathers the step count for the current day.
The first time I run the app, the count is "0", but if I click a button in the interface to re-run this function that queries HK, then the correct number appears.
I am guessing this is because HK needs some time to gather the data, or something, but I'm not sure how to fix it. Maybe HK can fire an event when the data is ready, and then I can update the UI?
Here's the routine that gathers the data from HK. This function executes immediately when the app start (and it shows "0 steps today"), and then as I describe above, I can tap a button to execute the function again (and then I get the right number).
func queryStepsSum() {
// prepare HK for the data
let endDate = NSDate() // right now
let startDate = NSCalendar.currentCalendar().dateBySettingHour(0, minute: 0, second: 0, ofDate: endDate, options: NSCalendarOptions())
let predicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: endDate, options: .None)
let sumOption = HKStatisticsOptions.CumulativeSum
let statisticsSumQuery = HKStatisticsQuery( quantityType: self.stepsCount!, quantitySamplePredicate: predicate,
options: sumOption)
{ [unowned self] (query, result, error) in
if let sumQuantity = result?.sumQuantity() {
self.numberOfSteps = Int(sumQuantity.doubleValueForUnit(HKUnit.countUnit()))
}
}
// run the HK query
self.healthStore?.executeQuery(statisticsSumQuery)
// update the UI with the result
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.stepsLabel.text = "\(self.numberOfSteps) steps today";
});
}
Your code doesn't wait for the query to finish before displaying the result (the query happens asynchronously). You should update your UI from the query's result handler, like this:
let statisticsSumQuery = HKStatisticsQuery( quantityType: self.stepsCount!, quantitySamplePredicate: predicate,
options: sumOption)
{ [unowned self] (query, result, error) in
dispatch_async(dispatch_get_main_queue()) {
if let sumQuantity = result?.sumQuantity() {
self.numberOfSteps = Int(sumQuantity.doubleValueForUnit(HKUnit.countUnit()))
self.stepsLabel.text = "\(self.numberOfSteps) steps today";
}
}
}
Also note that I've included a dispatch back to the main thread since the query results handler runs on a background thread and it's not safe to manipulate UI in that context.
Are you calling your function in viewDidLoad? You might try calling it in viewDidAppear instead.
If that doesn't work maybe there is some sort of delegate method that can update you when the data is ready like you said. I've never worked with HealthKit but I'll check to see if I can find anything.
EDIT:
This might set the UI component off the background thread.
Try:
var numberOfSteps: Int! {
didSet {
self.stepsLabel.text = "\(self.numberOfSteps) steps today"
}
}
That should get rid of the error that you're updating the UI on the background thread. Also make sure to remove your call in the closure that I told you to put.

Resources