V02max is always populated with 0.0 - swift

I am developing an app, which aims to analyze running workouts including the cardio fitness. Therefore I am trying to read inter alia the vo2max value from HKWorkout.
This works fine for other measures, but V02max is always populated with 0.0:
avgWatts = workout
.statistics(for: .init(.runningPower))?
.averageQuantity()?
.doubleValue(for: .watt()) ?? 0
vo2max = workout
.statistics(for: .init(.vo2Max))?
.maximumQuantity()?
.doubleValue(for: HKUnit(from: "ml/kg*min")) ?? 0.0
I am the opinion that I also have the permission correctly set:
func authorizationDetails(){
let allTypes = Set([HKObjectType.workoutType(),
HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)!,
HKObjectType.quantityType(forIdentifier: .distanceCycling)!,
HKObjectType.quantityType(forIdentifier: .distanceWalkingRunning)!,
HKObjectType.quantityType(forIdentifier: .heartRate)!,
HKObjectType.quantityType(forIdentifier: .runningStrideLength)!,
HKObjectType.quantityType(forIdentifier: .vo2Max)!])
healthStore.requestAuthorization(toShare: allTypes, read: allTypes) { (success, error) in
if !success {
// Handle the error here.
}else{
self.isAuthorized=true
}
}
}
I am not sure what the problem is and I cannot find the respective information in apples documentation. Is that quantity type maybe not available in HKWorkout?
I was also trying to use sumQuantity instead of maximumQuantity, but it still does not provide values:
vo2max = workout
.statistics(for: .init(.vo2Max))?
.sumQuantity()?
.doubleValue(for: HKUnit(from: "ml/kg*min")) ?? 0.0
In other apps (e.g. Health) I can see the vo2max value, so I assume that the data is actually available.
Thank you in advance for any suggestions.

The unit for vo2max is ml/kg/min not ml/kg*min. It's returning 0.0 because that's what you assign when it's nil. To debug further, I would verify each step of the expression to make sure it's what you expect it to be. Does the statistics call return a value? Does it have an averageQuantity? etc. (But I strongly expect the problem is the last transform, since it's the wrong unit.)
Also definitely make sure that you're looking at a workout that actually has a VO2Max value attached. It often isn't recorded (it has a lot of requirements).

Related

how can core data values be used in if statements

I have a value that goes up by one every time a question is correctly answered (learn_streak) I am trying to check if this value is bigger than a value in core data (bsVoLearn, type: Int64)
the aim is to check whether or not the learn_streak is larger than bsVoLearn and if it is to set bsVoLearn to learn_streak
I have tried this in a function within content view
if learn_streak >= student.bsVoLearn { // error on this line
let student = Student(context: moc)
student.bsVoLearn = Int64(learn_streak)
try? moc.save()
}
however this didn't work (the error is that it can't find student in scope)
here is the solution I have found,
let student = Student(context: moc)
if learn_streak >= student.bsVoLearn {
student.bsVoLearn = Int64(learn_streak)
try? moc.save()
}

Health Kit: How to set 'totalEnergyBurned' for workout?

Today I worked for the first time with Apple Health Kit and successfully saved a workout in Health with the basic informations (activityType, start and end).
My app is in basic function an interval timer where you can create your own workout. Now in the next step I want to add the calories burned in the workout. This information is stored in the attribute 'totalEnergyBurned'.
Do I need to calculate this value myself or can I query this value directly if the user is wearing an Apple Watch? Or maybe the value is even automatically added to the workout if there is the corresponding record? (So far I have only tested the app in the simulator, which is why I can't answer this possibility).
My current code:
func saveToHealthKit(entryID: String){
if HKHealthStore.isHealthDataAvailable() {
let healthStore = HKHealthStore()
if(healthStore.authorizationStatus(for: HKObjectType.workoutType()) == .sharingAuthorized && healthStore.authorizationStatus(for: HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.activeEnergyBurned)!) == .sharingAuthorized){
let newWorkout = HKWorkout(activityType: HKWorkoutActivityType.highIntensityIntervalTraining, start: entry.date!, end: Date())
healthStore.save(newWorkout) { success, error in
guard success else {
// Perform proper error handling here.
return
}
// Add detail samples here.
}
}
}
}
}
Thanks :)
I am not sure I understood your case correctly, but if the user is using an Apple Watch and does a workout you can query HealthKit for an HKWorkout object, which holds a totalEnergyBurned attribute.
If you are not querying this HKWorkout object, then I believe you can query for the activeEnergyBurned quantity type using the start and end date-time of the user workout in the predicate.

Simultaneous transactions in Firebase Realtime Database are failing to get most recent data (Swift)

I'm trying to make multiple updates to my firebase realtime database at the same time using transactions, but when I do this the calls aren't waiting for one another, and instead I only get one update instead of the seven I should be getting.
Below is a segment of my code that is called 7 times in a for loop:
global.newGame.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in
if let post = currentData.value as? NSDictionary {
let player0Info = post["player0Info"] as? NSDictionary
var serverHand = player0Info!["Hand"] as? Array<Int>
serverHand?.append(2)
print("The value of serverHand is \(serverHand ?? [-1])")
global.newGame.child("player0Info/Hand").setValue(serverHand)
}
return TransactionResult.success(withValue: currentData)
}) { (error, committed, snapshot) in
if let error = error {
print(error.localizedDescription)
}
}
And the output it gives is
The value of serverHand is [2]
The value of serverHand is [2]
The value of serverHand is [2]
The value of serverHand is [2]
The value of serverHand is [2]
The value of serverHand is [2]
Is there a way to prevent this from happening? Why doesn't putting this inside of a transaction keep it from being simultaneously changed by multiple calls?
As a side note, everything works as expected when I make these calls in spread-out intervals, I only get this issue from overlapping transactions.
I'm not an iOS dev, so expect typos/syntax errors.
From what I can tell, the way you handle the transaction is incorrect. When using a transaction, you ask for the "latest" data (see note), mutate that data, then send it back. However, in your code you are getting the data, mutating it and then at the same time, both overriding it using setValue and sending the changed data back. Because you are using setValue here, you create a loose infinite loop - where sometimes the transaction finishes first (everything is okay) and sometimes the setValue finishes first (the transaction is tried again).
A transaction is not a one-time operation, if the data at the location you are using is modified, the transaction can be retried. In this case, the call to setValue can trigger one of these retries.
If serverHand.append() mutates the array (i.e. you don't need to also add player0Info!["Hand"] = serverHand.append()), then simply removing the setValue should fix your problem.
Lastly, as pointed out by #Jay in this answer's comments, you need to also set currentData.value with the new data to commit any changes.
global.newGame.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in
if let post = currentData.value as? NSDictionary {
let player0Info = post["player0Info"] as? NSDictionary
var serverHand = player0Info!["Hand"] as? Array<Int>
serverHand?.append(2)
print("The value of serverHand is \(serverHand ?? [-1])")
// note that the setValue was removed
currentData.value = post; // link new data for updating
}
return TransactionResult.success(withValue: currentData)
}) { (error, committed, snapshot) in
if let error = error {
print(error.localizedDescription)
}
}
Note: I used "latest" in quotes because the first time a transaction is tried, the locally cached data is used to process the transaction. If the server's data is different from what's cached locally, the transaction will be processed a second time.
Edit 1: Added #Jay's contributions and removed brief references to the log messages being abnormal, as if called in a loop, multiple logs are to be expected.

AudioKit - How do you tap an AKMicrophone's data to an array of doubles?

I need to get the data from AKMicrophone in raw form so i can make a custom plot. All of the examples from AudioKit use their built in plots, but I need to use a plot I made. The input my plot is expecting is an array of Doubles, but im not very worried about typing since I can change that. I just cant get a tap to access the data working correctly. I have already looked at these:
https://groups.google.com/forum/#!searchin/audiokit/tap%7Csort:date/audiokit/V16xV7zZzPM/wuJjmhG8BwAJ
AudioKit - How to get Real Time floatChannelData from Microphone?
but these answers really just show examples from the audiokit examples which aren't helpful for what i need
Here is my attempt, which instantly crashes saying "required condition is false: [AVAEGraphNode.mm:851:CreateRecordingTap: (nullptr == Tap())]
2018-12-27 13:13:25.628188-0700"
mic.avAudioNode.installTap(onBus: 0, bufferSize:
AVAudioFrameCount(bufferSize), format: nil) { [weak self] (buffer, _) in
guard let strongSelf = self else {
AKLog("Unable to create strong reference to self")
return
}
buffer.frameLength = AVAudioFrameCount(strongSelf.bufferSize)
let offset = Int(buffer.frameCapacity - buffer.frameLength)
if let tail = buffer.floatChannelData?[0] {
print(tail)
}
}

Unexpectedly unwrapping an optional to find a nil after an API call to Spotify

So I know this may be a bit specific but I've been staring at my code and am unable to resolve this issue. Basically, I'm making a network call to spotify to obtain a certain playlist and pass a number that will ultimately determine the number of songs I get back. The code is basically as follows:
// A network call is made just above to return somePlaylist
let playlist = somePlaylist as! SPTPartialPlaylist
var songs: [SPTPartialTrack] = []
// load in playlist to receive back songs
SPTPlaylistSnapshot.playlistWithURI(playlist.uri, session: someSession) { (error: NSError!, data: AnyObject!) in
// cast the data into a correct format
let playlistViewer = data as! SPTPlaylistSnapshot
let playlist = playlistViewer.firstTrackPage
// get the songs
for _ in 1...numberOfSongs {
let random = Int(arc4random_uniform(UInt32(playlist.items.count)))
songs.append(playlist.items[random] as! SPTPartialTrack)
}
}
The problem comes at the portion of code that initializes random. In maybe 1 in 20 calls to this function I, for whatever, reason unwrap a nil value for playlist.items.count and can't seem to figure out why. Maybe it's something I don't understand about API calls or something else I'm failing to see but I can't seem to make sense of it.
Anyone have any recommendations on addressing this issue or how to go about debugging this?
Ok, after sleeping on it and working on it some more I seem to have resolved the issue. Here's the error handling I implemented into my code.
if let actualPlaylist = playlist, actualItems = actualPlaylist.items {
if actualItems.count == 0 {
SongScraper.playlistHasSongs = false
print("Empty playlist, loading another playlist")
return
}
for _ in 1...numberOfSongs {
let random = Int(arc4random_uniform(UInt32(actualItems.count)))
songs.append(actualPlaylist.items[random] as! SPTPartialTrack)
}
completionHandler(songs: songs)
}
else {
print("Returned a nil playlist, loading another playlist")
SongScraper.playlistHasSongs = false
return
}