Does apple healthkit queries run asynchronously? - ios

Does the apple healthkit queries like HKStatisticsQuery, HKSampleQuery and HKStatisticsCollectionQuery run asynchronously or do we need to explicitly run the queries in a separate thread ?
I just wrote the queries using any async way as follows and it works. I want to know whether I should put it inside an async block
private func readOxygrnSaturation(){
dispatchGroup.enter()
let quantityType = HKObjectType.quantityType(forIdentifier: .oxygenSaturation)!
let sampleQuery = HKSampleQuery.init(sampleType: quantityType,
predicate: nil,
limit: HKObjectQueryNoLimit,
sortDescriptors: nil,
resultsHandler: { (query, results, error) in
guard let samples = results as? [HKQuantitySample] else {
print(error!)
return
}
for sample in samples {
let mSample = sample.quantity.doubleValue(for: HKUnit(from: "%"))
self.oxygenSaturation.append(HealthData(unit: "%", startDate: self.dateFormatter.string(from: sample.startDate) , endDate: self.dateFormatter.string(from: sample.endDate), value: mSample))
}
self.dispatchGroup.leave()
})
self.healthStore .execute(sampleQuery)
}

Related

Apple healthKit REM, Deep, Light sleep analysis

I am working on an IOS sleep application where i need to do sleep analysis. I am using Healthkit for sleep data from where i can successfully fetch sleep analysis data using below code :
func retrieveSleepAnalysis(from startDate: Date?, to endDate: Date? , completion: #escaping ([HKCategorySample], Error?) -> Void) {
guard let sleepType = HKObjectType.categoryType(forIdentifier: .sleepAnalysis) else { return}
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
let query = HKSampleQuery(sampleType: sleepType, predicate: predicate, limit: 10000, sortDescriptors: [sortDescriptor]) { (query, result, error) in
if error != nil {
completion([], error)
return
}
if let result = result {
let samples = result.compactMap({ $0 as? HKCategorySample})
completion(samples, nil)
}
}
// finally, we execute our query
HKHealthStore().execute(query)
}
I am not able to find any healthKit code for Sleep REM cycles , Deep sleep , light sleep etc. Is it even possible to get this data from healthKit ? if Yes , How to do it? , if not with healthKit, How to do it in IOS Applications ?
You may find what you're looking for in this year's WWDC talk on "What's New In HealthKit"
Here's how you can declare a predicate for all samples:
// Predicate for all asleep samples (unspecified, core, deep, REM)
let allAsleepPredicate = HKCategoryValueSleepAnalysis.predicateForSamples(equalTo: .allAsleepValues)
And here's how you might use it to fetch all samples in a range:
let healthStore = HKHealthStore()
let sleepType = HKCategoryType(.sleepAnalysis)
let dateRangePredicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [dateRangePredicate, allAsleepPredicate])
let query = HKSampleQuery(sampleType: sleepType, predicate: predicate) { (query, result, error) in
// handle results
}
Hope that helps

Accessing resting heart rate

Good Day,
I'm a novice at Swift 4 and am having a trouble getting resting heart rate data.
Here is what I have:
// Declarations
var heartRateType = HKAnchoredObjectQuery.self
private func createStreamingQuery() -> HKQuery {
let calendar = NSCalendar.current;
let now = NSDate();
let sevenDaysAgo = calendar.date(byAdding: .day, value: -7, to: now as Date);
let startDate = calendar.startOfDay(for: sevenDaysAgo!);
let predicate = HKQuery.predicateForSamples(withStart: startDate as Date, end: now as Date, options: [])
let query = HKAnchoredObjectQuery(type: heartRateType, predicate: predicate, anchor: nil, limit: Int(HKObjectQueryNoLimit)) {
(query, samples, deletedObjects, anchor, error) -> Void in
self.formatSamples(samples: samples)
}
query.updateHandler = { (query, samples, deletedObjects, anchor, error) -> Void in
self.formatSamples(samples: samples)
}
return query
}
#IBAction func readHeartRate(_ sender: Any) {
self.healthKitStore.execute(self.createStreamingQuery())
}
private func formatSamples(samples: [HKSample]?) {
guard let heartRateSamples = samples as? [HKQuantitySample] else { return }
guard let sample = heartRateSamples.first else{return}
let value = sample.quantity.doubleValue(for: heartRateType)
print("HeartRate: \(value)")
}
I'm getting the following errors with these lines and can't check to see if this will even work.
// Error: Generic parameter 'T' could not be inferred
// Code with error:
let query = HKAnchoredObjectQuery(type: heartRateType, predicate: predicate, anchor: nil, limit: Int(HKObjectQueryNoLimit)) {
(query, samples, deletedObjects, anchor, error) -> Void in
self.formatSamples(samples: samples)
}
The other error: Cannot convert value of type 'HKAnchoredObjectQuery.Type' to expected argument type 'HKUnit'
// Code with error:
let value = sample.quantity.doubleValue(for: heartRateType)
Any assistance you can provide is greatly appreciated.
Thank you so much!
Kevin
Since I posted this, I've researched and learn more about this. I ended up rewriting the function to the following:
//MARK: - Get Users Resting Heart Rate
func getUsersRestingHeartRate(completion: #escaping (HKQuantitySample) -> Void) {
// print("getUsersRestingHeartRate(Completion)")
guard let restingHeartRateSampleType = HKSampleType.quantityType(forIdentifier: .restingHeartRate) else {
print("Resting Heart Rate Sample Type is no longer available in HealthKit")
return
}
//1. Use HKQuery to load the most recent samples.
let mostRecentPredicate = HKQuery.predicateForSamples(withStart: Date.distantPast,
end: Date(),
options: .strictEndDate)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)
let sampleQuery = HKSampleQuery(sampleType: restingHeartRateSampleType,
predicate: mostRecentPredicate,
limit: HKObjectQueryNoLimit,
sortDescriptors:
[sortDescriptor]) { (query, samples, error) in
DispatchQueue.main.async {
guard let samples = samples,
let mostRecentSample = samples.first as? HKQuantitySample else {
print("getUserRestingHeartRate sample is missing")
return
}
completion(mostRecentSample)
}
}
HKHealthStore().execute(sampleQuery)
}

HKSampleQuery returns no results even when there are

I'm trying to get all heart rate samples from the past month, and extract the times and values from them.
So far, I've got the following method:
func getThisMonthsHeartRates() {
print("func called")
let heartRateUnit:HKUnit = HKUnit(from: "count/min")
let heartRateType:HKQuantityType = HKQuantityType.quantityType(forIdentifier: .heartRate)!
//predicate
let startDate = Date()
let endDate = Date() - 1.month
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: [])
//descriptor
let sortDescriptors = [
NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
]
let heartRateQuery = HKSampleQuery(sampleType: heartRateType,
predicate: predicate,
limit: Int(HKObjectQueryNoLimit),
sortDescriptors: sortDescriptors)
{ (query:HKSampleQuery, results:[HKSample]?, error:Error?) -> Void in
guard error == nil else { print("error"); return }
print("results")
print(results!)
for result in results! {
guard let currData:HKQuantitySample = result as? HKQuantitySample else { return }
print("Heart Rate: \(currData.quantity.doubleValue(for: heartRateUnit))")
print("quantityType: \(currData.quantityType)")
print("Start Date: \(currData.startDate)")
print("End Date: \(currData.endDate)")
print("Metadata: \(String(describing: currData.metadata))")
print("UUID: \(currData.uuid)")
print("Source: \(currData.sourceRevision)")
print("Device: \(String(describing: currData.device))")
print("---------------------------------\n")
}
} //eo-query
healthStore.execute(heartRateQuery)
}//eom
However, the results will always return an empty array, even though I've got samples on my device! Really curious as to how this can be and how to fix it. I'm at a total loss.
Thanks
Update
After logging the query before it gets executed and while it's being executed, this is what the console says:
<HKSampleQuery:0x1c4117610 inactive>
And
<HKSampleQuery:0x1c4117610 deactivated>
I have no idea what this means nor can I find anything online about it.
The problem might be that you have requested authorization to write .heartRate sample types, but not to read them as well. In this case you don't receive an error when executing the query, however the samples array will be empty.
I had the same problem because I was requesting authorization this way:
healthStore.requestAuthorization(toShare: types, read: nil) {}
Instead, you need to specify the types that you want to read even though they are already in the types set.

How can I fetch the most recent weight entry from HealthKit data for each Day in a defined time interval

Hello I want to grab the latest datapoint for body mass of each day in a defined time interval
(in my case I need it for an interval of one week but only the last entry for each day.)
Actually, Using this code I can get all the entries from start X date to the end X date
let query = HKSampleQuery(sampleType: type!, predicate: predicate,
limit: 0, sortDescriptors: nil, resultsHandler: { (query, results, error) in
if let myResults = results {
for result in myResults {
let bodymass = result as! HKQuantitySample
let weight = bodymass.quantity.doubleValue(for: unit)
Print ("this is my weight value",weight )
}
}
else {
print("There was an error running the query: \(String(describing: error))")
}
This query returns any samples measuring weight consumed that fall within the time frame.
I just want to return the last entry recorded is there any way to do it with heath-kit query?
I've tried to define sort descriptor but I don’t find a way to make it work with defined time interval.
Thanks
I read this and this one
As you said you want to use a sort descriptor, just use Date.distantPast and Date() as your range, then just grab the first:
func getUserBodyMass(completion: #escaping (HKQuantitySample) -> Void) {
guard let weightSampleType = HKSampleType.quantityType(forIdentifier: .bodyMass) else {
print("Body Mass Sample Type is no longer available in HealthKit")
return
}
//1. Use HKQuery to load the most recent samples.
let mostRecentPredicate = HKQuery.predicateForSamples(withStart: Date.distantPast,
end: Date(),
options: [])
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate,
ascending: false)
let limit = 1
let sampleQuery = HKSampleQuery(sampleType: weightSampleType,
predicate: mostRecentPredicate,
limit: limit,
sortDescriptors: [sortDescriptor]) { (query, samples, error) in
//2. Always dispatch to the main thread when complete.
DispatchQueue.main.async {
guard let samples = samples,
let mostRecentSample = samples.first as? HKQuantitySample else {
print("getUserBodyMass sample is missing")
return
}
completion(mostRecentSample)
}
}
healthStore.execute(sampleQuery)
}

Cannot find an initializer for type

let endDate = NSDate()
let startDate = NSDate()
let v : Float?
let stepsCount:HKQuantityType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)!
let predicate:NSPredicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: endDate, options: .None)
let query = HKQuantitySample(sampleType: stepsCount, predicate: predicate, limit: 1, sortDescriptors: nil, resultsHandler: {
(query, results, error) in
if results == nil {
print(error)
}
v = result.first.Quantity
})
healthStore.executeQuery(query)
Cannot find an initializer for type 'HKQuantitySample' that accepts
an argument list of type '(sampleType: HKQuantityType, predicate:
NSPredicate, limit: Int, sortDescriptors: nil, resultsHandler: (_, _,
_) -> _)'
Just replace HKQuantitySample with HKSampleQuery and it will work fine.
For more Info refer THIS tutorial.
Where you can find sample code like:
func readMostRecentSample(sampleType:HKSampleType , completion: ((HKSample!, NSError!) -> Void)!)
{
// 1. Build the Predicate
let past = NSDate.distantPast() as! NSDate
let now = NSDate()
let mostRecentPredicate = HKQuery.predicateForSamplesWithStartDate(past, endDate:now, options: .None)
// 2. Build the sort descriptor to return the samples in descending order
let sortDescriptor = NSSortDescriptor(key:HKSampleSortIdentifierStartDate, ascending: false)
// 3. we want to limit the number of samples returned by the query to just 1 (the most recent)
let limit = 1
// 4. Build samples query
let sampleQuery = HKSampleQuery(sampleType: sampleType, predicate: mostRecentPredicate, limit: limit, sortDescriptors: [sortDescriptor])
{ (sampleQuery, results, error ) -> Void in
if let queryError = error {
completion(nil,error)
return;
}
// Get the first sample
let mostRecentSample = results.first as? HKQuantitySample
// Execute the completion closure
if completion != nil {
completion(mostRecentSample,nil)
}
}
// 5. Execute the Query
self.healthKitStore.executeQuery(sampleQuery)
}
Documentation doesn't talk about any initializer like the one you are providing… Even looked in the Beta docs and didn't found anything about the one you are trying to call.
Please look here for existing HKQuantitySample initializers available :
https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HKQuantitySample_Class/
See Dharmesh Kheni's answer for the correct way to create a query :).

Resources