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.
Related
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 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.
I'm trying to obtain the steps from the last 7 days, but I could not find how to do it. What i would like to receive is an array of 7 elements in which every element is a Day with it respectives total steps. I currently have this code, which obtains today's steps:
//Gets the steps
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, error) in
guard let result = result, let sum = result.sumQuantity() else {
print("Failed to fetch steps = \(error?.localizedDescription ?? "N/A")")
completion(0.0)
return
}
DispatchQueue.main.async {
completion(sum.doubleValue(for: HKUnit.count()))
}
}
healthKitStore.execute(query)
}
And I call the function like this:
getTodaysSteps { (steps) in
self.stepsNumber = Int(steps)
}
Try using HKStatisticsCollectionQuery, which will do the date math for you and bucket the results automatically. Here's an example that should provide the step counts for the last 7 days:
let stepsQuantityType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
let now = Date()
let exactlySevenDaysAgo = Calendar.current.date(byAdding: DateComponents(day: -7), to: now)!
let startOfSevenDaysAgo = Calendar.current.startOfDay(for: exactlySevenDaysAgo)
let predicate = HKQuery.predicateForSamples(withStart: startOfSevenDaysAgo, end: now, options: .strictStartDate)
let query = HKStatisticsCollectionQuery.init(quantityType: stepsQuantityType,
quantitySamplePredicate: predicate,
options: .cumulativeSum,
anchorDate: startOfSevenDaysAgo,
intervalComponents: DateComponents(day: 1))
query.initialResultsHandler = { query, results, error in
guard let statsCollection = results else {
// Perform proper error handling here...
}
statsCollection.enumerateStatistics(from: startOfSevenDaysAgo, to: now) { statistics, stop in
if let quantity = statistics.sumQuantity() {
let stepValue = quantity.doubleValueForUnit(HKUnit.countUnit())
// ...
}
}
}
There's even more simpler solution here.
func getTotalSteps(forPast days: Int, completion: #escaping (Double) -> Void) {
// Getting quantityType as stepCount
guard let stepsQuantityType = HKObjectType.quantityType(forIdentifier: .stepCount) else {
print("*** Unable to create a step count type ***")
return
}
let now = Date()
let startDate = Calendar.current.date(byAdding: DateComponents(day: -days), to: now)
let predicate = HKQuery.predicateForSamples(withStart: startDate, 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()))
}
execute(query)
}
Now to call just use the following code:
HKHealthStore().getTotalSteps(forPast: 30) { totalSteps in
print(totalSteps)
}
The only change you have to implement is to change the Date object you supply as the startWith parameter to your HKStatisticsQuery. You can create a Date object representing the start of the day 7 days ago by first going back exactly 7 days in time using Calendar.date(byAdding:,to:), then calling startOfDay(for:) on that object.
let now = Date()
let exactlySevenDaysAgo = Calendar.current.date(byAdding: DateComponents(day: -7), to: now)!
let startOfSevenDaysAgo = Calendar.current.startOfDay(for: exactlySevenDaysAgo)
let predicate = HKQuery.predicateForSamples(withStart: startOfSevenDaysAgo, end: now, options: .strictStartDate)
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.