I'm new to HealthKit framework and wants real time oxygen saturation in blood stream.
REF : https://github.com/coolioxlr/watchOS-2-heartrate
FROM THIS REPO i have tried to make query for oxygen saturation
func createOxygenSaturationStreamingQuery(_ workoutStartDate: Date) -> HKQuery? {
guard let quantityType = HKObjectType.quantityType(forIdentifier: .oxygenSaturation) else { return nil }
let datePredicate = HKQuery.predicateForSamples(withStart: workoutStartDate, end: nil, options: .strictEndDate )
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates:[datePredicate])
let oxygenSaturationQuery = HKAnchoredObjectQuery(type: quantityType,
predicate: predicate,
anchor: nil,
limit: Int(HKObjectQueryNoLimit)) { (query, sampleObjects, deletedObjects, newAnchor, error) -> Void in
self.updateOxygenSaturation(sampleObjects)
}
oxygenSaturationQuery.updateHandler = {(query, samples, deleteObjects, newAnchor, error) -> Void in
self.updateOxygenSaturation(samples)
}
return oxygenSaturationQuery
}
func updateOxygenSaturation(_ samples: [HKSample]?) {
guard let oxygenSaturationSamples = samples as? [HKQuantitySample] else {return}
DispatchQueue.main.async {
// RETURN FROM HERE AS EMPTY ARRAY
guard let sample = oxygenSaturationSamples.first else { return }
let value = sample.quantity.doubleValue(for: HKUnit(from: "%/min"))
let stringValue = String(UInt16(value))
self.label.setText(stringValue)
// retrieve source from sample
let name = sample.sourceRevision.source.name
print("sample.sourceRevision.source.name : \(name)")
print("PERCENT : \(stringValue)")
}
}
P.S : I'M CHECKING IN SIMULATOR
I'm getting Heart Rate from that repo, but getting oxygen level empty?
Let me know what wrong i have done and how can i correct?
watchOS doesn't automatically record Oxygen Saturation samples, so you shouldn't expect to get any results back unless you have a device that is recording samples of this type and writing them to HealthKit using an app.
Related
I'm trying to build an app with swift which will extract the heart rate from iwatch and display it to the user along with it will play some music in the user's iphone. Pretty new to ios ,so im trying to figure out how to extract data from healthkit in iwatch and sync it with the mobile app. Do i need to build 2 apps , one for watch and phone or the same app? and how to integrate health kit since i have just enabled it in the target->capabilities.
public func subscribeToHeartBeatChanges() {
// Creating the sample for the heart rate
guard let sampleType: HKSampleType =
HKObjectType.quantityType(forIdentifier: .heartRate) else {
return
}
/// Creating an observer, so updates are received whenever HealthKit’s
// heart rate data changes.
self.heartRateQuery = HKObserverQuery.init(
sampleType: sampleType,
predicate: nil) { [weak self] _, _, error in
guard error == nil else {
log.warn(error!)
return
}
/// When the completion is called, an other query is executed
/// to fetch the latest heart rate
self.fetchLatestHeartRateSample(completion: { sample in
guard let sample = sample else {
return
}
/// The completion in called on a background thread, but we
/// need to update the UI on the main.
DispatchQueue.main.async {
/// Converting the heart rate to bpm
let heartRateUnit = HKUnit(from: "count/min")
let heartRate = sample
.quantity
.doubleValue(for: heartRateUnit)
/// Updating the UI with the retrieved value
self?.heartRateLabel.setText("\(Int(heartRate))")
}
})
}
}
public func fetchLatestHeartRateSample(
completion: #escaping (_ sample: HKQuantitySample?) -> Void) {
/// Create sample type for the heart rate
guard let sampleType = HKObjectType
.quantityType(forIdentifier: .heartRate) else {
completion(nil)
return
}
/// Predicate for specifiying start and end dates for the query
let predicate = HKQuery
.predicateForSamples(
withStart: Date.distantPast,
end: Date(),
options: .strictEndDate)
/// Set sorting by date.
let sortDescriptor = NSSortDescriptor(
key: HKSampleSortIdentifierStartDate,
ascending: false)
/// Create the query
let query = HKSampleQuery(
sampleType: sampleType,
predicate: predicate,
limit: Int(HKObjectQueryNoLimit),
sortDescriptors: [sortDescriptor]) { (_, results, error) in
guard error == nil else {
print("Error: \(error!.localizedDescription)")
return
}
completion(results?[0] as? HKQuantitySample)
}
self.healthStore.execute(query)
}
I have created a session in Watch and updating Heart Rate Data in Health Kit. Now, I want to Display the current Heart Rate in my iPhone Screen. Watch sensor update the Heart Rate Data in Health kit BUT iPhone Application in NOT able to Fetch Real-Time Data from the Health kit. I have tested out below TWO scenarios. I have also recalled this method/function using timer BUT it is not getting real-time data.
Note: When I open Health App and Re-open my application then It will automatically refresh the data. If my application continuously in foreground then below code not refreshing latest data from the health kit
1. Tried to get Real Time Heart Rate Data using HKSampleQuery
let calendar = NSCalendar.current
let components = calendar.dateComponents([.year, .month, .day], from: Date())
let startDate : NSDate = calendar.date(from: components)! as NSDate
let endDate : Date = calendar.date(byAdding: Calendar.Component.day, value: 1, to: startDate as Date)!
let predicate = HKQuery.predicateForSamples(withStart: startDate as Date, end: endDate, options:[])
//descriptor
let sortDescriptors = [NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)]
self.heartRateQuery = HKSampleQuery(sampleType: self.heartRateType, predicate: predicate, limit: 1, sortDescriptors: sortDescriptors, resultsHandler: { (query:HKSampleQuery, results:[HKSample]?, error:Error?) in
guard error == nil else { print("error in getting data"); return }
self.collectCurrentHeartRateSample(currentSampleTyple: results)
})
self.healthStore.execute(self.heartRateQuery!)
2. Tried to get Real Time Heart Rate Data using HKAnchoredObjectQuery
let sampleType : HKSampleType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!
let predicate : NSPredicate = HKQuery.predicateForSamples(withStart: startDate as Date, end: endDate, options: [])
let anchor: HKQueryAnchor = HKQueryAnchor(fromValue: 0)
let anchoredQuery = HKAnchoredObjectQuery(type: sampleType, predicate: predicate, anchor: anchor, limit: HKObjectQueryNoLimit) { (query, samples, deletedObjects, anchor, error ) in
self.collectCurrentHeartRateSample(currentSampleTyple: samples!, deleted: deletedObjects!)
}
anchoredQuery.updateHandler = { (query, samples, deletedObjects, anchor, error) -> Void in
self.collectCurrentHeartRateSample(currentSampleTyple: samples!, deleted: deletedObjects!)
}
self.healthStore.execute(anchoredQuery)
=============================================
Parsed the Data
func collectCurrentHeartRateSample(currentSampleTyple : [HKSample]?, deleted :
[HKDeletedObject]?){
// func collectCurrentHeartRateSample(currentSampleTyple : [HKSample]?){
DispatchQueue.main.async {
self.currentHeartRateSample = currentSampleTyple
//Get Last Sample of Heart Rate
self.currentHeartLastSample = self.currentHeartRateSample?.last
print("lastSample : \(String(describing: self.currentHeartLastSample))")
if self.currentHeartLastSample != nil {
let result = self.currentHeartLastSample as! HKQuantitySample
let heartRateBPM = result.quantity.doubleValue(for: HKUnit(from: "count/min"))
let heartRateBPMUnit = "count/min"
let deviceUUID = self.currentHeartLastSample?.uuid
let deviceIdentity = result.sourceRevision.source.name
let deviceProductName = self.currentHeartLastSample?.device?.name
let deviceProductType = result.sourceRevision.productType
let deviceOSVersion = result.sourceRevision.version
let startDate = self.currentHeartLastSample?.startDate
let endDate = self.currentHeartLastSample?.endDate
self.aCollectionView.reloadData()
}
}
}
I think the best method is to simply send the heart rate data to the phone app using Watch Communication.
In Watch code:
func send(heartRate: Int) {
guard WCSession.default.isReachable else {
print("Phone is not reachable")
return
}
WCSession.default.sendMessage(["Heart Rate" : heartRate], replyHandler: nil) { error in
print("Error sending message to phone: \(error.localizedDescription)")
}
}
and on phone you receive the data with:
func session(_ session: WCSession, didReceiveMessage message: [String: Any]) {
if let heartRate = message["Heart Rate"] {
print("Received heart rate: \(heartRate)")
} else {
print("Did not receive heart rate =[")
}
}
This should happen in pretty much real time. Alternatively there is another less reliable solution (imo) which is to just perform the heart rate query once every 5 seconds or so, but if I understand correctly you've already tried that and it didn't work.
Here is my own analysis regarding get nearby real-time Heart Rate.
1. If you are accessing Health Kit data using iPhone app, In this scenario, Health Kit DB NOT frequently updated/refreshed. So, your app not able to get real-time latest updated data through iPhone app.
2. Using a watch app, you can access near real-time data through Health Kit DB. Watch app is able to get the real-time latest updated Health Kit Data.
3. You need to transfer data from watch to iPhone app. Here is a code for your reference. You can write code as per your requirement. You just need to access Heart Rate through HKQuery
let defaultSession = WCSession.default
let healthStore = HKHealthStore()
var currentHeartRateSample : [HKSample]?
var currentHeartLastSample : HKSample?
var currentHeartRateBPM = Double()
//Get Heart Rate from Health Kit
func getCurrentHeartRateData(){
let calendar = Calendar.current
let components = calendar.dateComponents([.year, .month, .day], from: Date())
let startDate : Date = calendar.date(from: components)!
let endDate : Date = calendar.date(byAdding: Calendar.Component.day, value: 1, to: startDate as Date)!
let sampleType : HKSampleType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!
let predicate : NSPredicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: [])
let anchor: HKQueryAnchor = HKQueryAnchor(fromValue: 0)
let anchoredQuery = HKAnchoredObjectQuery(type: sampleType, predicate: predicate, anchor: anchor, limit: HKObjectQueryNoLimit) { (query, samples, deletedObjects, anchor, error ) in
if samples != nil {
self.collectCurrentHeartRateSample(currentSampleTyple: samples!, deleted: deletedObjects!)
}
}
anchoredQuery.updateHandler = { (query, samples, deletedObjects, anchor, error) -> Void in
self.collectCurrentHeartRateSample(currentSampleTyple: samples!, deleted: deletedObjects!)
}
self.healthStore.execute(anchoredQuery)
}
//Retrived necessary parameter from HK Sample
func collectCurrentHeartRateSample(currentSampleTyple : [HKSample]?, deleted : [HKDeletedObject]?){
self.currentHeartRateSample = currentSampleTyple
//Get Last Sample of Heart Rate
self.currentHeartLastSample = self.currentHeartRateSample?.last
if self.currentHeartLastSample != nil {
let lastHeartRateSample = self.currentHeartLastSample as! HKQuantitySample
self.currentHeartRateBPM = lastHeartRateSample.quantity.doubleValue(for: HKUnit(from: "count/min"))
let heartRateStartDate = lastHeartRateSample.startDate
let heartRateEndDate = lastHeartRateSample.endDate
//Send Heart Rate Data Using Send Messge
DispatchQueue.main.async {
let message = [
"HeartRateBPM" : "\(self.currentHeartRateBPM)",
"HeartRateStartDate" : "\(heartRateStartDate)",
"HeartRateEndDate" : "\(heartRateEndDate)"
]
//Transfer data from watch to iPhone
self.defaultSession.sendMessage(message, replyHandler:nil, errorHandler: { (error) in
print("Error in send message : \(error)")
})
}
}
}
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)
}
I'm trying to retrieve the Heart rate information from the Healthkit.
I have some heart rate data on my profile.
Here is my query:
private func createStreamingQuery() -> HKQuery {
let predicate = HKQuery.predicateForSamples(withStart: NSDate() as Date, end: nil, 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
}
And now my function format Samples:
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: self.heartRateUnit)
print("HeartRate: \(value)")
}
I already debugged and I found that in the first line of code of "formatSamples", the list "samples" has a lot of values,
guard let heartRateSamples = samples as? [HKQuantitySample] else { return }
but when I try to get the first value of this list, suddenly my list is empty and it ends the function.
Here->
guard let sample = heartRateSamples.first else{return}
I don't understand why the samples list empties by itself from one line to the next one.
The query is executed.
#IBAction func readHeartRate(_ sender: Any) {
self.healthStore.execute(self.createStreamingQuery())
}
Can you help me?
The problem was my predicate who was incorrect.
Here an example of a correct predicate that I used. It limits the results to the last seven days.
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: [])
I'm following some tutorials on HealthKit using swift, one of the tutorials I'm following is how to retrieve some data from the HealthKit such as weight, height age. The tutorial shows how to retrieve the most recent record for each them, the following codes shows that:
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)
}
Then in other class the developer passes the parameters to get the required most recent record, the following code shows the method that retrieves the height record:
func updateHeight()
{
// 1. Construct an HKSampleType for Height
let sampleType = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeight)
// 2. Call the method to read the most recent Height sample
self.healthManager?.readMostRecentSample(sampleType, completion: { (mostRecentHeight, error) -> Void in
if( error != nil )
{
println("Error reading height from HealthKit Store: \(error.localizedDescription)")
return;
}
var heightLocalizedString = self.kUnknownString;
self.height = mostRecentHeight as? HKQuantitySample;
// 3. Format the height to display it on the screen
if let meters = self.height?.quantity.doubleValueForUnit(HKUnit.meterUnit()) {
let heightFormatter = NSLengthFormatter()
heightFormatter.forPersonHeightUse = true;
heightLocalizedString = heightFormatter.stringFromMeters(meters);
}
// 4. Update UI. HealthKit use an internal queue. We make sure that we interact with the UI in the main thread
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.heightLabel.text = heightLocalizedString
self.updateBMI()
});
})
}
I created a similar method to the first one, but with few changes so I can get an array of 10 glucose records:
func readAllGlucose(sampleType:HKSampleType , completion: (([HKSample!], NSError!) -> Void)!)
{
let now = NSDate()
let df = NSDateFormatter()
df.dateFormat = "yyyy-MM-dd"
let pastt = df.dateFromString("2015-05-18")
//let mostRecentPredicate = HKQuery.predicateForSamplesWithStartDate(past, endDate:now, options: .None)
let allreadings = HKQuery.predicateForSamplesWithStartDate(pastt, 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 = 10
// 4. Build samples query
let sampleQuery = HKSampleQuery(sampleType: sampleType, predicate: allreadings, limit: limit, sortDescriptors: [sortDescriptor])
{ (sampleQuery, results, error ) -> Void in
if let queryError = error {
completion([nil],error)
return;
}
// Get the first sample
let allSamples = results as? [HKQuantitySample]
// Execute the completion closure
if completion != nil {
completion(allSamples!,nil)
}
}
// 5. Execute the Query
self.healthKitStore.executeQuery(sampleQuery)
}
Then I created another method similar to updateHeight() method,but of course with doing the necessary changes:
func updateLastGlucoRecords()
{
// 1. Construct an HKSampleType for weight
let sampleType = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBloodGlucose)
// 2. Call the method to read the most recent weight sample
self.healthManager?.readAllGlucose(sampleType, completion: {([allReadings], error) -> Void in
if (error != nil) {
println("Error reading glucose readings from HealthKit Store: \(error.localizedDescription)")
return;
}
var glucoseLocalizedString = self.kUnknownString;
self.glucose = allReadings as? [HKQuantitySample]
for reading in readings {
if let record = reading.quantity {
glucoseLocalizedString = record
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.glucoReadings.append("\(glucoseLocalizedString)")
})
}
})
self.healthManager?.readMostRecentSample(sampleType, completion: { (mostRecentWeight, error) -> Void in
if( error != nil )
{
println("Error reading weight from HealthKit Store: \(error.localizedDescription)")
return;
}
var weightLocalizedString = self.kUnknownString;
// 3. Format the weight to display it on the screen
self.weight = mostRecentWeight as? HKQuantitySample;
if let kilograms = self.weight?.quantity.doubleValueForUnit(HKUnit.gramUnitWithMetricPrefix(.Kilo)) {
let weightFormatter = NSMassFormatter()
weightFormatter.forPersonMassUse = true;
weightLocalizedString = weightFormatter.stringFromKilograms(kilograms)
}
// 4. Update UI in the main thread
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.weightLabel.text = weightLocalizedString
self.updateBMI()
});
});
}
But unfortunately I'm getting two errors at the line:
self.healthManager?.readAllGlucose(sampleType, completion: {([allReadings], error) -> Void in
first error:
[HKSample!]' is not a subtype of '<>
second error:
Use of undeclared type 'allReadings'
I have another question, how can I get the metadata from each object, I want to get the date and time that the data was inserted
If someone have any idea that could solve this problem I will be thankful and grateful for that
thanks in advance
I was having the same problem . Please look into my code below .
1.) allReadings error is due to the reason that you have not passed the array of HKSample so to make it so you have to write as: let allReadings:[HKSample!] and then pass allReadings.
func readAllSample(sampleType:HKSampleType , completion: (([HKSample!], NSError!) -> Void)!)
{
let now = NSDate()
let past = now.dateByAddingTimeInterval(-10*24*60*60) as NSDate!
let mostRecentPredicate = HKQuery.predicateForSamplesWithStartDate(past, endDate:now, options: .None)
let sortDescriptor = NSSortDescriptor(key:HKSampleSortIdentifierStartDate, ascending: false)
let limit = 10
let sampleQuery = HKSampleQuery(sampleType: sampleType, predicate: mostRecentPredicate, limit: limit, sortDescriptors: [sortDescriptor])
{ (sampleQuery, results, error ) -> Void in
if(error == nil)
{
print(results)
}
}
self.healthKitStore.executeQuery(sampleQuery)
}