canopas/UIPilot

Destination view immediately pops back

denizdogan opened this issue · 6 comments

In one of my SwiftUI views, I end the body with:

.task {
    pilot.push(.Feed)
}

That causes the navigation stack to pop to the previous path. However, the following works fine:

.task {
    // sleep for 3 seconds
    try! await Task.sleep(nanoseconds: 3 * NSEC_PER_SEC)
    pilot.push(.Feed)
}

Additionally, the following doesn't work:

.task {
    // sleep for 0.1 seconds
    try! await Task.sleep(nanoseconds: UInt64(0.1) * NSEC_PER_SEC)
    pilot.push(.Feed)
}

So I guess there's some timing/threading issues going on here, but I don't know what it is. Any ideas?

I'm guessing this is caused by the well-known NavigationLink issues in SwiftUI, for example: https://developer.apple.com/forums/thread/677333

Seems like that linked issue is causing it as UIPilot is just a nice wrapper around NavigationView and NavigationLink. I will check the issue, can you push the minimal code that's causing the issue somewhere?

@jimmy0251 It seems harder than I expected to reproduce this in a fresh project. I will try to find some time to reliably reproduce this for you.

@jimmy0251 I got a small example working here: https://github.com/denizdogan/NavigationLink-Bug

The bug is that I expect to land on ViewD, but it stops at ViewC -> ViewD -> ViewC

Looks like it's the exact issue you mentioned.

I converted your sample code into pure NavigationUI code and the issue still persists

struct ContentView: View {
    var body: some View {
        NavigationView {
            ViewA()
        }
    }
}

struct ViewA: View {
    @State var isActive = false

    var body: some View {
        VStack {
            NavigationLink(isActive: $isActive, destination: { ViewB() }, label: { EmptyView() })
                .isDetailLink(false)
        Text("A")
            .padding()
            .task {
                isActive = true
            }
        }
    }
}

struct ViewB: View {
    @State var isActive = false

    var body: some View {
        VStack {
            NavigationLink(isActive: $isActive, destination: { ViewC() }, label: { EmptyView() })
                .isDetailLink(false)
        Text("B")
            .padding()
            .task {
                isActive = true
            }
        }
    }
}

struct ViewC: View {
    @State var isActive = false

    var body: some View {
        VStack {
            NavigationLink(isActive: $isActive, destination: { ViewD() }, label: { EmptyView() })
                .isDetailLink(false)
        Text("C")
            .padding()
            .onAppear {
                isActive = true
            }
        }
    }
}

struct ViewD: View {
    var body: some View {
        Text("D")
            .padding()
    }
}

Looks like a timing issue to me. The issue disappears if I replace task with onAppear on both View A and B.
I think the best thing we can do now is try some workaround, until the issue is officially fixed from apple side.

@jimmy0251 Thanks for taking the time to investigate it. Let's cross our fingers for a clean solution to this soon.