I'm making a CrossFit app that has, as one of the features, a stopwatch or timer that the user will start and stop, and when it's stopped, it'll log the exercise to Health Kit, it will calculate the approximate calories based on the exercise intensity and time to complete the exercise, and will finally show a sheet telling the user the number of calories consumed and how much time it took.
The view that contains the stopwatch and the buttons is the following:
struct FooterTimerView: View {
@State private var completedSheetPresented = false
var workout: Workout? = nil
var subworkout: Subworkout? = nil
@Environment(\.managedObjectContext) private var viewContext
@EnvironmentObject var healthViewModel: HealthViewModel
fileprivate func getConsumedCaloriesAndShowSheet(_ completable: Completable, timeInMinutes minutes: Int) async -> Double {
return await healthViewModel.logCompletable(completable, timeInMinutes: minutes, inContext: viewContext) ?? 0.0
}
var body: some View {
var loggedTime = ("", "", "")
var consumedCalories = 0.0
return TimerView { (timeString: String) in
loggedTime = converTimeStringToTimeInterval(timeString)
if let minutes = Int(loggedTime.0), let seconds = Int(loggedTime.1), let miliseconds = Int(loggedTime.2) {
let totalMinutes = minutes + seconds / 60 + (miliseconds / (60 * 1000))
if let workout = workout {
Task {
consumedCalories = await getConsumedCaloriesAndShowSheet(workout, timeInMinutes: totalMinutes)
completedSheetPresented = true
}
} else if let subworkout = subworkout {
Task {
consumedCalories = await getConsumedCaloriesAndShowSheet(subworkout, timeInMinutes: totalMinutes)
completedSheetPresented = true
}
}
}
}.sheet(isPresented: $completedSheetPresented) {
CompletedWorkoutSheet(workoutTime: loggedTime, workoutCalories: consumedCalories)
}
}
private func converTimeStringToTimeInterval(_ timeString: String) -> (String, String, String) {
let timeString = "59:56:23"
let components = timeString.components(separatedBy: ":")
let millis = components[2]
let seconds = components[1]
let minutes = components[0]
return (minutes, seconds, millis)
}
}
To notice in the code, the two variables that the sheet will need (loggedTime
and consumedCalories
) are defined in the body
of the view, so the sheet can use their values, and I'm not sure if there's a better way to do this.
Also, my view TimerView
which is created in the code above has the following code:
struct TimerView: View {
let stopWatchFrameHeight = CGFloat(70)
let stopWatchFontSize = CGFloat(70)
@StateObject var stopWatch = StopWatch()
let onTimerFinished: (String) -> Void
init(onTimerFinishedAction: @escaping (String) -> Void) {
self.onTimerFinished = onTimerFinishedAction
}
so when it is called in the FooterTimerView
, the lambda passed to it is a function to be executed when the timer is finished - which is when the consumed calories can be calculated based on the time spent exercising.
The problem I'm facing is that in the two assignments to the consumedCalories
variable, I get the error Mutation of captured var 'consumedCalories' in concurrently-executing code
and I'm not sure how to fix the code so that:
- The amount of calories is calculated first in an async way.
- Once the 1. is finished, the sheet is shown, and it takes the
consumedCalories
value to it is shown to the user.
I've been trying to fix it for a couple of days and as soon as I think I'm heading in the right direction and I start fixing it, I always end up at a dead end.
Thanks a lot in advance!