I am building an App in Swift for a IOS App. I just want to display the last saved heartrate of the Healthkit. Can someone help me here?
my Authorization request:
static func authorizeHealthKit() {
let HeartRateData: Set = [
HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!,
]
HealthStore.requestAuthorization(toShare: HeartRateData, read: HeartRateData) {_,_ in }
}
#IBAction func authorizeTapped(_ sender: AnyObject) {
HeartrateViewController.authorizeHealthKit()
}
get heart rate:
func fetchHeartRateWithCompletionHandler (_ completionHandler: #escaping (Double?, NSError?)-> ()) {
let now = Date()
let startDate = now
let endDate = now
let HeartRateSampleType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
let HeartRate = HKStatisticsQuery(quantityType: HeartRateSampleType!, quantitySamplePredicate: predicate, options: .cumulativeSum)
{ query, result, error in
if result != nil {
completionHandler(nil, error as NSError?)
return
}
var HeartRateData = 0.0
if let quantitiy = result!.sumQuantity() {
let heartRateUnit = HKUnit(from: "count/min")
HeartRateData = quantitiy.doubleValue(for: heartRateUnit)
}
completionHandler(HeartRateData, error as NSError?)
}
HeartRateTextField.text = String (describing: HeartRate)
}
but it doesn't display anything in the heart rate text field.
Are you executing the query?
Also, the query you are creating is not very efficient.
Your start date and end date are same, and predicate will not find heartRate sample in between them.
If you are looking for most recent heart rate sample, read about anchoredObjectQuery.
Related
I can able to get my health data list from health kit. I used below code to get my today's step count :
func getTodaysSteps(completion: #escaping (Double) -> Void) {
let stepsQuantityType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
let now = Date()
let startOfDay = Calendar.current.startOfDay(for: now)
let predicate = HKQuery.predicateForSamples(
withStart: startOfDay,
end: now,
options: .strictStartDate
)
let query = HKStatisticsQuery(
quantityType: stepsQuantityType,
quantitySamplePredicate: predicate,
options: .cumulativeSum
) { _, result, _ in
guard let result = result, let sum = result.sumQuantity() else {
completion(0.0)
return
}
completion(sum.doubleValue(for: HKUnit.count()))
}
healthStore.execute(query)
}
But i have one more health data of my brother, where he invited me to see his health data in my real device. Now i was not able to read / get his health data. Is there any way to get that.
Any solution would be great ...!
You don't want a statics query if you want the step count. Try this;
let comp: DateComponents = Calendar.current.dateComponents([.year, .month], from: Date())
let startOfMonth = Calendar.current.date(from: comp)!
searchPredicate = HKQuery.predicateForSamples(withStart: startOfMonth, end: Date(), options: .strictStartDate)
let sampleQuery = HKSampleQuery(sampleType: .stepCount, predicate: searchPredicate, limit: limit, sortDescriptors: [])
{
(query, result, error) in
if error != nil
{
completion(-1)
}
self.hkSampleRecs.removeAll(keepingCapacity: true)
if result != nil
{
for r in result!
{
self.hkSampleRecs.append(r)
}
}
completion(self.hkSampleRecs.count)
}
healthStore.execute(sampleQuery)
Note that I haven't shown you the completion setup I used. I'm also storing the records in an array I defined elsewhere (hkSampleRecs). This example also gives data only for this month so far. Change the dates as you need.
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 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: [])
private func readDataFromHealthKit(_ forIdentifier: HKQuantityTypeIdentifier,_ completionHandler : #escaping (Double?,NSError?) -> Void) {
let endDate = currentDate
let startDate = calendar.startOfDay(for: currentDate)
let type = HKSampleType.quantityType(forIdentifier: forIdentifier)
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: [])
// The actual HealthKit Query which will fetch all of the steps and sub them up for us.
let statisticsQuery = HKStatisticsQuery(quantityType: type!, quantitySamplePredicate: predicate, options: .cumulativeSum) { (query, results, error) in
var resultCount = 0.0
if let result = results?.sumQuantity() {
if forIdentifier == .distanceWalkingRunning {
resultCount = result.doubleValue(for: HKUnit.meter())
} else {
resultCount = result.doubleValue(for: HKUnit.count())
}
}
completionHandler(resultCount,nil)
}
healthKitStore.execute(statisticsQuery)
}
I am fetching steps and distance from healthkit. When I call this function first time i get wrong value. But if this function called again I get proper result.
I am getting today steps from healthkit using below code.
func retrieveStepCount(completion: (stepRetrieved: Double) -> Void) {
let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
let date = NSDate()
let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let newDate = cal.startOfDayForDate(NSDate())
let predicate = HKQuery.predicateForSamplesWithStartDate(newDate, endDate: NSDate(), options: .None) // Our search predicate which will fetch all steps taken today
let interval: NSDateComponents = NSDateComponents()
interval.day = 1
let query = HKStatisticsCollectionQuery(quantityType: type!, quantitySamplePredicate: predicate, options: .CumulativeSum, anchorDate: newDate as NSDate, intervalComponents:interval as NSDateComponents)
query.initialResultsHandler = { query, results, error in
if error != nil {
print("Something went Wrong")
return
}
if let myResults = results{
myResults.enumerateStatisticsFromDate(newDate, toDate: NSDate()) {
statistics, stop in
if let quantity = statistics.sumQuantityForSource(HKSource.defaultSource()) {
let steps = quantity.doubleValueForUnit(HKUnit.countUnit())
print("Steps = \(Int(steps))")
completion(stepRetrieved: steps)
}
}
}
}
executeQuery(query)
}
Now lets say I have these steps in total
From which I have some steps which were auto detected by device. and some were added by some other application to heathkit.
I do want both of them and I m getting both of them but the problem comes when user some some manuall steps to the healthkit.
I do not want to get these manually added steps. So basically I want to get (5,793 - 2300) = 3493 steps.
How can I do that ? I have tried to get name of HKSource I do know that when user enter steps manually, name of the source is "Health" but how do I filter steps on this base ? Please guide me about this and what am I missing here ? Thanks in advance
This might not be the best solution, But I believe it will work. What you can do is get all the steps which were added manually using HKSampleQuery. here is an example.
func todayManuallyAddedStepsSteps(completion: (Double, NSError?) -> () )
{
let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount) // The type of data we are requesting
let date = NSDate()
let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let newDate = cal.startOfDayForDate(date)
let predicate = HKQuery.predicateForSamplesWithStartDate(newDate, endDate: NSDate(), options: .None) // Our search predicate which will fetch all steps taken today
// The actual HealthKit Query which will fetch all of the steps and add them up for us.
let query = HKSampleQuery(sampleType: type!, predicate: predicate, limit: 0, sortDescriptors: nil) { query, results, error in
var steps: Double = 0
if results?.count > 0
{
for result in results as! [HKQuantitySample]
{
print("Steps \(result.quantity.doubleValueForUnit(HKUnit.countUnit()))")
print()
// checking and adding manually added steps
if result.sourceRevision.source.name == "Health" {
// these are manually added steps
print(result.sourceRevision.source.name)
print("Steps \(result.quantity.doubleValueForUnit(HKUnit.countUnit()))")
steps += result.quantity.doubleValueForUnit(HKUnit.countUnit())
}
else{
// these are auto detected steps which we do not want from using HKSampleQuery
}
}
print(steps)
}
completion(steps, error)
}
executeQuery(query)
}
and then get the today total steps using HKStatisticsCollectionQuery like below
func TodayTotalSteps(completion: (stepRetrieved: Double) -> Void) {
let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount) // The type of data we are requesting
let calendar = NSCalendar.currentCalendar()
let interval = NSDateComponents()
interval.day = 1
let anchorComponents = calendar.components([.Day , .Month , .Year], fromDate: NSDate())
anchorComponents.hour = 0
let anchorDate = calendar.dateFromComponents(anchorComponents)
let stepsQuery = HKStatisticsCollectionQuery(quantityType: type!, quantitySamplePredicate: nil, options: .CumulativeSum, anchorDate: anchorDate!, intervalComponents: interval)
stepsQuery.initialResultsHandler = {query, results, error in
let endDate = NSDate()
let startDate = calendar.dateByAddingUnit(.Day, value: 0, toDate: endDate, options: [])
if let myResults = results{ myResults.enumerateStatisticsFromDate(startDate!, toDate: endDate) { statistics, stop in
if let quantity = statistics.sumQuantity(){
let date = statistics.startDate
let steps = quantity.doubleValueForUnit(HKUnit.countUnit())
print("\(date): steps = \(steps)")
completion(stepRetrieved: steps)
}
}
}
}
executeQuery(stepsQuery)
}
Now you can call these methods and subtract manually added steps like below
todayManuallyAddedSteps({ (steps , error) in
if error != nil{
print(error)
}
else{
// truncating manuall steps
TodayTotalSteps({ (stepRetrieved) in
// steps without manuall steps
print(Int(stepRetrieved - steps))
})
}
})
You should create HKSourceQuery predicate and compound it with predicate you want to use. In my case i did
fileprivate static func configureSourcePredicate(identifier: HKQuantityTypeIdentifier, completion: #escaping(NSPredicate?) -> Void) {
var deviceSources : Set<HKSource> = Set()
let appleHealth = "com.apple.health"
let handler : (HKSourceQuery, Set<HKSource>?, Error?) -> Void = { query , sources , error in
if sources == nil || error != nil {
completion(nil)
return
}
for source in sources! {
if source.bundleIdentifier.hasPrefix(appleHealth){
deviceSources.insert(source)
}
}
completion(HKQuery.predicateForObjects(from: deviceSources))
}
let sampleType = HKQuantityType.quantityType(forIdentifier: identifier)
let sourceQuery = HKSourceQuery(sampleType: sampleType!, samplePredicate: nil, completionHandler: handler)
healthStore?.execute(sourceQuery)
}
fileprivate static func request(type identifier: HKQuantityTypeIdentifier, startDate : Date, interval : DateComponents, completion : #escaping(TYPE_OF_DATA) -> Void) {
configureSourcePredicate(identifier: identifier, completion: { sourcePredicate in
let type = HKSampleType.quantityType(forIdentifier: identifier)
var predicate = HKQuery.predicateForSamples(withStart: startDate, end: Date(), options: [.strictStartDate, .strictEndDate])
if sourcePredicate != nil {
predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate , sourcePredicate!])
}
let query = HKStatisticsCollectionQuery(quantityType: type!, quantitySamplePredicate: predicate, options: [.cumulativeSum], anchorDate: Date(), intervalComponents: interval)
query.initialResultsHandler = { query, results, error in
if error != nil || results == nil {
return
}
//your code here
DispatchQueue.main.async {
completion(RETURNED_DATA)
}
}
healthStore?.execute(query)
})
}
First Configure the HKSources, which indicates where we have to get the health data. (com.apple.health, com.apple.Health ...)
func configureSourcePredicate() {
self.queryGroup = DispatchGroup()
self.deviceSources = Set()
//Only get the health data from the apple health without manually added steps
let appleHealth = "com.apple.health"
self.queryGroup?.enter()
let sourcesQueryCompletionHandler : CompletionBlock = { (query , sources , error) in
s
if let deviceSourcesLocal = sources {
for source in deviceSourcesLocal {
if source.bundleIdentifier.hasPrefix(appleHealth){
self.deviceSources?.insert(source)
print("Device sources are \(source.bundleIdentifier)")
}
}
self.queryGroup?.leave()
}
}
let sampleType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)
let sourceQuery = HKSourceQuery(sampleType: sampleType!, samplePredicate: nil, completionHandler: sourcesQueryCompletionHandler)
self.healthStore.execute(sourceQuery)
self.queryGroup?.notify(queue: DispatchQueue.global(), execute: {
self.todayTotalSteps()
}) }
//Step count access from the data sources
func todayTotalSteps() {
let completionHandler: (HKStatisticsQuery, HKStatistics?, Error?) -> Void = {
(_query, result, error) -> Void in
DispatchQueue.main.async {
print("Result is \(result)")
if let quantity: HKQuantity = result?.sumQuantity() {
let steps = quantity.doubleValue(for: HKUnit.count())
print("Steps = \(steps)")
self.stepsCount.text = "\(steps)"
self.queryGroup?.leave()
}
}
}
let stepsCount = HKQuantityType.quantityType(forIdentifier: .stepCount)
let predicate = predicateForSamplesToday()
self.queryGroup?.enter()
let query = HKStatisticsQuery(quantityType: stepsCount!, quantitySamplePredicate: predicate, options: HKStatisticsOptions.cumulativeSum, completionHandler: completionHandler)
if (self.healthStore) != nil {
self.healthStore.execute(query)
}}
//Creating predicate
private func predicateForSamplesToday() -> NSPredicate
{
print("Device sources \(self.deviceSources)")
let (starDate, endDate): (Date, Date) = self.datesFromToday()
var predicate: NSPredicate = HKQuery.predicateForSamples(withStart: starDate, end: endDate, options: HKQueryOptions.strictStartDate)
if deviceSources == self.deviceSources {
predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate , HKQuery.predicateForObjects(from: deviceSources!)])
}
return predicate
}