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)
}
Related
I'm trying to build an iPhone app based on the user's live heart rate data.
I was able to get the latest heart rate from the Health App but once new information is recorded the app is not updated until relaunch.
Is there a way to get heart rate data in live? Maybe with HKObserverQuery?
here's my code so far: (Who manages to pull the last heartbeat)
import Foundation
import UIKit
import HealthKit
class HealthStore {
var healthStore: HKHealthStore?
init() {
if HKHealthStore.isHealthDataAvailable(){
healthStore = HKHealthStore()
}
}
func requestAuthorization(completion: #escaping (Bool) -> Void){
let heartBeat = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!
guard let healthStore = self.healthStore else {return completion(false)}
healthStore.requestAuthorization(toShare: [], read: [heartBeat]) { (success, error) in completion(success)
}
}
func latestHarteRate(){
guard let sampleType = HKObjectType.quantityType(forIdentifier: .heartRate) else {
return
}
let startDate = Calendar.current.date(byAdding: .month, value: -1, to: Date())
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: Date(), options: .strictEndDate)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)
let query = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: Int(HKObjectQueryNoLimit), sortDescriptors: [sortDescriptor]){(sample,result,error) in guard error == nil else{
return
}
let data = result![0] as! HKQuantitySample
let unit = HKUnit(from: "count/min")
let latestHr = data.quantity.doubleValue(for: unit)
print("Latest Hr\(latestHr) BPM")
healthStore?.execute(query)
}
You can use an HKAnchoredObjectQuery to create a query that returns an initial set of data and then updates to the data set.
Unfortunately, you can't provide a sort descriptor to the HKAnchoredObjectQuery, so you need to sort the data after you receive it if you don't want ascending order.
Here is a model object I created so that I could test in SwiftUI.
It creates an HKAnchoredQuery and sets an update handler function. The update handler converts the HealthKit results into my HeartRateEntry struct (This is so I could easily display the data in a SwiftUI list). The array is then sorted by descending date.
The update function stores the newAnchor that was received so that only changes are delivered in the future.
While testing I found that running the heart rate app on my watch, moving my test app into the background and then swapping back to it triggered the new heart rate data more quickly than just waiting for the new data to be delivered.
import Foundation
import HealthKit
struct HeartRateEntry: Hashable, Identifiable {
var heartRate: Double
var date: Date
var id = UUID()
}
class HeartHistoryModel: ObservableObject {
#Published var heartData: [HeartRateEntry] = []
var healthStore: HKHealthStore
var queryAnchor: HKQueryAnchor?
var query: HKAnchoredObjectQuery?
init() {
if HKHealthStore.isHealthDataAvailable() {
healthStore = HKHealthStore()
} else {
fatalError("Health data not available")
}
self.requestAuthorization { authorised in
if authorised {
self.setupQuery()
}
}
}
func requestAuthorization(completion: #escaping (Bool) -> Void){
let heartBeat = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!
self.healthStore.requestAuthorization(toShare: [], read: [heartBeat]) { (success, error) in completion(success)
}
}
func setupQuery() {
guard let sampleType = HKObjectType.quantityType(forIdentifier: .heartRate) else {
return
}
let startDate = Calendar.current.date(byAdding: .month, value: -1, to: Date())
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: .distantFuture, options: .strictEndDate)
self.query = HKAnchoredObjectQuery(type: sampleType, predicate: predicate, anchor: queryAnchor, limit: HKObjectQueryNoLimit, resultsHandler: self.updateHandler)
self.query!.updateHandler = self.updateHandler
healthStore.execute(self.query!)
}
func updateHandler(query: HKAnchoredObjectQuery, newSamples: [HKSample]?, deleteSamples: [HKDeletedObject]?, newAnchor: HKQueryAnchor?, error: Error?) {
if let error = error {
print("Health query error \(error)")
} else {
let unit = HKUnit(from: "count/min")
if let newSamples = newSamples as? [HKQuantitySample], !newSamples.isEmpty {
print("Received \(newSamples.count) new samples")
DispatchQueue.main.async {
var currentData = self.heartData
currentData.append(contentsOf: newSamples.map { HeartRateEntry(heartRate: $0.quantity.doubleValue(for: unit), date: $0.startDate)
})
self.heartData = currentData.sorted(by: { $0.date > $1.date })
}
}
self.queryAnchor = newAnchor
}
}
}
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)")
})
}
}
}
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.
I am trying to get HKWorkout samples by HKSampleQuery in Apple's HealthKit. By the way I can't get HKWorkout's device name which was tracked by Apple Watch.
I could get several HKWorkout Sample data and confirm the sourceRevision and totalDistance are available. But the only device data couldn't be confirmed. It showed it has a null value.
Is Apple missing it on that?
Here is my code to experiment this.
func getRunningWorkouts(completionHandler: #escaping (_ data:[AnyObject]?, _ response:Bool, _ error:Error?) -> Void) {
let predicateForRunning = HKQuery.predicateForWorkouts(with: .running)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)
let sampleType = HKWorkoutType.workoutType()
let sampleQuery = HKSampleQuery(sampleType: sampleType, predicate: predicateForRunning, limit: HKObjectQueryNoLimit, sortDescriptors: [sortDescriptor]) { (query, resultsArray, error) in
if error != nil {
print("Get an error to extract running workout: \(error)")
return
}
if let samples = resultsArray as? [HKWorkout] {
for sample in samples {
print(sample.device?.name)
completionHandler(samples, true, nil)
}
}
}
}
healthStore.execute(sampleQuery)
}
You should file a bug with Apple if you believe that it's a mistake that the name of the Apple Watch is missing from the sample.
I am using iHealth devices to get the vitals from device using there iHealth app,those data is stored in Health app. I have configured my application to communicate with Health app but I have no idea how to get the stored data of health to my own custom app.
As there are no examples for this issue and the documentation also didn't provide in-depth information about it.
As long as you (or the user) of your app has granted access to your app to read what iHealth has stored in the HealthKit database, you can query it with the HealthKit APIs.
// 1. these are the items we want to read
let healthKitTypesToRead = NSSet(array:[
HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMass),
HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBloodGlucose)
])
// 2. these are the items we want to write
let healthKitTypesToWrite = NSSet(array:[
HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMass),
HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBloodGlucose)
])
// 3. Request HealthKit authorization
healthKitStore!.requestAuthorizationToShareTypes(healthKitTypesToWrite, readTypes: healthKitTypesToRead) { (success, error) -> Void in
if( completion != nil )
{
if (success == true) {
self.initialized = true
}
completion(success:success,error:error)
}
}
Then you can query for the data:
// 4. Build the Predicate
let past = NSDate.distantPast() as NSDate
let now = NSDate()
let mostRecentPredicate = HKQuery.predicateForSamplesWithStartDate(past, endDate:now, options: .None)
// Build the sort descriptor to return the samples in descending order
let sortDescriptor = NSSortDescriptor(key:HKSampleSortIdentifierStartDate, ascending: false)
// we want to limit the number of samples returned by the query to just 1 (the most recent)
let limit = 1
// 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)
}
FYI, After enable Healthkit in your AppID.You have to authenticate require from health store.
let healthKitStore:HKHealthStore = HKHealthStore()
func authorizeHealthKit(completion: ((success:Bool, error:NSError!) -> Void)!)
{
// 1. Set the types you want to read from HK Store
var healthKitTypesToRead = self.dataTypesToRead() as! Set<NSObject>
// 2. Set the types you want to write to HK Store
var healthKitTypesToWrite = self.dataTypesToWrite() as! Set<NSObject>
// 3. If the store is not available (for instance, iPad) return an error and don't go on.
if !HKHealthStore.isHealthDataAvailable()
{
let error = NSError(domain: "com.domain.....", code: 2, userInfo: [NSLocalizedDescriptionKey:"HealthKit is not available in this Device"])
if( completion != nil )
{
completion(success:false, error:error)
}
return;
}
else
{
// 4. Request HealthKit authorization
healthKitStore.requestAuthorizationToShareTypes(healthKitTypesToWrite, readTypes:healthKitTypesToRead, completion: { (success, error) -> Void in
if( completion != nil )
{
completion(success:success,error:error)
}
})
}
}
Useful link as below:
https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HealthKit_Framework/index.html#//apple_ref/doc/uid/TP40014707
https://en.wikipedia.org/wiki/Health_%28application%29
http://www.raywenderlich.com/86336/ios-8-healthkit-swift-getting-started
https://developer.apple.com/videos/wwdc/2014/#203