Bahn-X / swift-composable-navigator

An open source library for building deep-linkable SwiftUI applications with composition, testing and ergonomics in mind
MIT License
580 stars 25 forks source link

goBack fails to dismiss multiple views correctly. #72

Open andrewjmeier opened 3 years ago

andrewjmeier commented 3 years ago

Bug description

[//]: # Using goBack to navigate back to a previous screen doesn't dismiss all of the views when each view is presented modally.

Steps to reproduce

[//]: # Open a series of at least 3 modal views and then try to navigate back to the original view. One modal view will remain.


struct RootView: View {

    var body: some View {
        let dataSource = Navigator.Datasource(root: MainScreen())
        let navigator = Navigator(dataSource: dataSource)

        return Root(dataSource: dataSource, navigator: navigator, pathBuilder: MainScreen.Builder())
    }

}

struct MainScreen: Screen {

    var presentationStyle: ScreenPresentationStyle = .push

    struct Builder: NavigationTree {
        var builder: some PathBuilder {
            Screen(
                content: { (_: MainScreen) in MainView() },
                nesting: { ModalScreen.Builder().eraseCircularNavigationPath() }
            )
        }
    }
}

struct MainView: View {
    @Environment(\.navigator) private var navigator
    @Environment(\.currentScreenID) private var currentScreenID

    var body: some View {
        VStack {
            Button {
                navigator.go(to: ModalScreen(viewCount: 1, onDismiss: {
                    print(currentScreenID)
                    navigator.goBack(to: currentScreenID)
                }), on: currentScreenID)
            } label: {
                Text("Show new view")
            }
        }
    }
}

struct ModalScreen: Screen {

    var presentationStyle: ScreenPresentationStyle = .sheet(allowsPush: true)
    var viewCount: Int
    var onDismiss: () -> Void

    struct Builder: NavigationTree {
        var builder: some PathBuilder {
            Screen(
                content: { (screen: ModalScreen) in ModalView(viewCount: screen.viewCount, onDismiss: screen.onDismiss) },
                nesting: { ModalScreen.Builder().eraseCircularNavigationPath() }
            )
        }
    }
}

extension ModalScreen: Equatable {

    static func == (lhs: ModalScreen, rhs: ModalScreen) -> Bool {
        return lhs.viewCount == rhs.viewCount
    }

}

extension ModalScreen: Hashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(viewCount)
    }
}

struct ModalView: View {
    @Environment(\.navigator) private var navigator
    @Environment(\.currentScreenID) private var currentScreenID

    var viewCount: Int
    var onDismiss: () -> Void

    var body: some View {
        VStack {
            Text("View \(viewCount)")
            Button {
                navigator.go(to: ModalScreen(viewCount: viewCount + 1, onDismiss: onDismiss), on: currentScreenID)
            } label: {
                Text("Show new view")
            }
            Button {
                onDismiss()
            } label: {
                Text("dismiss all views")
            }
        }
    }

}

Expected behavior

[//]: I would expect the original screen to be fully visible and all of the modal views to be dismissed.

Screenshots

[//]: Simulator Screen Recording - iPod touch (7th generation) - 2021-05-14 at 10 13 21

Environment

ohitsdaniel commented 3 years ago

Hm, interesting. I will look into this. 🧐