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

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.

Related

Which predicate should i use if number of data exceed than 10 in core data swift 5?

i am storing data in core data. I want to remove data if its exceed count 10. I am not sure how to write predicate for this
for ex. if i have 20 record data from 11 to 20 should remove
my remove code is like
func removeOldData(_ removeAfterCount: Int) {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "dataTrack")
fetchRequest.returnsObjectsAsFaults = false
let predicaate = ?? how to write predicate for this case
fetchRequest.predicate = predicaate
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do {
try context.execute(deleteRequest)
try context.save()
print("Deleted Old Core data objects from Entity ")
} catch let error {
print("Detele Old data in error :", error)
}
}
It depends on indexes you used in this class. It isn't always sorted by created time.
So if you use offset property of predicate, it could be correct or not.
To flow offset approach, you should use created time property to sort it to ensure the correct result.
But why you don't prevent from insertion?, you could avoid insert if number of record is exceeded 10. You could implement func validateForInsert() to avoid insertion.
One more thing, in your source code, the fetchRequest.returnsObjectsAsFaults = false should be set to true, you don't need to fetch unFault of object to delete it.

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.

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.

HealthKit anchored queries with fallback methods?

I need to synchronize my app's database with HealthKit, and I'm currently using HKAnchoredObjectQuery to receive only the recent data. My deployment target is iOS 8.0, but I wanted to implement a fallback method to have better support for iOS 9.0+ as well. Here's the current code:
func synchronize(sampleType: HKSampleType) {
if #available(iOS 9.0, *) {
let queryAnchor = HKQueryAnchor(fromValue: self.anchor)
let resultsHandler: (HKAnchoredObjectQuery, [HKSample]?, [HKDeletedObject]?, HKQueryAnchor?, NSError?) -> Void = {
query, newSamples, deletedSamples, newAnchor, error in
// Handle results here
// TODO: QueryAnchor should persist in order to receive only new data changes!
}
let query = HKAnchoredObjectQuery(type: sampleType,
predicate: nil,
anchor: queryAnchor,
limit: HKObjectQueryNoLimit,
resultsHandler: resultsHandler)
healthKitStore.executeQuery(query)
} else {
// Fallback on earlier versions
let completionHandler: (HKAnchoredObjectQuery, [HKSample]?, Int, NSError?) -> Void = {
query, results, newAnchor, error in
// Handle results here
self.anchor = newAnchor
}
let query = HKAnchoredObjectQuery(type: sampleType,
predicate: nil,
anchor: self.anchor,
limit: HKObjectQueryNoLimit,
completionHandler: completionHandler)
healthKitStore.executeQuery(query)
}
}
Two issues:
I don't know how to persist the HKQueryAnchor, because iOS 8 doesn't support it. I'm supposed to update the persisted variable to the new anchor object the query handler returns. If I could somehow convert it to Int, I could store it as a class variable, but I don't know how.
The deprecated initializer for HKAnchoredObjectQuery uses a handler that doesn't return deleted objects. Does this mean I cannot track deleted HKSamples in iOS 8?
Regarding the first issue, HKQueryAnchor was introduced on iOS 9 and indeed isn't available on iOS 8. However, reading the documentation of it yields it conforms to NSSecureCoding which mean you can store it in a user defaults for persistence. So you can manage a dictionary that contain a key of the relevant type identifier and a value of the corresponded HKQueryAnchor on iOS 9, while on iOS 8 manage the same list with an NSNumber that holds the anchor value.
For backwards compatibility you can use the anchorFromValue: class method of HKQueryAnchor to convert old anchor values to the new class.
Regarding your second question, as far as I know there isn't a straightforward way of tracking deleted HKSamples on iOS 8. You can learn more about the way of doing it on iOS 9 in session 203 of WWDC2015

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

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.

Resources