Get heartRateVariabilitySDNN during LiveWorkoutSession at WatchOS - ios

I've a made a simple watchOS app to run a running session and monitor heart rate variability(sdnn). Running on a simulator I succeed to get other p-s like distance, heart rate or calories, but not sdnn.
Here is my way to setup a WorkoutManager:
func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) {
//
}
func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, didCollectDataOf collectedTypes: Set<HKSampleType>) {
for type in collectedTypes{
print(type)
guard let quantityType = type as? HKQuantityType else {return}
let statistics = workoutBuilder.statistics(for: quantityType)
updateForStatistics(statistics)
}
}
}
And this piece is fetching data in realtime:
func updateForStatistics(_ statistics: HKStatistics?) {
guard let statistics = statistics else { return }
DispatchQueue.main.async {
switch statistics.quantityType {
case HKQuantityType.quantityType(forIdentifier: .heartRate):
let heartRateUnit = HKUnit.count().unitDivided(by: HKUnit.minute())
self.heartRate = statistics.mostRecentQuantity()?.doubleValue(for: heartRateUnit) ?? 0
self.averageHeartRate = statistics.averageQuantity()?.doubleValue(for: heartRateUnit) ?? 0
case HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned):
let energyUnit = HKUnit.kilocalorie()
self.activeEnergy = statistics.sumQuantity()?.doubleValue(for: energyUnit) ?? 0
case HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning), HKQuantityType.quantityType(forIdentifier: .distanceCycling):
let meterUnit = HKUnit.meter()
self.distance = statistics.sumQuantity()?.doubleValue(for: meterUnit) ?? 0
case HKQuantityType.quantityType(forIdentifier: .heartRateVariabilitySDNN):
let sdnnUnit = HKUnit.count()
self.sdnn = statistics.mostRecentQuantity()?.doubleValue(for: sdnnUnit) ?? 0
default:
return
}
}
}
As mentioned, all other p-s are emulating by WatchOS excluding sdnn - here I always getting no data.
Also, I know how to write my own sdnn values through the HKQuantitySample, but need the specific ones tracked by OS during the session. Or some workaround to force OS to save this for me.
Any ideas, please?

The ssdUnit should be HKUnit(from: "ms")

Unfortunately, You cannot access heartRateVariabilitySDNN parameter because it is not measured during workout. However, you can manually trigger measurement of HRV by using Breathe exercise in Mindfulness app

Related

HealthKit Blood Oxygen SPO2

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

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.

How to fetch Resting energy value from HealthKit which has same value as Health App's value?

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.

Solve memory issue with migrating a big bunch of data

In my previous version of my app I stored all user data into .dat files (with NSKeyedArchiver), but in my new version I want to upgrade to a real(m) database.
I'm trying to import all of this data (and that can be a LOT) into Realm. But it's taking so much memory that the debugger eventually kill my app before the migration has finished. The 'strange' thing is that the data on hard disk is only 1.5 mb big, but it's taking memory for more than 1gb so I'm doing something wrong.
I also tried to work with multiple threads, but that didn't help. Well it speeded up the migration process (which is good), but it also took the same amount of memory.
Who can help me out? See my code below for more information..
FYI Async can be found here https://github.com/duemunk/Async
import Async
let startDate = NSDate(timeIntervalSince1970: 1388534400).startOfDay // Start from 2014 jan 1st
let endDate = NSDate().dateByAddingTimeInterval(172800).startOfDay // 2 days = 3600 * 24 * 2 = 172.800
var pathDate = startDate
let calendar = NSCalendar.currentCalendar()
let group = AsyncGroup()
var allPaths = [(Int, Int)]()
while calendar.compareDate(pathDate, toDate: endDate, toUnitGranularity: .Month) != .OrderedDescending {
// Components
let currentMonth = calendar.component(.Month, fromDate: pathDate)
let currentYear = calendar.component(.Year, fromDate: pathDate)
allPaths.append((currentYear, currentMonth))
// Advance by one month
pathDate = calendar.dateByAddingUnit(.Month, value: 1, toDate: pathDate, options: [])!
}
for path in allPaths {
group.background {
// Prepare path
let currentYear = path.0
let currentMonth = path.1
let path = (Path.Documents as NSString).stringByAppendingPathComponent("Stats_\(currentMonth)_\(currentYear).dat")
print(path)
if NSFileManager.defaultManager().fileExistsAtPath(path) {
NSKeyedUnarchiver.setClass(_OldStatisticsDataModel.self, forClassName: "StatisticsDataModel")
if let statistics = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? [_OldStatisticsDataModel] {
// Loop through days
for i in 1...31 {
let dateComponents = NSDateComponents()
dateComponents.year = currentYear
dateComponents.month = currentMonth
dateComponents.day = i
dateComponents.hour = 0
dateComponents.minute = 0
// Create date from components
let userCalendar = NSCalendar.currentCalendar() // user calendar
guard let date = userCalendar.dateFromComponents(dateComponents) else {
continue
}
// Search for order items
let filtered = statistics.filter {
if let date = $0.date {
let dateSince1970 = date.timeIntervalSince1970
return date.startOfDay.timeIntervalSince1970 <= dateSince1970 && date.endOfDay.timeIntervalSince1970 >= dateSince1970
}
return false
}
if filtered.isEmpty == false {
// Create order
let transaction = Transaction()
transaction.employee = Account.API().administratorEmployee()
let order = Order()
order.status = PayableStatus.Paid
order.createdDate = date.timeIntervalSince1970
order.paidDate = date.timeIntervalSince1970
// Loop through all found items
for item in filtered {
// Values
let price = (item.price?.doubleValue ?? 0.0) * 100.0
let tax = (item.tax?.doubleValue ?? 0.0) * 100.0
// Update transaction
transaction.amount += Int(price)
// Prepare order item
let orderItem = OrderItemm()
orderItem.amount = item.amount
orderItem.price = Int(price)
orderItem.taxPercentage = Int(tax)
orderItem.name = item.name ?? ""
orderItem.product = Product.API().productForName(orderItem.name, price: orderItem.price, tax: orderItem.taxPercentage)
// Add order item to order
order.orderItems.append(orderItem)
}
if order.orderItems.isEmpty == false {
print("\(date): \(order.orderItems.count) order items")
// Set transaction for order
order.transactions.append(transaction)
// Save the order
Order.API().saveOrders([order])
}
}
}
}
}
}
}
group.wait()
As in the comments of my question was suggested by #bdash, autoreleasepool did the trick.
I use AsyncSwift as syntactic sugar for grand central dispatch, but when using block groups the group keeps a reference to the block which caused that the memory wasn't released. I still make use of a group now but I leave the group after it's finished.
I provided my code example below, to make things clearer. Using multiple threads gave me incredibly (like 10 times faster) more performance while memory won't go above 150MB. Previously app was crashing at 1,3GB due to memory pressure.
let group = AsyncGroup()
var allPaths = [(Int, Int)]()
// Some logic to fill the paths -> not interesting
for path in allPaths {
group.enter() // The following block will be added to the group
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
autoreleasepool { // Creating an autoreleasepool to free up memory for the loaded statistics
// Stripped unnecessary stuff
if NSFileManager.defaultManager().fileExistsAtPath(path) {
// Load the statistics from .dat files
NSKeyedUnarchiver.setClass(_OldStatisticsDataModel.self, forClassName: "StatisticsDataModel")
if let statistics = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? [_OldStatisticsDataModel] {
// Loop through days
for i in 1...31 {
autoreleasepool {
// Do the heavy stuff here
}
}
}
}
}
group.leave() // Block has finished, now leave the group
}
}
group.wait()

Convert Kelvin into Celsius in Swift

I'm trying to retrieve the temperature from the users current location.
I am using the API from OpenWeatherMap. The problem is, they provide the temperature in Kelvin as default, and I would like it in Celsius.
I understand that I just need to subtract 273.15 from the kelvin value....? But i'm struggling to figure out where to do that.
My code for setting my labels:
var jsonData: AnyObject?
func setLabels(weatherData: NSData) {
do {
self.jsonData = try NSJSONSerialization.JSONObjectWithData(weatherData, options: []) as! NSDictionary
} catch {
//handle error here
}
if let name = jsonData!["name"] as? String {
locationLabel.text = "using your current location, \(name)"
}
if let main = jsonData!["main"] as? NSDictionary {
if let temperature = main["temp"] as? Double {
self.tempLabel.text = String(format: "%.0f", temperature)
}
}
}
Can anyone help me get this right please, as I'm really not sure where to start, thanks.
Let me know if you need to see more of my code.
if let kelvinTemp = main["temp"] as? Double {
let celsiusTemp = kelvinTemp - 273.15
self.tempLabel.text = String(format: "%.0f", celsiusTemp)
}
or simply
self.tempLabel.text = String(format: "%.0f", temperature - 273.15)
For Swift 4.2:
Use a Measurement Formatter.
let mf = MeasurementFormatter()
This method converts one temperature type (Kelvin, Celsius, Fahrenheit) to another:
func convertTemp(temp: Double, from inputTempType: UnitTemperature, to outputTempType: UnitTemperature) -> String {
mf.numberFormatter.maximumFractionDigits = 0
mf.unitOptions = .providedUnit
let input = Measurement(value: temp, unit: inputTempType)
let output = input.converted(to: outputTempType)
return mf.string(from: output)
}
Usage:
let temperature = 291.0
let celsius = convertTemp(temp: temperature, from: .kelvin, to: .celsius) // 18°C
let fahrenheit = convertTemp(temp: temperature, from: .kelvin, to: .fahrenheit) // 64°F
To output the localized temperature format, remove the line mf.unitOptions = .providedUnit
From the code above, it seems to me the right place to do this would be right after you get the temperature
if let temperatureInKelvin = main["temp"] as? Double {
let temperatureInCelsius = temperatureInKelvin - 273.15
self.tempLabel.text = String(format: "%.0f", temperature)
}
In the future though, I would probably parse your JSON values in a separate class and store them in a model object which you can call later on.
A simpler example of the above handy function (updated for Swift 5.3) would be something like:
func convertTemperature(temp: Double, from inputTempType: UnitTemperature, to outputTempType: UnitTemperature) -> Double {
let input = Measurement(value: temp, unit: inputTempType)
let output = input.converted(to: outputTempType)
return output.value
}
Here:
self.tempLabel.text = String(format: "%.0f", temperature - 273.15)
or you can do it here (pseudo syntax as I don't know Swift that well):
if let temperature = (main["temp"] as? Double) - 273.15 {
self.tempLabel.text = String(format: "%.0f", temperature)
}

Resources