As a disclaimer, I am new to swift and am mainly relying on other coding experience. I am working on an app that predicts the blood glucose drop of a Type 1 Diabetic after exercise. This requires the input of multiple different Healthkit quantities, including active calories, insulin, and blood glucose, but I have had trouble fetching these quantities to do math with them in the same place. I am working on the foundation of SpeedySloth, Apple's example workout app, and this is how they retrieve Healthkit data:
case HKQuantityType.quantityType(forIdentifier: .heartRate):
/// - Tag: SetLabel
let heartRateUnit = HKUnit.count().unitDivided(by: HKUnit.minute())
let value = statistics.mostRecentQuantity()?.doubleValue(for: heartRateUnit)
let roundedValue = Double( round( 1 * value! ) / 1 )
label.setText("\(roundedValue) BPM")
case HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned):
let energyUnit = HKUnit.kilocalorie()
let value = statistics.sumQuantity()?.doubleValue(for: energyUnit)
let roundedValue = Double( round( 1 * value! ) / 1 )
label.setText("\(roundedValue) cal\n")
As you can see, a new case is created every time a quantity is recalled, and each one is called separately. However, is there any way to call them together, or multiple at once, so I can work with the quantities? I tried other methods, including a Predicate within one of the cases, but that did not seem to work. Any suggestions would be appreciated.
Since you cannot query for multiple quantities within the same query, is it possible to nest a predicate query within the quantity recall so that I could work with both variables? Here is what I tried to put a predicate query within the active energy case:
let energyUnit = HKUnit.kilocalorie()
let value = statistics.sumQuantity()?.doubleValue(for: energyUnit)
let roundedValue = Double( round( 1 * value! ) / 1 )
let quantityType = HKObjectType.quantityType(forIdentifier:(HKQuantityTypeIdentifier.bloodGlucose))
let mostRecentPredicate = HKQuery.predicateForSamples(withStart: Date.distantPast,
end: Date(),
options: .strictEndDate)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate,
ascending: false)
let limit = 1
let sampleQuery = HKSampleQuery(sampleType: quantityType!,
predicate: mostRecentPredicate,
limit: limit,
sortDescriptors: [sortDescriptor]) { (query, samples, error) in
let sample = samples?.first as? HKQuantitySample
let BG = sample?.quantity
let ESF = 0.125
let TotBGChg = -1 * ESF * roundedValue
let roundedTBGC = round(TotBGChg)
label.setText("\(roundedValue) cal\n\(String(describing: BG)) mg/dL")
}
return
Related
With the series 6 Apple Watch, you can now get a measure of your SP02, hemoglobin content in your blood oxygen. The health app on the iPhone shows you all the measurements in the Respiratory section. This is a critical component for COVID patients.
I have not been able to find anyway to access this information programatically.
I have checked all HKObjectTypes in the latest Apple documentation. Is this information currently available to iOS developers?
Any information would be of great use as several researchers are requesting it.
Ok, I am being told that this is the same as Oxygen Saturation.Here is the code I use to query HK for Oxygen Saturation:
// Get SPO2
func getOxygenSaturation()
{
// Type is SPO2 SDNN
let osType:HKQuantityType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.oxygenSaturation)!
let predicate = HKQuery.predicateForSamples(withStart: Date.distantPast, end: Date(), options: .strictEndDate)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)
let osUnit:HKUnit = HKUnit(from: "%")
let osQuery = HKSampleQuery(sampleType: osType,
predicate: predicate,
limit: 10,
sortDescriptors: [sortDescriptor]) { (query, results, error) in
guard error == nil else { print("error"); return }
// Get the array of results from the sample query
let sampleArray:[HKSample]? = results!
// Loop through the array of rsults
for (_, sample) in sampleArray!.enumerated()
{
// Be sure something is there
if let currData:HKQuantitySample = sample as? HKQuantitySample
{
let os: Double = (currData.quantity.doubleValue(for: osUnit) * 100.0)
let d1: Date = currData.startDate
let str1 = SwiftLib.returnDateAndTimeWithTZ(date: d1, info: self.info!)
Dispatch.DispatchQueue.main.async {
self.tvOxygenValue.text = String(format: "%.0f%#", os, "%");
self.tvOxygenDate.text = str1
//print("\(os)");
}
}
}
print("Done")
self.loadAndDisplayActivityInformation()
}
healthStore!.execute(osQuery)
}
I'm currently using the following code to query for the number of hours the user was asleep in the last 24 hours:
func getHealthKitSleep() {
let healthStore = HKHealthStore()
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
// Get all samples from the last 24 hours
let endDate = Date()
let startDate = endDate.addingTimeInterval(-1.0 * 60.0 * 60.0 * 24.0)
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: [])
// Sleep query
let sleepQuery = HKSampleQuery(
sampleType: HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!,
predicate: predicate,
limit: 0,
sortDescriptors: [sortDescriptor]){ (query, results, error) -> Void in
if error != nil {return}
// Sum the sleep time
var minutesSleepAggr = 0.0
if let result = results {
for item in result {
if let sample = item as? HKCategorySample {
if sample.value == HKCategoryValueSleepAnalysis.asleep.rawValue && sample.startDate >= startDate {
let sleepTime = sample.endDate.timeIntervalSince(sample.startDate)
let minutesInAnHour = 60.0
let minutesBetweenDates = sleepTime / minutesInAnHour
minutesSleepAggr += minutesBetweenDates
}
}
}
self.sleep = Double(String(format: "%.1f", minutesSleepAggr / 60))!
print("HOURS: \(String(describing: self.sleep))")
}
}
// Execute our query
healthStore.execute(sleepQuery)
}
This works great if the user has only one sleep app as the source for the data. The problem is if the user is using 2 sleep apps, for example, as sources, the data will be doubled. How can I differentiate the sources? If able to differentiate the sources, I would like to either only grab data from one source, or maybe take the average of the sources.
When you're looping over the samples, you can access information about the source for each. I only accept a single source, so I just keep a variable of the source name and if the current sample has a different source name I continue looping without processing the data from that sample, but you could combine the data in other ways if you wanted to.
Here's how to access the source info:
if let sample = item as? HKCategorySample {
let name = sample.sourceRevision.source.name
let id = sample.sourceRevision.source.bundleIdentifier
}
There's some more info on the HKSourceRevision object in the docs here.
I'm using apple's HealthKit sample however resting energy value shown in Health app in iPhone doesn't match with the value fetched in sample app.
As per apple docs, HKQuantityTypeIdentifierBasalEnergyBurned is representing resting energy so I fetched this value from the HealthKit but the value I received doesn't match with the resting energy shown in Health App.
So I came across the apple's HealthKit sample where they are calculating resting energy based on formula:
// Calculates the user's total basal (resting) energy burn based off of their height, weight, age,
// and biological sex. If there is not enough information, return an error.
private func fetchTotalBasalBurn(completion: #escaping (HKQuantity?, Error?) -> Void)
{
let todayPredicate: NSPredicate = self.predicateForSamplesToday()
let weightType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyMass)!
let heightType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.height)!
let queryWeigth: HKCompletionHandle = {
(weight, error) -> Void in
guard let weight = weight else {
completion(nil, error)
return
}
let queryHeigth: HKCompletionHandle = {
(height, error) -> Void in
if height == nil {
completion(nil, error)
return;
}
var dateOfBirth: Date!
do {
dateOfBirth = try self.healthStore!.dateOfBirth()
} catch {
completion(nil, error)
return
}
var biologicalSexObjet: HKBiologicalSexObject!
do {
biologicalSexObjet = try self.healthStore!.biologicalSex()
} catch {
completion(nil, error)
return
}
// Once we have pulled all of the information without errors, calculate the user's total basal energy burn
let basalEnergyButn: HKQuantity? = self.calculateBasalBurnTodayFromWeight(weight, height: height, dateOfBirth: dateOfBirth!, biologicalSex: biologicalSexObjet)
completion(basalEnergyButn, nil)
}
if let healthStore = self.healthStore {
healthStore.mostRecentQuantitySample(ofType: heightType, predicate: todayPredicate, completion: queryHeigth)
}
}
if let healthStore = self.healthStore {
healthStore.mostRecentQuantitySample(ofType: weightType, predicate: nil, completion: queryWeigth)
}
}
private func calculateBasalBurnTodayFromWeight(_ weight: HKQuantity?, height: HKQuantity?, dateOfBirth: Date, biologicalSex: HKBiologicalSexObject) -> HKQuantity?
{
// Only calculate Basal Metabolic Rate (BMR) if we have enough information about the user
guard let weight = weight, let height = height else {
return nil
}
// Note the difference between calling +unitFromString: vs creating a unit from a string with
// a given prefix. Both of these are equally valid, however one may be more convenient for a given
// use case.
let heightInCentimeters: Double = height.doubleValue(for: HKUnit(from:"cm"))
let weightInKilograms: Double = weight.doubleValue(for: HKUnit.gramUnit(with: HKMetricPrefix.kilo))
let nowDate = Date()
let ageComponents: DateComponents = Calendar.current.dateComponents([Calendar.Component.year], from: dateOfBirth, to: nowDate)
let ageInYears: Int = ageComponents.year!
// BMR is calculated in kilocalories per day.
let BMR: Double = self.calculateBMRFromWeight(weightInKilograms: weightInKilograms, height: heightInCentimeters, age: ageInYears, biologicalSex: biologicalSex.biologicalSex)
// Figure out how much of today has completed so we know how many kilocalories the user has burned.
let (startOfToday, endOfToday): (Date, Date) = self.datesFromToday()
let secondsInDay: TimeInterval = endOfToday.timeIntervalSince(startOfToday)
let percentOfDayComplete: Double = nowDate.timeIntervalSince(startOfToday) / secondsInDay
let kilocaloriesBurned: Double = BMR * percentOfDayComplete
let basalBurn = HKQuantity(unit: HKUnit.kilocalorie(), doubleValue: kilocaloriesBurned)
return basalBurn
}
/// Returns BMR value in kilocalories per day. Note that there are different ways of calculating the
/// BMR. In this example we chose an arbitrary function to calculate BMR based on weight, height, age,
/// and biological sex.
private func calculateBMRFromWeight(weightInKilograms: Double, height heightInCentimeters: Double, age ageInYears: Int, biologicalSex: HKBiologicalSex) -> Double
{
var BMR: Double = 0
if biologicalSex == .male {
BMR = 66.0 + (13.8 * weightInKilograms) + (5.0 * heightInCentimeters) - (6.8 * Double(ageInYears))
return BMR
}
BMR = 655 + (9.6 * weightInKilograms) + (1.8 * heightInCentimeters) - (4.7 * Double(ageInYears))
return BMR
}
I'm tried the sample app to fetch resting energy however still resting energy value shown in health app and sample app doesn't have same value.
Could any body tell me how to fetch resting energy or what is the calculation used by Health App to find resting energy?
It would be great if someone can give me some pointers on it, I'm pretty new to HealthKit.
Thanks.
It seems that Apple's sample is outdated. As of iOS 8 and watchOS 2 there's a call to retrieve this information in the same way that active calories are retrieved; simply change the identifier. Apple Documentation
HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.basalEnergyBurned)
Don't forget to include the additional permission to read this data as well.
So I have been trying to get the exercise minutes from Healthkit onto my application and store it in a variable.
But the whenever the application is opened on an Iphone connected to an apple wathc, it crashes. I have tried debugging the application, but it works fine on a simulator or my Ipod touch. This the function I am using to retrieve the exercise minutes.
func getExerciseTime(completion: #escaping (Double) -> Void) {
let exerciseQuantityType = HKQuantityType.quantityType(forIdentifier: .appleExerciseTime)!
/*
let now = Date()
let startOfDay = Calendar.current.startOfDay(for: now)
let predicate = HKQuery.predicateForSamples(withStart: startOfDay, end: now, options: .strictStartDate)
*/
var now : Date
var startOfDay : Date
var predicate : NSPredicate
switch dwmValue {
case 0:
now = Date()
startOfDay = Calendar.current.startOfDay(for: now)
predicate = HKQuery.predicateForSamples(withStart: startOfDay, end: now, options: .strictStartDate)
break
case 1:
now = Date()
startOfDay = Calendar.current.startOfDay(for: Date(timeIntervalSinceNow: -60 * 60 * 24 * 7))
predicate = HKQuery.predicateForSamples(withStart: startOfDay, end: now, options: .strictStartDate)
break
case 2:
now = Date()
let wx = -60 * 60 * 24 * 2
startOfDay = Calendar.current.startOfDay(for: Date(timeIntervalSinceNow: TimeInterval((-60 * 60 * 24 * 7 * 4) + wx)))
predicate = HKQuery.predicateForSamples(withStart: startOfDay, end: now, options: .strictStartDate)
break
default:
now = Date()
startOfDay = Calendar.current.startOfDay(for: now)
predicate = HKQuery.predicateForSamples(withStart: startOfDay, end: now, options: .strictStartDate)
break
}
let query = HKStatisticsQuery(quantityType: exerciseQuantityType, quantitySamplePredicate: predicate, options: .cumulativeSum ) { (_, result, error) in
var resultCount = 0.0
guard let result = result else {
//log.error("Failed to fetch steps = \(error?.localizedDescription ?? "N/A")")
completion(resultCount)
return
}
if let sum = result.sumQuantity() {
resultCount = sum.doubleValue(for: HKUnit.count())
}
DispatchQueue.main.async {
completion(resultCount)
print("Exercise time : \(resultCount)")
}
}
healthKitStore.execute(query)
}
This is the code I use in viewdidAppear to store the value from the above function in a global variable
getExerciseTime(){ time in
exerciseTime = time
}
I have no idea why the application keeps crashing on the Iphone. I have tried to change the options in StatisticsQuery but nothing has worked. Please help me out here!! And I know there is no problem with healthkit authentication as it return some data on the simulator and the iPod but crashes on the Iphone that is connected to an apple watch.
When you are summing the quantities, you are using an incompatible type (HKUnit.count()), you need to use a time unit.
resultCount = sum.doubleValue(for: HKUnit.minute())
Also, if you are not doing so already, you need to ask for permission to read
override func viewDidAppear(_ animated: Bool) {
healthKitStore.requestAuthorization(toShare: nil, read: [exerciseQuantityType], completion: { (userWasShownPermissionView, error) in
self.getExerciseTime(){ time in
self.exerciseTime = time
}
})
}
You need to set a usage description in your plist
<key>NSHealthShareUsageDescription</key>
<string>Foo</string>
Aslo your app needs HealthKit capability set in the project target settings.
I want to fill a tableView with the last heard songs. With .nowPlayingItem I can get the very last song, but how can I get the songs heard before?
I think this question already has an answer at Retrieve list of songs ordered by last play time in iOS, but it is in Objective-C and I´m not able to translate it. Or is there an even better way to do it in Swift instead?
You could do something like this:
let startTime: NSTimeInterval = NSDate().timeIntervalSince1970
let songsQuery: MPMediaQuery = MPMediaQuery.songsQuery()
let songsArray: [MPMediaItem] = songsQuery.items!
let songsNSArray : NSArray = NSArray(array: songsArray)
let descriptor: NSSortDescriptor = NSSortDescriptor(key: MPMediaItemPropertyLastPlayedDate, ascending: false)
let sortedResults: NSArray = songsNSArray.sortedArrayUsingDescriptors([descriptor])
let finishTime: NSTimeInterval = NSDate().timeIntervalSince1970
NSLog("Execution took %f seconds to return %i results.", finishTime - startTime, sortedResults.count)
The results would be stored in the sortedResults array
This is how you can do it in Swift,
let start = NSDate().timeIntervalSince1970
let songsQuery = MPMediaQuery.songsQuery()
if let songsArray = songsQuery.items {
let sortedArray = songsArray.sort { item1, item2 in
if let lastPlayedDateForItem1 = item1.lastPlayedDate,
let lastPlayedDateForItem2 = item2.lastPlayedDate {
return lastPlayedDateForItem1.compare(lastPlayedDateForItem2) == .OrderedDescending
}
return false
}
}