Use ios health kit to get swimmingStrokesCount - ios

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

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.

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