iosswiftuiswiftui-animationswiftui-transitionswiftui-ondisappear

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


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:

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?


Solution

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