swiftuiswiftui-navigationlinkswiftui-navigationstack

How can I combine the NavigationLink(value:label:) with a longPressGesture?


NavigationLink stops working if I add a .onLongPressGesture to my label element. Since .onTapGesture and .onLongPressGesture work well together, I thought these could coexist as well.

I've found answers that solved this for the old NavigationLink(titleKey:destination:isActive:) but none for the new NavigationLink(value:label:)

I'm also using different value types, so I'm unsure if I can use the NavigationStack(path:) and programmatically append values.

import SwiftUI

struct CombineLongPressAndNavigationLink: View {
    @State private var rowColor: Color = .red

    var body: some View {
        NavigationStack {
            ForEach(0..<50) { index in
                NavigationLink(value: index) {
                    Rectangle().fill(rowColor)
                        .frame(height: 40)
                        .overlay {
                            Text("Navigate to \(index)")
                        }
                    // Uncomment and NavigationLink stops working
                    // .onLongPressGesture {
                    //    rowColor = rowColor == .red ? .green : .red
                    // }
                }
            }
            .navigationDestination(for: Int.self) { index in
                Text("\(index)")
            }

        }

    }
}

Solution

  • The same ideas in the solutions (using the deprecated NavigationLink initialisers) here can be applied to value based navigation too.

    The idea is that you handle both gestures (use .onTapGesture and .onLongPressGesture), and navigate programmatically.

    Before, you needed a NavigationLink with an isActive parameter to programmatically navigate, but now you don't even need a NavigationLink. You can just append a value to the navigation path.

    Declare a navigation path as @State.

    @State private var path = [Int]()
    // or if your path contains different types of values
    @State private var path = NavigationPath()
    

    Pass it to NavigationStack(path: $path), and use the Rectangle().fill(rowColor) directly.

    NavigationStack(path: $path) {
        ForEach(0..<50) { index in
            Rectangle().fill(rowColor)
                .frame(height: 40)
                .overlay {
                    Text("Navigate to \(index)")
                }
                .onTapGesture {
                    // here we programmatically navigate
                    path.append(index)
                }
                .onLongPressGesture {
                    rowColor = rowColor == .red ? .green : .red
                }
        }
        .navigationDestination(for: Int.self) { index in
            Text("\(index)")
        }
    
    }