SwiftUI
NavigationLink
iOS Development
Bug Fixing
Mobile App Development

NavigationLink Works Only for Once

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A common SwiftUI bug causes NavigationLink to work only once — you tap it, navigate to the detail view, go back, and the same link stops responding. This happens because SwiftUI's state management marks the link as "already activated" and never resets it. The fix depends on which NavigationLink API you use: the older isActive binding, the programmatic NavigationStack with navigationDestination, or the implicit push style. In most cases, switching to NavigationStack (iOS 16+) or ensuring proper state reset resolves the issue.

The Problem

swift
1// This NavigationLink may stop working after first tap-and-back
2struct ContentView: View {
3    var body: some View {
4        NavigationView {
5            List {
6                NavigationLink("Go to Detail", destination: DetailView())
7            }
8            .navigationTitle("Home")
9        }
10    }
11}
12
13struct DetailView: View {
14    var body: some View {
15        Text("Detail Screen")
16    }
17}

On iPad or with NavigationView in a split-view context, the link activates once, pushes the detail, but after dismissal it becomes unresponsive. This is a known SwiftUI behavior related to how NavigationView manages its column-based navigation internally.

Fix 1: Use NavigationStack (iOS 16+)

swift
1struct ContentView: View {
2    var body: some View {
3        NavigationStack {
4            List {
5                NavigationLink("Go to Detail", value: "detail")
6            }
7            .navigationDestination(for: String.self) { value in
8                DetailView()
9            }
10            .navigationTitle("Home")
11        }
12    }
13}

NavigationStack replaced NavigationView in iOS 16 and does not suffer from the one-time activation bug. It uses a value-based navigation model where links push values onto a path, and navigationDestination maps values to views.

Fix 2: Use isActive Binding with State Reset

swift
1struct ContentView: View {
2    @State private var isActive = false
3
4    var body: some View {
5        NavigationView {
6            VStack {
7                NavigationLink(
8                    destination: DetailView(),
9                    isActive: $isActive
10                ) {
11                    Text("Go to Detail")
12                }
13            }
14            .navigationTitle("Home")
15        }
16        .navigationViewStyle(.stack)  // Forces single-column stack
17    }
18}

Two key fixes here: binding isActive to a @State variable so SwiftUI properly tracks and resets the navigation state, and using .navigationViewStyle(.stack) to force single-column navigation instead of the split-view default on iPad.

Fix 3: Force Stack Navigation Style

swift
1// The simplest fix for NavigationView — force stack style
2NavigationView {
3    List {
4        NavigationLink("Detail", destination: DetailView())
5    }
6}
7.navigationViewStyle(.stack)  // This alone often fixes the issue

On iPad, NavigationView defaults to a DoubleColumnNavigationViewStyle (sidebar + detail). The detail column retains its view, making the link appear "stuck." Forcing .stack style makes it behave like iPhone navigation.

Fix 4: Programmatic Navigation with NavigationStack

swift
1struct ContentView: View {
2    @State private var path = NavigationPath()
3
4    var body: some View {
5        NavigationStack(path: $path) {
6            List {
7                Button("Go to Detail") {
8                    path.append("detail")
9                }
10            }
11            .navigationDestination(for: String.self) { value in
12                DetailView()
13            }
14            .navigationTitle("Home")
15        }
16    }
17}

Using NavigationPath gives you full programmatic control. You can push, pop, and reset the navigation stack at any time, which completely avoids the one-time activation issue.

Fix 5: List Selection Pattern

swift
1struct Item: Identifiable, Hashable {
2    let id = UUID()
3    let name: String
4}
5
6struct ContentView: View {
7    let items = [Item(name: "A"), Item(name: "B"), Item(name: "C")]
8    @State private var selectedItem: Item?
9
10    var body: some View {
11        NavigationSplitView {
12            List(items, selection: $selectedItem) { item in
13                Text(item.name)
14                    .tag(item)
15            }
16        } detail: {
17            if let item = selectedItem {
18                DetailView(item: item)
19            } else {
20                Text("Select an item")
21            }
22        }
23    }
24}

For iPad split-view apps, NavigationSplitView (iOS 16+) with a selection binding is the correct pattern. It handles sidebar-detail navigation without the one-time link bug.

Common Pitfalls

  • Using NavigationView on iPad without .stack style: The default double-column style on iPad is the primary cause of the one-time activation bug. Always add .navigationViewStyle(.stack) if you want push navigation, or migrate to NavigationStack/NavigationSplitView.
  • Nesting multiple NavigationViews: Placing a NavigationView inside another NavigationView (e.g., in a child view) causes unpredictable navigation behavior including links that stop working. Only the root view should contain the navigation container.
  • Not storing the NavigationPath: When using NavigationStack(path:), the path must be stored in @State or an @ObservableObject. If the path is recreated on each render, navigation resets unexpectedly.
  • Mixing NavigationLink styles: Using both the old NavigationLink(destination:) and the new value-based NavigationLink(value:) in the same NavigationStack can cause conflicts. Pick one pattern consistently.
  • Forgetting Hashable conformance: Value-based NavigationLink and navigationDestination require the value type to conform to Hashable. Missing conformance causes a compile error or silent navigation failure.

Summary

  • The "NavigationLink works only once" bug is caused by NavigationView's split-view behavior on iPad
  • Add .navigationViewStyle(.stack) to force single-column navigation as a quick fix
  • Migrate to NavigationStack (iOS 16+) for reliable push navigation without state bugs
  • Use NavigationSplitView for iPad apps that need sidebar-detail layout
  • Bind navigation state to @State variables or NavigationPath for full programmatic control
  • Never nest multiple NavigationView or NavigationStack containers

Course illustration
Course illustration

All Rights Reserved.