The HKSourceQuery provides a way for you to request what apps and devices are saving a specified sample type to the health store. This tutorial will be one of the shorter ones as there isn’t too much that can be done with this particular query.
A read through of the documentation shows that the HKSourceQuery simply has an init method, and nothing else.
init(sampleType: HKSampleType, samplePredicate objectPredicate: NSPredicate?, completionHandler: @escaping (HKSourceQuery, Set<HKSource>?, Error?) -> Void)
This instruction shows that we need a HKSampleType (something like bodyMass, stepCount, or whatever). We also need a predicate. The predicate would be used to limit the results based on date. If we are interested to know the source for all samples of a particular type, we can just pass in nil here.
We then have a completion handler which provides the source query we created, a Set of HKSource (optional), and an optional error.
HKSource Tutorial
Lets begin by downloading our HealthKit project from where we left off last time. Alternatively, you can set up your own single view application, and then run through the HealthKit setup tutorial.
func testSourceQuery() { guard let bodyMassType = HKObjectType.quantityType(forIdentifier: .bodyMass) else { fatalError("*** Unable to get the body mass type ***") } let query = HKSourceQuery.init(sampleType: bodyMassType, samplePredicate: nil) { (query, sources, error) in for source in sources! { print(source.name) } } healthStore.execute(query) }
Lines 2-4 we create the quantityType that we are interested in. In this example, it will be bodyMass. Note that we have already requested permission from the user on line 24 (line 24 of the downloaded project) in the Set, and then requested authorisation on line 28.
Line 6 is where we create the query. This is one of the simplest queries. We specify our sample type here, and then on line 7 we specify a nil predicate.
Line 7 also implements the completion handler which passes back a query, a set of sources (optional), and an error which is also optional.
On lines 8-10 we loop through the sources (if any), and print out the HKSource .name property. The other property available is the bundleIdentifier of the source. .name is a more friendly name for users to see.
If you go ahead and run the app now you will get a list of sources that have saved body weight/mass to HealthKit. Here is an example of what my phone lists:
Health
Workflow
Matthew
Matthew’s Apple Watch
Sync Solver
Overall, this is a very simple query to run. If you have a need to know the sources that have written data to the health store, then this is the query for you. However, you might have noticed in other tutorials on HealthKit here that others also provide HKSource, so depending on what other queries you run, you might not need this particular query.
You can download the updated project here, and see the full source code for the project (including past tutorials). Lines 24-32 are commented out, but if you uncomment them out then you can test other types of HealthKit queries with the code below:
import UIKit import HealthKit class ViewController: UIViewController { let healthStore = HKHealthStore() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. if HKHealthStore.isHealthDataAvailable() { let readDataTypes : Set = [HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!, HKObjectType.characteristicType(forIdentifier: HKCharacteristicTypeIdentifier.biologicalSex)!, HKObjectType.characteristicType(forIdentifier: HKCharacteristicTypeIdentifier.dateOfBirth)!, HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyMass)!] let writeDataTypes : Set = [HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!] healthStore.requestAuthorization(toShare: writeDataTypes, read: readDataTypes) { (success, error) in if !success { // Handle the error here. } else { //self.testCharachteristic() //self.testSampleQuery() //self.testSampleQueryWithPredicate() //self.testSampleQueryWithSortDescriptor() //self.testAnchoredQuery() //self.testStatisticsQueryCumulitive() //self.testStatisticsQueryDiscrete() //self.testStatisticsCollectionQueryCumulitive() //self.testStatisticsCollectionQueryDiscrete() self.testSourceQuery() } } } } // Fetches biologicalSex of the user, and date of birth. func testCharachteristic() { // Biological Sex if try! healthStore.biologicalSex().biologicalSex == HKBiologicalSex.female { print("You are female") } else if try! healthStore.biologicalSex().biologicalSex == HKBiologicalSex.male { print("You are male") } else if try! healthStore.biologicalSex().biologicalSex == HKBiologicalSex.other { print("You are not categorised as male or female") } // Date of Birth if #available(iOS 10.0, *) { try! print(healthStore.dateOfBirthComponents()) } else { // Fallback on earlier versions do { let dateOfBirth = try healthStore.dateOfBirth() print(dateOfBirth) } catch let error { print("There was a problem fetching your data: \(error)") } } } // HKSampleQuery with a nil predicate func testSampleQuery() { // Simple Step count query with no predicate and no sort descriptors let sampleType = HKSampleType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount) let query = HKSampleQuery.init(sampleType: sampleType!, predicate: nil, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { (query, results, error) in print(results ?? 0) } healthStore.execute(query) } // HKSampleQuery with a predicate func testSampleQueryWithPredicate() { let sampleType = HKSampleType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyMass) let today = Date() let startDate = Calendar.current.date(byAdding: .month, value: -3, to: today) let predicate = HKQuery.predicateForSamples(withStart: startDate, end: today, options: HKQueryOptions.strictEndDate) let query = HKSampleQuery.init(sampleType: sampleType!, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { (query, results, error) in print(results ?? 0) } healthStore.execute(query) } // Sample query with a sort descriptor func testSampleQueryWithSortDescriptor() { let sampleType = HKSampleType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyMass) let today = Date() let startDate = Calendar.current.date(byAdding: .month, value: -3, to: today) let predicate = HKQuery.predicateForSamples(withStart: startDate, end: today, options: HKQueryOptions.strictEndDate) let query = HKSampleQuery.init(sampleType: sampleType!, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)]) { (query, results, error) in print(results ?? 0) } healthStore.execute(query) } func testAnchoredQuery() { guard let bodyMassType = HKObjectType.quantityType(forIdentifier: .bodyMass) else { fatalError("*** Unable to get the body mass type ***") } var anchor = HKQueryAnchor.init(fromValue: 0) if UserDefaults.standard.object(forKey: "Anchor") != nil { let data = UserDefaults.standard.object(forKey: "Anchor") as! Data anchor = NSKeyedUnarchiver.unarchiveObject(with: data) as! HKQueryAnchor } let query = HKAnchoredObjectQuery(type: bodyMassType, predicate: nil, anchor: anchor, limit: HKObjectQueryNoLimit) { (query, samplesOrNil, deletedObjectsOrNil, newAnchor, errorOrNil) in guard let samples = samplesOrNil, let deletedObjects = deletedObjectsOrNil else { fatalError("*** An error occurred during the initial query: \(errorOrNil!.localizedDescription) ***") } anchor = newAnchor! let data : Data = NSKeyedArchiver.archivedData(withRootObject: newAnchor as Any) UserDefaults.standard.set(data, forKey: "Anchor") for bodyMassSample in samples { print("Samples: \(bodyMassSample)") } for deletedBodyMassSample in deletedObjects { print("deleted: \(deletedBodyMassSample)") } print("Anchor: \(anchor)") } query.updateHandler = { (query, samplesOrNil, deletedObjectsOrNil, newAnchor, errorOrNil) in guard let samples = samplesOrNil, let deletedObjects = deletedObjectsOrNil else { // Handle the error here. fatalError("*** An error occurred during an update: \(errorOrNil!.localizedDescription) ***") } anchor = newAnchor! let data : Data = NSKeyedArchiver.archivedData(withRootObject: newAnchor as Any) UserDefaults.standard.set(data, forKey: "Anchor") for bodyMassSample in samples { print("samples: \(bodyMassSample)") } for deletedBodyMassSample in deletedObjects { print("deleted: \(deletedBodyMassSample)") } } healthStore.execute(query) } func testStatisticsQueryCumulitive() { guard let stepCountType = HKObjectType.quantityType(forIdentifier: .stepCount) else { fatalError("*** Unable to get the step count type ***") } let query = HKStatisticsQuery.init(quantityType: stepCountType, quantitySamplePredicate: nil, options: [HKStatisticsOptions.cumulativeSum, HKStatisticsOptions.separateBySource]) { (query, results, error) in print("Total: \(results?.sumQuantity()?.doubleValue(for: HKUnit.count()) ?? 0)") for source in (results?.sources)! { print("Seperate Source: \(results?.sumQuantity(for: source)?.doubleValue(for: HKUnit.count()) ?? 0)") } } healthStore.execute(query) } func testStatisticsQueryDiscrete() { guard let bodyMassType = HKObjectType.quantityType(forIdentifier: .bodyMass) else { fatalError("*** Unable to get the body mass type ***") } let query = HKStatisticsQuery.init(quantityType: bodyMassType, quantitySamplePredicate: nil, options: [HKStatisticsOptions.discreteMax, HKStatisticsOptions.separateBySource]) { (query, results, error) in print("Total: \(results?.maximumQuantity()?.doubleValue(for: HKUnit.pound()) ?? 0)") for source in (results?.sources)! { print("Seperate Source: \(results?.maximumQuantity(for: source)?.doubleValue(for: HKUnit.pound()) ?? 0)") } } healthStore.execute(query) } func testStatisticsCollectionQueryCumulitive() { guard let stepCountType = HKObjectType.quantityType(forIdentifier: .stepCount) else { fatalError("*** Unable to get the step count type ***") } var interval = DateComponents() interval.hour = 1 let calendar = Calendar.current let anchorDate = calendar.date(bySettingHour: 12, minute: 0, second: 0, of: Date()) let query = HKStatisticsCollectionQuery.init(quantityType: stepCountType, quantitySamplePredicate: nil, options: [.cumulativeSum, .separateBySource], anchorDate: anchorDate!, intervalComponents: interval) query.initialResultsHandler = { query, results, error in let startDate = calendar.startOfDay(for: Date()) print("Cumulative Sum") results?.enumerateStatistics(from: startDate, to: Date(), with: { (result, stop) in print("Time: \(result.startDate), \(result.sumQuantity()?.doubleValue(for: HKUnit.count()) ?? 0)") }) print("By Source") for source in (results?.sources())! { print("Next Device: \(source.name)") results?.enumerateStatistics(from: startDate, to: Date(), with: { (result, stop) in print("\(result.startDate): \(result.sumQuantity(for: source)?.doubleValue(for: HKUnit.count()) ?? 0)") }) } } query.statisticsUpdateHandler = { query, statistics, statisticsCollection, error in print(query) print(statistics ?? 0) print(statisticsCollection ?? 0) print(error ?? 0) } healthStore.execute(query) } func testStatisticsCollectionQueryDiscrete() { guard let bodyMassType = HKObjectType.quantityType(forIdentifier: .bodyMass) else { fatalError("*** Unable to get the body mass type ***") } var interval = DateComponents() interval.month = 1 let calendar = Calendar.current let components = calendar.dateComponents([.year, .month], from: Date()) let startOfMonth = calendar.date(from: components) let anchorDate = calendar.date(bySettingHour: 12, minute: 0, second: 0, of: startOfMonth!) let query = HKStatisticsCollectionQuery.init(quantityType: bodyMassType, quantitySamplePredicate: nil, options: [.discreteAverage, .separateBySource], anchorDate: anchorDate!, intervalComponents: interval) query.initialResultsHandler = { query, results, error in let date = calendar.startOfDay(for: Date()) let startDate = Calendar.current.date(byAdding: .year, value: -5, to: date) print("Cumulative Sum") results?.enumerateStatistics(from: startDate!, to: Date(), with: { (result, stop) in print("Month: \(result.startDate), \(result.averageQuantity()?.doubleValue(for: HKUnit.pound()) ?? 0)lbs") }) print("By Source") for source in (results?.sources())! { print("Next Device: \(source.name)") results?.enumerateStatistics(from: startDate!, to: Date(), with: { (result, stop) in print("\(result.startDate): \(result.averageQuantity(for: source)?.doubleValue(for: HKUnit.pound()) ?? 0)lbs") }) } } healthStore.execute(query) } func testSourceQuery() { guard let bodyMassType = HKObjectType.quantityType(forIdentifier: .bodyMass) else { fatalError("*** Unable to get the body mass type ***") } let query = HKSourceQuery.init(sampleType: bodyMassType, samplePredicate: nil) { (query, sources, error) in for source in sources! { print(source.name) } } healthStore.execute(query) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }
Leave a Reply
You must be logged in to post a comment.