Back gesture not disabled with navigationBarBackButtonHidden(true) when using .zoom transition

[Submitted as FB22226720]

For a NavigationStack destination, applying .navigationBarBackButtonHidden(true) hides the back button and also disables the interactive left-edge back gesture when using the standard push navigation transition.

However, when the destination uses .navigationTransition(.zoom), the back button is hidden but the left-edge back gesture is still available—it can still be dismissed even though back is intentionally suppressed.

This creates inconsistent behavior between navigation transition styles. navigationBarBackButtonHidden(_:) works with a standard push transition, but not with .navigationTransition(.zoom).

In the code below, .interactiveDismissDisabled(true) is also applied as another attempt to suppress the back-swipe gesture, but it has no effect. As a result, there’s currently no clean way to prevent back navigation when using the zoom transition.

REPRO STEPS

  1. Create an iOS project then replace ContentView with code below, build and run.
  2. Leave nav type set to List Push.
  3. Open an item.
  4. Verify there is no back button, then try the left-edge back gesture.
  5. Return to the root view.
  6. Change nav type to Grid Zoom.
  7. Open an item.
  8. Verify there is no back button, then try the left-edge back gesture.

ACTUAL

In List Push mode, the left-edge back gesture is prevented.

In Grid Zoom mode, the back button is hidden, but the left-edge back gesture still works and returns to the previous view.

EXPECTED

Behavior should be consistent across navigation transition styles. If this configuration is meant to suppress interactive backward navigation for a destination, it should also suppress the left-edge back gesture when using .navigationTransition(.zoom).

SCREEN RECORDING

SAMPLE CODE

struct ContentView: View {
    private enum NavigationMode: String, CaseIterable {
        case listPush = "List Push"
        case gridZoom = "Grid Zoom"
    }

    @Namespace private var namespace
    @State private var navigationMode: NavigationMode = .listPush

    private let colors: [Color] = [.red, .blue]

    var body: some View {
        NavigationStack {
            VStack(spacing: 16) {
                Picker("Navigation Type", selection: $navigationMode) {
                    ForEach(NavigationMode.allCases, id: \.self) { mode in
                        Text(mode.rawValue).tag(mode)
                    }
                }
                .pickerStyle(.segmented)

                if navigationMode == .gridZoom {
                    HStack {
                        ForEach(colors.indices, id: \.self) { index in
                            NavigationLink(value: index) {
                                VStack {
                                    RoundedRectangle(cornerRadius: 14)
                                        .fill(colors[index])
                                        .frame(height: 120)
                                    Text("Grid Item \(index + 1)")
                                        .font(.subheadline.weight(.medium))
                                }
                                .padding(12)
                                .frame(maxWidth: .infinity)
                                .background(.quaternary.opacity(0.25), in: RoundedRectangle(cornerRadius: 16))
                                .matchedTransitionSource(id: index, in: namespace)
                            }
                            .buttonStyle(.plain)
                        }
                    }
                } else {
                    ForEach(colors.indices, id: \.self) { index in
                        NavigationLink(value: index) {
                            HStack {
                                Circle()
                                    .fill(colors[index])
                                    .frame(width: 24, height: 24)
                                Text("List Item \(index + 1)")
                                Spacer()
                                Image(systemName: "chevron.right")
                                    .foregroundStyle(.secondary)
                            }
                            .padding()
                            .background(.quaternary.opacity(0.25), in: RoundedRectangle(cornerRadius: 12))
                        }
                        .buttonStyle(.plain)
                    }
                }

                Spacer()
            }
            .padding(20)
            .navigationTitle("Prevent Back Swipe")
            .navigationSubtitle("Compare Grid Zoom vs List Push")
            .navigationDestination(for: Int.self) { index in
                if navigationMode == .gridZoom {
                    DetailView(color: colors[index])
                        .navigationTransition(.zoom(sourceID: index, in: namespace))
                } else {
                    DetailView(color: colors[index])
                }
            }
        }
    }
}

private struct DetailView: View {
    @Environment(\.dismiss) private var dismiss
    let color: Color

    var body: some View {
        ZStack {
            color.ignoresSafeArea()
            Text("Try left-edge swipe back")
                .font(.title.bold())
                .multilineTextAlignment(.center)
                .padding(.horizontal, 24)
        }
        .navigationBarBackButtonHidden(true)
        .interactiveDismissDisabled(true)
        .toolbar {
            ToolbarItem(placement: .topBarTrailing) {
                Button("Close", action: dismiss.callAsFunction)
            }
        }
    }
}
Back gesture not disabled with navigationBarBackButtonHidden(true) when using .zoom transition
 
 
Q