nathantannar4 / Transmission

Bridges UIKit presentation APIs to a SwiftUI API so you can use presentation controllers, interactive transitions and more.
BSD 2-Clause "Simplified" License
419 stars 15 forks source link

State update prevents nested NavigationStack from working #13

Closed PhilipDukhov closed 11 months ago

PhilipDukhov commented 11 months ago

It works fine as long as content is static, but when I add any state change it stops working

struct ContentView: View {
    @State var rootNavigationPath = NavigationPath()
    @State var childNavigationPath = NavigationPath()
    @State var isSheetShown = false

    @State var state = 0

    var body: some View {
        NavigationStack(path: $rootNavigationPath) {
            Button {
                withAnimation {
                    isSheetShown = true
                }
            } label: {
                Text("Show sheet \(state)")
            }
            .task {
                state += 1
            }
            .presentation(
                transition: .slide(
                    options: .init(
                        edge: .bottom,
                        prefersScaleEffect: true,
                        isInteractive: true,
                        options: .init(
                            shouldAutomaticallyDismissDestination: false,
                            modalPresentationCapturesStatusBarAppearance: true,
                            preferredPresentationBackgroundColor: .gray
                        )
                    )
                ),
                isPresented: $isSheetShown
            ) {
                NavigationStack(path: $childNavigationPath) {
                    Button {
                        childNavigationPath.append("Some")
                    } label: {
                        Text("Push Next")
                    }
                    .navigationDestination(for: String.self) { item in
                        Text(item)
                    }
                }
                .frame(maxHeight: .infinity, alignment: .top)
            }
        }
    }
}
nathantannar4 commented 11 months ago

It works fine as long as content is static, but when I add any state change it stops working

Current example is working for me, no issues. Tested on iOS simulator and physical device. Am I missing a state change?

PhilipDukhov commented 11 months ago

It works fine as long as content is static, but when I add any state change it stops working

Current example is working for me, no issues. Tested on iOS simulator and physical device. Am I missing a state change?

hm, I created a new project, added Transmission v1.0.0 as a dependency and can reproduce it both on simulator and on a real device

this state change is enough to make it fail:

.task {
    state += 1
}

https://github.com/nathantannar4/Transmission/assets/6103621/dc1798d3-8ede-45aa-8137-e785a6ac775d

nathantannar4 commented 11 months ago

Ok I can reproduce. Seems like the @State value must also be read for the issue to reproduce. Very strange, not sure what's going on.

nathantannar4 commented 11 months ago

Seems like another obscure NavigationStack bug I'm not sure how to work around. Workaround is to install the presentation modifier outside of the NavigationStack

@available(iOS 16.0, *)
struct PresentedNavigationPathKey: ViewOutputKey {
    struct Content: View {
        var isPresented: Binding<Bool>
        var body: AnyView

        init<V: View>(isPresented: Binding<Bool>, @ViewBuilder body: () -> V) {
            self.isPresented = isPresented
            self.body = AnyView(body())
        }
    }
}

@available(iOS 16.0, *)
struct ParentView: View {
    @State var path = NavigationPath()

    var body: some View {
        ViewOutputKeyReader(PresentedNavigationPathKey.self) { value in
            NavigationStack(path: $path) {
                VStack {
                    ChildView(id: "A")

                    ChildView(id: "B")
                }
                .navigationTitle("Parent")
            }
            .background {
                ViewOutputKeyValueReader(value) { list in
                    ForEach(list) { destination in
                        Color.clear
                            .presentation(
                                transition: .sheet,
                                isPresented: destination.content.isPresented
                            ) {
                                destination
                            }
                    }
                }
            }
        }
    }
}

@available(iOS 16.0, *)
struct ChildView: View {
    var id: String
    @State var path = NavigationPath()
    @State var isPresented = false

    var body: some View {
        Button {
            withAnimation {
                isPresented = true
            }
        } label: {
            Text("Show Stack")
        }
        .viewOutput(PresentedNavigationPathKey.self) {
            PresentedNavigationPathKey.Content(
                isPresented: $isPresented
            ) {
                NavigationStack(path: $path) {
                    Button {
                        withAnimation {
                            path.append("Some")
                        }
                    } label: {
                        Text("Button \(id)")
                    }
                    .navigationDestination(for: String.self) { value in
                        Text(value)
                    }
                    .navigationTitle("Child")
                }
            }
        }
    }
}
PhilipDukhov commented 11 months ago

Seems like another obscure NavigationStack bug I'm not sure how to work around. Workaround is to install the presentation modifier outside of the NavigationStack

thank you for looking though, and for the workaround!