HealthKit Blood Oxygen SPO2 - ios

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)
}

Related

How to get workout heart rate details durations and calculate data like Fat burn/Build fitness

I am building an apple health kit app and now I want to generate how much time users spent on workouts and how much time spent on certain states like extreme, fat burn and some others.
Like this following image:
So far I have got workouts from sample query like this.
func readWorkouts() async -> [HKWorkout]? {
clearAllWorkoutData()
let predicate = HKQuery.predicateForSamples(withStart: startDate.getDateOnly().toDateOnly(), end: endDate, options: .strictStartDate)
let samples = try! await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[HKSample], Error>) in
store.execute(HKSampleQuery(sampleType: .workoutType(), predicate: predicate, limit: HKObjectQueryNoLimit,sortDescriptors: [.init(keyPath: \HKSample.startDate, ascending: false)], resultsHandler: { query, samples, error in
if let hasError = error {
continuation.resume(throwing: hasError)
return
}
guard let samples = samples else {
fatalError("*** Invalid State: This can only fail if there was an error. ***")
}
continuation.resume(returning: samples)
}))
}
guard let workouts = samples as? [HKWorkout] else {
return nil
}
for item in workouts {
Task{
await
self.calculateOtherWorkoutData(startDate: item.startDate, endDate: item.endDate, item: item)
}
}
return workouts
}
Then I called a function to calculate those data.
func calculateOtherWorkoutData(startDate:Date,endDate:Date,item:HKWorkout) async{
let hrType = HKQuantityType.quantityType(forIdentifier: .heartRate)!
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
let samples = try! await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[HKSample], Error>) in
store.execute(HKSampleQuery(sampleType: hrType, predicate: predicate, limit: HKObjectQueryNoLimit,sortDescriptors: [.init(keyPath: \HKSample.startDate, ascending: false)], resultsHandler: { query, samples, error in
if let hasError = error {
continuation.resume(throwing: hasError)
return
}
guard let samples = samples else {
fatalError("*** Invalid State: This can only fail if there was an error. ***")
}
continuation.resume(returning: samples)
}))
}
guard let heartrates = samples as? [HKQuantitySample] else {
return
}
for heartrate in heartrates {
let beats: Double? = heartrate.quantity.doubleValue(for: HKUnit.count().unitDivided(by: HKUnit.minute()))
print(heartrate.startDate, heartrate.endDate)
guard beats != nil else {
return
}
let workoutDiff = item.duration
if beats! > 0 && beats! < 93{
self.warmthTime += heartrate.endDate.timeIntervalSince(heartrate.startDate)
}
if beats! > 93 && beats! < 111{
self.fatBurnTime += heartrate.endDate.timeIntervalSince(heartrate.startDate)
}
if beats! > 111 && beats! < 130{
self.buildFitTime += heartrate.endDate.timeIntervalSince(heartrate.startDate)
}
if beats! > 130 && beats! < 148{
self.highIntTime += heartrate.endDate.timeIntervalSince(heartrate.startDate)
}
if beats! > 167{
self.extremeTime += heartrate.endDate.timeIntervalSince(heartrate.startDate)
}
}
}
But I am stuck on getting those durations. if I use total workout durations it calculates again and again. The health kit is measuring heart rates on times but not with durations.
I also tried statistical queries but it only gives those averages and only one heart rate data is coming for one workout like that.
Does anyone have an idea how to accomplish this like in the image I attached?

How to differentiate sources with HealthKit sleep query

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.

Can you retrieve different HKQuantity sources together?

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

Getting yesterdays steps from HealthKit

I'm building an app for personal use, and I am currently stuck on how to accurately get yesterdays steps from the healthkit. And then from there, placing it into a variable (should be easy, I know).
I have a HealthKitManager class that calls the function from inside a view, and then appends that to a variable from that same view.
I have scoured most of the healthKit questions, and I get back data, but I don't think it is accurate data. My phone data from yesterday is 1442 steps, but it's returning 2665 steps. On top of that, when I try to put the data is a variable it prints out as 0.
HealthKitManagerClass
import Foundation
import HealthKit
class HealthKitManager {
let storage = HKHealthStore()
init()
{
checkAuthorization()
}
func checkAuthorization() -> Bool
{
// Default to assuming that we're authorized
var isEnabled = true
// Do we have access to HealthKit on this device?
if HKHealthStore.isHealthDataAvailable()
{
// We have to request each data type explicitly
let steps = NSSet(object: HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)!)
// Now we can request authorization for step count data
storage.requestAuthorizationToShareTypes(nil, readTypes: steps as? Set<HKObjectType>) { (success, error) -> Void in
isEnabled = success
}
}
else
{
isEnabled = false
}
return isEnabled
}
func yesterdaySteps(completion: (Double, NSError?) -> ())
{
// The type of data we are requesting (this is redundant and could probably be an enumeration
let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
// Our search predicate which will fetch data from now until a day ago
// (Note, 1.day comes from an extension
// You'll want to change that to your own NSDate
let calendar = NSCalendar.currentCalendar()
let yesterday = calendar.dateByAddingUnit(.Day, value: -1, toDate: NSDate(), options: [])
//this is probably why my data is wrong
let predicate = HKQuery.predicateForSamplesWithStartDate(yesterday, endDate: NSDate(), options: .None)
// The actual HealthKit Query which will fetch all of the steps and sub 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]
{
steps += result.quantity.doubleValueForUnit(HKUnit.countUnit())
}
}
//I'm unsure if this is correct as well
completion(steps, error)
print("\(steps) STEPS FROM HEALTH KIT")
//this adds the steps to my character (is this in the right place)
Player.User.Gold.addSteps(Int(steps))
}
//not 100% on what this does, but I know it is necessary
storage.executeQuery(query)
}}
ViewControllerClass
import UIKit
import Foundation
class UpdateViewController: UIViewController {
#IBOutlet var back: UIButton!
let HKM = HealthKitManager()
var stepsFromPhone = Double()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
back.transform = CGAffineTransformMakeRotation(CGFloat(M_PI_2))
HKM.yesterdaySteps(){ steps, error in
self.stepsFromPhone = steps
}
Player.User.Gold.addSteps(Int(stepsFromPhone))
print(Player.User.Gold.getSteps(), "IN PLAYER")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The output from
print(Player.User.Gold.getSteps(), "IN PLAYER")
is
0 IN PLAYER
The output from
print("\(steps) STEPS FROM HEALTH KIT")
is
2665.0 STEPS FROM HEALTH KIT
so, basically my questions are:
what NSDate() do I need for the whole of yesterday?
how do I take the steps from the yesterdaySteps() and correctly place them into a variable in the UpdateViewController?
Thank you for any help!
This is the method I am using in my healthStore class
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()
var steps = 0.0
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
steps = quantity.doubleValueForUnit(HKUnit.countUnit())
// print("\(date): steps = \(steps)")
}
completion(stepRetrieved: steps)
}
} else {
completion(stepRetrieved: steps)
}
}
executeQuery(stepsQuery)
}
and here is How I am using it
func getStepsData() {
// I am sendng steps to my server thats why using this variable
var stepsToSend = 0
MyHealthStore.sharedHealthStore.todayManuallyAddedSteps({ (steps , error) in
if error != nil{
// handle error
}
else{
// truncating manuall steps
MyHealthStore.sharedHealthStore.TodayTotalSteps({ (stepRetrieved) in
stepsToSend = Int(stepRetrieved - steps)
})
}
})
}
and here is the function used above for manually added steps which we are truncating in order to get exact steps
func todayManuallyAddedSteps(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]
{
// checking and adding manually added steps
if result.sourceRevision.source.name == "Health" {
// these are manually added steps
steps += result.quantity.doubleValueForUnit(HKUnit.countUnit())
}
else{
// these are auto detected steps which we do not want from using HKSampleQuery
}
}
completion(steps, error)
} else {
completion(steps, error)
}
}
executeQuery(query)
}
I hope it helps. Let me know if you face any issue.
You can use HKStatisticsQuery
let quantityType = HKSampleType.quantityType(forIdentifier: .stepCount)!
let predicate = HKQuery.predicateForSamples(
withStart: startDate,
end: endDate,
options: [.strictStartDate, .strictEndDate]
)
let query = HKStatisticsQuery(
quantityType: quantityType,
quantitySamplePredicate: predicate,
options: .cumulativeSum) { (query, result, error) in
guard let result = result, error == nil else {
print("HeathService error \(String(describing: error))")
return
}
callback(result)
}

Query HealthKit for HKCategoryTypeIdentifierSleepAnalysis

I have built a method that imports a sleep sample but I can't get it to return the proper value for hours asleep.
The method to query for sleep data looks like this:
func updateHealthCategories() {
let categoryType = HKObjectType.categoryTypeForIdentifier(HKCategoryTypeIdentifierSleepAnalysis)
let start = NSDate(dateString:"2015-11-04")
let end = NSDate(dateString:"2015-11-05")
let categorySample = HKCategorySample(type: categoryType!,
value: HKCategoryValueSleepAnalysis.Asleep.rawValue,
startDate: start,
endDate: end)
self.hoursSleep = Double(categorySample.value)
print(categorySample.value)
}
The date is formatted like this:
extension NSDate
{
convenience
init(dateString:String) {
let dateStringFormatter = NSDateFormatter()
dateStringFormatter.dateFormat = "yyyy-MM-dd"
dateStringFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
let d = dateStringFormatter.dateFromString(dateString)!
self.init(timeInterval:0, sinceDate:d)
}
}
I'm calling data from November 4-5, which contains this data:
However, the categorySample.value returns 1 instead of 3.
The value you are accessing is the category sample value, an HKCategoryType, and not the number of hours of sleep.
The definition for HKCategoryTypeIdentifierSleepAnalysis
typedef enum : NSInteger {
HKCategoryValueSleepAnalysisInBed,
HKCategoryValueSleepAnalysisAsleep,
} HKCategoryValueSleepAnalysis;
defines two possible values, 0 or 1 where the value of 1 matches HKCategoryValueSleepAnalysisAsleep.
Getting the hours asleep requires setting up a HKSampleQuery.
The code looks something like this:
if let sleepType = HKObjectType.categoryTypeForIdentifier(HKCategoryTypeIdentifierSleepAnalysis) {
let predicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: endDate, options: .None)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
let query = HKSampleQuery(sampleType: sleepType, predicate: predicate, limit: 30, sortDescriptors: [sortDescriptor]) { (query, tmpResult, error) -> Void in
if let result = tmpResult {
for item in result {
if let sample = item as? HKCategorySample {
let value = (sample.value == HKCategoryValueSleepAnalysis.InBed.rawValue) ? "InBed" : "Asleep"
print("sleep: \(sample.startDate) \(sample.endDate) - source: \(sample.source.name) - value: \(value)")
let seconds = sample.endDate.timeIntervalSinceDate(sample.startDate)
let minutes = seconds/60
let hours = minutes/60
}
}
}
}
healthStore.executeQuery(query)
}
I summarized this from http://benoitpasquier.fr/sleep-healthkit/.
Here is Swift 5, iOS 16 compatible answer if someone is still looking. You can parse/operate data as per your needs.
func getSleepAnalysis() {
let healthStore = HKHealthStore()
let endDate = Date()
guard let startDate = Calendar.current.date(byAdding: .day, value: -7, to: endDate) else {
fatalError("*** Unable to create the start date ***")
}
// first, we define the object type we want
guard let sleepType = HKObjectType.categoryType(forIdentifier: .sleepAnalysis) else {
return
}
// we create a predicate to filter our data
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
// I had a sortDescriptor to get the recent data first
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
// we create our query with a block completion to execute
let query = HKSampleQuery(sampleType: sleepType, predicate: predicate, limit: Int(HKObjectQueryNoLimit), sortDescriptors: [sortDescriptor]) { (query, result, error) in
if error != nil {
// handle error
return
}
if let result = result {
// do something with those data
result
.compactMap({ $0 as? HKCategorySample })
.forEach({ sample in
guard let sleepValue = HKCategoryValueSleepAnalysis(rawValue: sample.value) else {
return
}
let isAsleep = sleepValue == .asleep
print("HealthKit sleep \(sample.startDate) \(sample.endDate) - source \(sample.sourceRevision.source.name) - isAsleep \(isAsleep)")
})
}
}
// finally, we execute our query
healthStore.execute(query)
}
I hope you'll get the authorization for SleepAnalysis before this so data is retrived.

Resources