SwiftUI - Undo a backwards move in a NavigationStack

160 views Asked by At

When I navigate backward (like swiping the view from the left), I want to be able to swipe it back from the right to move to that previous view. Sort of like an undo stack.

Is it possible to use NavigationStack or other existing views to solve that?

1

There are 1 answers

4
Benzy Neez On BEST ANSWER

It doesn't come for free, but you can implement this kind of functionality quite easily by saving the last navigation target and handling drag gestures. Like this:

struct ContentView: View {

    @State private var navPath = [Int]()
    @State private var previousTarget = -1

    private func dragNavigate(back: Bool) -> some Gesture {
        DragGesture()
            .onChanged() { value in
                let translation = value.translation
                if abs(translation.width) > abs(translation.height) &&
                    back == (translation.width > 0) {
                    if back && navPath.count > 0 {
                        navPath.removeLast()
                    } else if !back && previousTarget >= 0 && previousTarget != navPath.last {
                        navPath.append(previousTarget)
                    }
                }
            }
    }

    private func targetView(index: Int) -> some View {
        Text("View\(index + 1)")
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .contentShape(Rectangle())
            .gesture(dragNavigate(back: true))
    }

    var body: some View {
        NavigationStack(path: $navPath) {
            VStack(spacing: 50) {
                NavigationLink("View1", value: 0)
                NavigationLink("View2", value: 1)
                NavigationLink("View3", value: 2)
            }
            .navigationDestination(for: Int.self) { index in
                switch index {
                case 1: targetView(index: 1)
                case 2: targetView(index: 2)
                default: targetView(index: 0)
                }
            }
            .onChange(of: navPath) { newPath in
                if let target = newPath.last {
                    previousTarget = target
                }
            }
        }
        .gesture(dragNavigate(back: false))
    }
}

If the navigation hierarchy extends to more than one level then you might need to save more than one previous target, perhaps as a shadow stack.