Reputation: 3172
I am working on a niche workout app. The flow is similar to the built in app, but a little more complicated.
I have the following code at the top level:
@main
struct WatchApp: App {
@WKExtensionDelegateAdaptor(ExtensionDelegate.self) var delegate
@StateObject var coordinator = SessionCoordinator()
@StateObject var permissions = PermissionsViewModel()
var body: some Scene {
WindowGroup {
NavigationView {
switch coordinator.workout.state {
// ✅ Works on load
// ❌ Doesn't reload when user changes state back to `notStarted`
case .notStarted:
StartView()
.sheet(isPresented: $permissions.showPermissionsPrompt, content: {
PermissionsView(permissions: permissions)
})
// ✅ Works
case .countingDown:
CountdownView()
// ✅ Works
case .paddling, .paused:
WorkoutOrAlertView()
// ✅ Works
case .saving, .ended:
SummaryView()
}
}
.environmentObject(coordinator)
}
}
}
in the SummaryView there is a button which changes the state to notStarted
coordinator.workout.state = .notStarted
This does NOT reload the start view as expected from the case statement.
The coordinator
and the workout
are ObservableObjects
:
class SessionCoordinator: ObservableObject {
@Published public var workout: WorkoutSession {
didSet {
DDLogVerbose("😵💫 workout changed")
}
}
class WorkoutSession: NSObject, ObservableObject {
....
@Published var state = WorkoutSessionState.notStarted {
didSet {
DDLogVerbose("Workout state changed from: \(oldValue) to: \(state)")
}
}
So in summary, everything works as you would expect:
✅ Start a workout
✅ Show countdown if required
✅ Run workout
✅ End workout and get a summary
❌ Changing state to .notStarted
does not reload StartView()
So my questions are:
switch
the best way or should a different approach be used?Upvotes: 0
Views: 393
Reputation: 119232
If you have a @Published
property that holds a reference type, then this is only going to send objectWillChange
when you set a new instance of the reference type. It doesn't matter if the property you changed is published - nothing in your code is listening to changes on that object.
If you genuinely want your coordinator to publish every time a property of the session changes, then you should manually observe and re-publish object will change. However, that is likely to lead to over-publishing and excessive re-rendering, particularly since this is a top-level object.
A better solution is to create a container view, which observes the session:
struct WorkoutView: View {
@ObservedObject var workoutSession: WorkoutSession
var body: some View {
switch workoutSession.state {
//... all your cases here
}
}
}
Then your original view would be:
NavigationView {
WorkoutView(workoutSession: coordinator.workout)
}
This means you'll get a new WorkoutView whenever the instance of the workout changes, and that workout view will monitor the state.
You'll need to either inject the permissions into the environment or pass that as an observed object as well so it can be picked up.
Oh - and using switch statements in a view like this? Fantastic. The best way!
Upvotes: 3