Parsing results from query of HealthKit - swift

How do you parse information received from a query? I am looking to get the active calories burned using the following:
func getActiveEnergy() {
let calendar = NSCalendar.current
let now = Date()
let components = calendar.dateComponents([.year, .month, .day], from: now)
guard let startDate = calendar.date(byAdding: .day, value: -7, to: now) else {
fatalError("*** Unable to create the start date")
}
print("startDate -> \(startDate)")
guard let endDate = calendar.date(from: components) else {
fatalError("*** Unable to create the end date")
}
print("endDate -> \(endDate)")
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: [])
let sort = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: true)
guard let sampleType = HKSampleType.quantityType(forIdentifier: HKQuantityTypeIdentifier.activeEnergyBurned) else {
fatalError("*** This method should never fail ***")
}
let query = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: Int(HKObjectQueryNoLimit), sortDescriptors: [sort]) { (query, results, error) in
guard let samples = results as? [HKQuantitySample] else {
print("There was an error creating the query")
return
}
for sample in samples {
print("sample from query -> \(sample)")
}
DispatchQueue.main.async {
}
}
self.healthKit.execute(query)
}
The results produce a string of information:
0.055 kcal 0FBBA46F-F9C0-4BB7-87F2-84B567900084 "Kevin’s Series 4" (6.2.1), "Watch5,2" (6.2.1)"Apple Watch" (2020-04-14 17:05:27 -0400 - 2020-04-14 17:05:37 -0400)
I only want the value (double) at the beginning of the string.
Thank you for your assistance.

Related

HealthKit Fetch Workout Time, Distance, Avg Heart Rate

I was able to fetch some of the details separated from HealthKit app, but i have a missing points that i need help with such as getting the below exact data for each workout.
And here is what i tried for Exercise time:
func getExerciseTime(forDate date: Date, _ completion: ((Double, Error?) -> Void)!) {
let cal = Calendar.current
let startDate = cal.startOfDay(for: date)
var comps = DateComponents()
comps.day = 1
comps.second = -1
let endDate = cal.date(byAdding: comps, to: startDate)
let stepsCount = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.appleExerciseTime)
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
let interval: NSDateComponents = NSDateComponents()
interval.day = 1
let query = HKStatisticsCollectionQuery(quantityType: stepsCount!, quantitySamplePredicate: predicate, options: [.cumulativeSum], anchorDate: startDate as Date, intervalComponents: interval as DateComponents)
query.initialResultsHandler = { query, results, error in
if error != nil {
// Something went Wrong
return
}
var exercise = 0.0
if let myResults = results, let endDate = endDate {
myResults.enumerateStatistics(from: startDate, to: endDate) { statistics, _ in
if let quantity = statistics.sumQuantity() {
exercise = quantity.doubleValue(for: HKUnit.minute())
}
}
}
completion(round(exercise), error)
}
healthKitStore.execute(query)
}
I was able to get the beats but not the average heart rate for the workout:
func getAverageHeartRate(date:Date,completion:
#escaping ([HKWorkout]?, Error?) -> Void) {
guard let heartRateType =
HKObjectType.quantityType(forIdentifier:
HKQuantityTypeIdentifier.heartRate) else {
fatalError("*** Unable to create a Heart rate type ***")
}
let cal = Calendar.current
let startDate = cal.startOfDay(for: date)
var comps = DateComponents()
comps.day = 1
comps.second = -1
let endDate = cal.date(byAdding: comps, to: startDate)
// Get all workouts that only came from this app.
let workoutPredicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
let startDateSort = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: true)
let query = HKSampleQuery(sampleType: heartRateType,
predicate: workoutPredicate,
limit: 0,
sortDescriptors: [startDateSort]) { (sampleQuery, results, error) -> Void in
guard let heartRateSamples = results as? [HKQuantitySample] else {
// Perform proper error handling here.
return
}
// Use the workout's Heart rate samples here.
for heartrate in heartRateSamples {
let beats: Double? = heartrate.quantity.doubleValue(for: HKUnit.count().unitDivided(by: HKUnit.minute()))
print(heartrate.startDate, heartrate.endDate)
print(beats)
guard beats != nil else {
return
}
}
}
healthKitStore.execute(query)
And lastly:
func getWorkOutData(forDate date: Date, _ completion: ((Int, Error?) -> Void)!) {
let cal = Calendar.current
let startDate = cal.startOfDay(for: date)
var comps = DateComponents()
comps.day = 1
comps.second = -1
let endDate = cal.date(byAdding: comps, to: startDate)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: true)
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: HKQueryOptions())
let sampleQuery = HKSampleQuery(sampleType: HKWorkoutType.workoutType(), predicate: predicate, limit: 0, sortDescriptors: [sortDescriptor]) { (_, results, error ) -> Void in
var eligible = 0
if let myResults = results as? [HKWorkout] {
for workout in myResults where workout.duration >= 600 {
eligible += 1
}
}
completion(eligible, error)
}
healthKitStore.execute(sampleQuery)
}
1- I was able to get the heartbeats not the avg heart rate per workout.
2- I couldn't connect the data from each one to be like a workout model that contains Time,Distance,AvgHeart rate.

How do I display a string in View in Swift from HKSampleQuery

since this is my first Swift project and I am new to programming in general. I need some help with extracting the sample data I fetched from the query and printing it in the console. I am looking for a way to extract latestHr from the local scope and display it in the View in the app itself which is in the scope below:
var body: some View {
return VStack {
}
but I couldn't find a way to do it correctly.
This is most of the code I am using and the parts for calculation and fetching.
func latestheartRate(){
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: HKSampleSortIdentifierEndDate, 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)
let date = DateFormatter()
date.dateFormat = "HH:mm E, d MMM y"
let StartDate = date.string(from: data.startDate)
let EndDate = date.string(from: data.endDate)
print("Latest Hr\(latestHr) BPM // StartDate \(StartDate): EndDate \(EndDate)")
let HR = String(latestHr)
}
healthStore.execute(query)
}
//trigger the function to authorize read and write heart rate
init()
{
authorizeHealthKit()
}
var body: some View {
return VStack {
}

Swift How to Return from Function

I can't find a way to have the makeQuery() function return the summary result. How do I do this? I want to be able to call makeQuery() and have it return summary. summary is an object holding some data. HKActivitySummary to be specific.
func makeQuery() {
let calendar = NSCalendar.current
let endDate = Date()
guard let startDate = calendar.date(byAdding: .day, value: 0, to: endDate) else {
fatalError("error")
}
let units: Set<Calendar.Component> = [.day, .month, .year, .era]
var startDatecomps = calendar.dateComponents(units, from: startDate)
startDatecomps.calendar = calendar
var endDatecomps = calendar.dateComponents(units, from: endDate)
endDatecomps.calendar = calendar
let summariesWithinRange = HKQuery.predicate(
forActivitySummariesBetweenStart: startDatecomps, end: endDatecomps)
let query = HKActivitySummaryQuery(predicate: summariesWithinRange) {
(sample, results, error) -> Void in
if let results = results {
if let summary = results.first {
print(summary) // how to have makeQuery() return the summary value? Just printing it to the console right now to confirm data is being retrieved
}
}
}
healthstore.execute(query)
}

Most liked item from last 7 days?

I am using Firestore for my app and would like to be able to get the most liked item posted within the last 7 days. In an ideal world the call would look something like:
let calendar = Calendar.current
let components = calendar.dateComponents([.year, .month, .day], from: Date())
let start = calendar.date(from: components)!
let startDate = calendar.date(byAdding: .day, value: -7, to: start)!
let startDateStamp: Timestamp = Timestamp(date: startDate)
let mostLiked = colRef
.whereField("isReported", isEqualTo: false)
.whereField("timestamp", isGreaterThanOrEqualTo: startDateStamp)
.order(by: "likeCount", descending: true)
.limit(to: 1)
mostLiked.getDocuments { (snapshot, error) in
if let error = error { return }
guard let snap = snapshot else { return }
for document in snap.documents {
let data = document.data()
print(data)
}
}
But this is obviously not allowed.
I have ended up with:
let calendar = Calendar.current
let components = calendar.dateComponents([.year, .month, .day], from: Date())
let start = calendar.date(from: components)!
let startDate = calendar.date(byAdding: .day, value: -7, to: start)!
let startDateStamp: Timestamp = Timestamp(date: startDate)
let mostLiked = colRef
.whereField("isReported", isEqualTo: false)
.whereField("timestamp", isGreaterThanOrEqualTo: startDateStamp)
.order(by: "timestamp", descending: true)
mostLiked.getDocuments { (snapshot, error) in
if let error = error { return }
guard let snap = snapshot else { return }
for document in snap.documents {
let data = document.data()
...
data handling code to fill struct - newTopItem
...
if self.topLikedArray.isEmpty {
self.topLikedArray.append(newTopItem)
} else {
if likeCount > self.topLikedArray[0].likeCount! {
self.topLikedArray.removeAll()
self.topLikedArray.append(newTopItem)
}
}
}
print("Top Item: \(self.topLikedArray)")
}
Which fetches everything within the last 7 days and I'll have to manually sort through to get the max likeCount, which seems a little clumsy to me not to mention the scalability issue it presents as I can't limit the results to just 1. Is there a better way to do this?
let mostLiked = bleatsRef
.whereField("isReported", isEqualTo: false)
.whereField("timestamp", isGreaterThanOrEqualTo: startDateStamp)
.order(by: "likeCount", descending: true)
.limit(1)
mostLiked.getDocuments { (snapshot, error) in
guard
error == nil,
let snap = snapshot
else { return }
print("The most liked document is: \(snap.documents.first)")
}

How do I get this the currentStandHour value in Apple Watch iOS?

I want to retrieve the value that indicates whether or not the user has stood this hour. I also want to be able to retrieve the StandHours count for the day.
Here are the Apple links that I've been trying to understand in order get the value from HealthKit. I provide these links to help provide understanding for what I'm looking for and also to help you answer my question.
appleStandHour type property: https://developer.apple.com/documentation/healthkit/hkcategorytypeidentifier/1615539-applestandhour
HealthKit category type identifier: https://developer.apple.com/documentation/healthkit/hkcategorytypeidentifier
HealthKit constants: https://developer.apple.com/documentation/healthkit/healthkit_constants
Bruno's answer is only half of the answer. For example, his standUnit variable is how he pulls the # of hours that the user has stood today. I tested it. Also, I made the assumption that it had to be pulled from within the scope of the summaries variable.
I have found another question on StackOverflow that might provide some clues. I think they managed to pull a value via a HKCategoryTypeIdentifier: Watch os 2.0 beta: access heart beat rate
Here's my attempted code as far as I have been able to get:
import UIKit
import HealthKit
import HealthKitUI
class ViewController: UIViewController {
let hkStoreOnVC : HKHealthStore = HKHealthStore()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
authorizeHealthKit()
hkTest()
hkTest2()
}
func authorizeHealthKit() { //-> Bool {
print("health kit authorize?")
let healthStore = HKHealthStore()
let objectTypes: Set<HKObjectType> = [
HKObjectType.activitySummaryType()
]
healthStore.requestAuthorization(toShare: nil, read: objectTypes) { (success, error) in
// Authorization request finished, hopefully the user allowed access!
print("health kit authorized")
}
}
func hkTest() {
print("health kit test.")
let calendar = Calendar.autoupdatingCurrent
var dateComponents = calendar.dateComponents(
[ .year, .month, .day ],
from: Date()
)
// This line is required to make the whole thing work
dateComponents.calendar = calendar
let predicate = HKQuery.predicateForActivitySummary(with: dateComponents)
//----------------------
let query = HKActivitySummaryQuery(predicate: predicate) { (query, summaries, error) in
print("query")
guard let summaries = summaries, summaries.count > 0
else {
print("no summaries")
return
}
// Handle data
for thisSummary in summaries {
// print("for each summary")
let standUnit = HKUnit.count()
let standHours = thisSummary.appleStandHours.doubleValue(for: standUnit)
print("stand hours \(standHours)")
}//end for
} //end query
}
func hkTest2() {
var isEnabled = true
print ("authorize health kit" )
if HKHealthStore.isHealthDataAvailable() {
let stepsCount = NSSet(objects: HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount ) )
for thisValue in stepsCount {
// thisValue.
print("thisValue: \(thisValue)")
}
print(" authorize HK - steps count \(stepsCount) ")
}
// Create the date components for the predicate
guard let calendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian) else {
fatalError("*** This should never fail. ***")
}
let endDate = NSDate()
guard let startDate = calendar.date(byAdding: .day, value: -7, to: endDate as Date, options: []) else {
fatalError("*** unable to calculate the start date ***")
}
let units: NSCalendar.Unit = [.day, .month, .year, .era]
var startDateComponents = calendar.components(units, from: startDate)
startDateComponents.calendar = calendar as Calendar
var endDateComponents = calendar.components(units, from: endDate as Date)
endDateComponents.calendar = calendar as Calendar
// Create the predicate for the query
let summariesWithinRange = HKQuery.predicate(forActivitySummariesBetweenStart: startDateComponents, end: endDateComponents)
// Build the query
let query = HKActivitySummaryQuery(predicate: summariesWithinRange) { (query, summaries, error) -> Void in
guard let activitySummaries = summaries else {
guard let queryError = error else {
fatalError("*** Did not return a valid error object. ***")
}
// Handle the error here...
return
}
for thisSummary in activitySummaries {
// print("for each summary")
let standUnit = HKUnit.count()
let standHours = thisSummary.appleStandHours.doubleValue(for: standUnit)
// let stoodThisHourMaybe = thisSummary.appleStandHours.categ //doubleValue(for: standUnit)
//\(thisSummary.description) //stand unit _\(standUnit)_
print("day#\(thisSummary.dateComponents(for: calendar as Calendar).day) stand hours \(standHours) ")
}//end for
// Do something with the summaries here...
for thisItem in activitySummaries {
//thisItem.appleStandHours
print("abc \( thisItem.appleStandHours ) " )
}//end for
}
// Run the query
let hkStore : HKHealthStore = HKHealthStore()
hkStore.execute(query)
//***
let aStandHour = HKCategoryType.categoryType(forIdentifier: .appleStandHour)
// This is the type you want updates on. It can be any health kit type, including heart rate.
// let distanceType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.distanceWalkingRunning)
// Match samples with a start date after the workout start
// let predicate = .predicat //( , endDate: nil, options: .None)
// let theDate : Date =
let thepredicate = HKQuery.predicateForCategorySamples(with: .greaterThanOrEqualTo, value: 0) //.predicateForSamplesWithStartDate(startDate , endDate: nil, options: .None)
// predicate
// let predicate = . //(theDate , endDate: nil, options: .None)
let hka : HKQueryAnchor = HKQueryAnchor(fromValue: 0)
let sHourQuery = HKAnchoredObjectQuery(type: aStandHour!, predicate: thepredicate, anchor: hka, limit: 0, resultsHandler: { ( query, samples, deletedObjects, anchor, error) -> Void in
// Handle when the query first returns results
// TODO: do whatever you want with samples (note you are not on the main thread)
print("getting here A?")
// for thisSample in samples! {
// print("A smpLType \(thisSample.sampleType) thisSample \(thisSample)")
// }
})
// This is called each time a new value is entered into HealthKit (samples may be batched together for efficiency)
sHourQuery.updateHandler = { (query, samples, deletedObjects, anchor, error) -> Void in
// Handle update notifications after the query has initially run
// TODO: do whatever you want with samples (note you are not on the main thread)
print("getting here B?")
for thisSample in samples! {
print("B smpLType \(thisSample.sampleType) thisSample \(thisSample)")
}
}
// Start the query
self.hkStoreOnVC.execute(sHourQuery)
//***
}//end func
func myCompletionHandler(bTest: Bool ) {
print("my completion handler")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}//end viewController Class
Here's the code output - the log never prints to "getting here b?":
health kit authorize?
health kit test.
authorize health kit
health kit authorized
thisValue: HKQuantityTypeIdentifierStepCount
authorize HK - steps count {(
HKQuantityTypeIdentifierStepCount
)}
2017-11-04 19:18:30.100562-0500 watchapptest[25048:4695625] refreshPreferences: HangTracerEnabled: 0
2017-11-04 19:18:30.100600-0500 watchapptest[25048:4695625] refreshPreferences: HangTracerDuration: 500
2017-11-04 19:18:30.100615-0500 watchapptest[25048:4695625] refreshPreferences: ActivationLoggingEnabled: 0 ActivationLoggingTaskedOffByDA:0
getting here A?
day#Optional(28) stand hours 14.0
day#Optional(29) stand hours 14.0
day#Optional(30) stand hours 14.0
day#Optional(31) stand hours 14.0
day#Optional(1) stand hours 16.0
day#Optional(2) stand hours 13.0
day#Optional(3) stand hours 15.0
day#Optional(4) stand hours 13.0
abc 14 count
abc 14 count
abc 14 count
abc 14 count
abc 16 count
abc 13 count
abc 15 count
abc 13 count
I am new to HealthKit, so there probably is a nicer way to do this. But this seems to work for me. I check the actually standing minutes and call the completion handler with minutes > 0.
private let store = HKHealthStore()
func askPermission() {
let standType = HKQuantityType.quantityType(forIdentifier: .appleStandTime)!
store.requestAuthorization(toShare: [], read: [standType], completion: { (success, error) in
self.didStandThisHour { (didStand) in
print("Did stand this hour: \(didStand)")
}
})
}
func didStandThisHour(_ didStand: #escaping (Bool) -> ()) {
let store = HKHealthStore()
let calendar = Calendar.autoupdatingCurrent
let dateComponents = calendar.dateComponents([.year, .month, .day, .hour], from: Date())
let endDate = Date()
let startDate = calendar.date(from: dateComponents)!
let standTime = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.appleStandTime)!
var interval = DateComponents()
interval.hour = 1
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
let query = HKStatisticsCollectionQuery(quantityType: standTime, quantitySamplePredicate: predicate, options: [.cumulativeSum], anchorDate: startDate, intervalComponents:interval)
query.initialResultsHandler = { query, results, error in
guard error == nil, let myResults = results else {
fatalError("Something is wrong with HealthKit link")
}
myResults.enumerateStatistics(from: startDate, to: endDate, with: { (statistics, stop) in
guard let quantity = statistics.sumQuantity() else {
didStand(false)
return
}
let minutes = quantity.doubleValue(for: .minute())
didStand(minutes > 0)
})
}
store.execute(query)
}
Ok, if you want to retrieve today's activity ring info (including stand hours) you first request user authorization for the object type you want to retrieve:
let healthStore = HKHealthStore()
let objectTypes: Set<HKObjectType> = [
HKObjectType.activitySummaryType()
]
healthStore.requestAuthorization(toShare: nil, read: objectTypes) { (success, error) in
// Authorization request finished, hopefully the user allowed access!
}
Then you can use this predicate to retrieve today's date:
let calendar = Calendar.autoupdatingCurrent
var dateComponents = calendar.dateComponents(
[ .year, .month, .day ],
from: Date()
)
// This line is required to make the whole thing work
dateComponents.calendar = calendar
let predicate = HKQuery.predicateForActivitySummary(with: dateComponents)
Create a query...
let query = HKActivitySummaryQuery(predicate: predicate) { (query, summaries, error) in
guard let summaries = summaries, summaries.count > 0
else {
return
}
// Handle data
}
The data you'll receive is of type HKActivitySummary and you can retrieve, for example:
let sandUnit = HKUnit.count()
let standHours = summary.appleStandHours.doubleValue(for: standUnit)