Managing side-effects when SwiftUI's onDisappear not called immediately due to page transition animation

138 views Asked by At

I have a theoretical question about SwiftUI, the timing of the onDisappear() method, and view transitions.

Lets say you have a navigation setup like this:

enum ScreenToShow {
    case main, editting, settings
}

@Observable
class NavigationController {
    var screen: ScreenToShow = .main
    
}

Then, in content view we switch between screens like this:

@Environment(NavigationController.self) var navController

var body: some View {
        
        switch navController.screen { ... } 

}

So when we want to switch screens, we just set navContrller.screen to a new value.

So lets say our editing screen contains a recording widget view that users can record a voice memo with. This widget can be in a recording state when the user taps "back" from the editting screen and in this case, we want the recording to stop, obviously.

So, we add recorder.stop() to the onDisappear method of the record widget view.

This works as expected... but then we decide we want to use a sliding animation transition between the main screen and the edit screen, so we do this:

extension AnyTransition {
    static var backslide: AnyTransition {
        AnyTransition.asymmetric(
            insertion: .move(edge: .trailing),
            removal: .move(edge: .trailing))}
}

And in our "navigation switch logic" in content view we change

case .editting:
                EditScreen(memo: memoToEdit!)

to:

case .editting:
            EditScreen(memo: memoToEdit!).transition(.backslide)

This also appears to work as expected, however, as the learned among you might expect (and thus why requests for "more code" are misinformed), the onDisappear method of the record widget (contained in the edit screen) is not called immediately any more when the user taps the back button on the edit screen. Now, it is called only after the transition animation has completed.

This is problematic because:

  • The edit screen can be dismissed and while the sliding transition back to the main screen is still taking place (not finished and still "easing out"), the edit screen can be navigated to again and what will happen is that the transition will reverse, the view will be re-used (regardless of whether we are editting a different item or not), and onDisappear will never be called.

I'm tempted to think of ways to "tell" the recording widget: "your parent is being dismissed!" when the user taps back... maybe with a binding... but I fear I'm opening a can of spaghetti worms with any workarounds.

Is anyone familiar with this problem and is there a way to rethink it with a more built in solution?

1

There are 1 answers

0
Benzy Neez On BEST ANSWER

I would suggest adding an onChange callback inside your EditScreen that listens for changes to the selected screen. Something like:

.onChange(of: navController.screen) { oldVal, newVal in
    if newVal != .editting {
        stopRecording()
    }
}