SwiftUI NavigationStack 導航教學

在 App 中,最常見的頁面切換方式就是「推入 (Push)」和「返回 (Pop)」。在 SwiftUI 中,我們使用 NavigationStack (iOS 16+) 來管理這種堆疊式的導航。

注意:iOS 16 之前使用的是 NavigationView。雖然目前尚未完全移除,但 Apple 強烈建議新專案使用 NavigationStack,因为它解決了舊版無法精確控制路由的問題。

基礎導航 (Basic Navigation)

要啟用導航功能,首先要在最外層包一個 NavigationStack。然後使用 NavigationLink 來建立跳轉按鈕。

struct ContentView: View {
    var body: some View {
        NavigationStack {
            VStack(spacing: 20) {
                Text("這是首頁")
                
                // 簡單的跳轉,直接指定目標 View
                NavigationLink("前往詳情頁") {
                    DetailView() 
                }
            }
            .navigationTitle("首頁") // 設定導航列標題
            .navigationBarTitleDisplayMode(.large) // 標題樣式 (large/inline)
        }
    }
}

struct DetailView: View {
    var body: some View {
        Text("這是詳情頁")
            .navigationTitle("詳情")
    }
}

數值驅動導航 (Value-Based Navigation)

這是 NavigationStack 最強大的功能。傳統的 NavigationView 需要在每個 Link 裡寫死目標 View,導致程式碼耦合且難以管理。

現在,我們可以將「導航邏輯」和「UI」分離:

  1. NavigationLink 只負責傳遞 資料 (Value)
  2. .navigationDestination 負責決定看到該資料時要顯示什麼頁面
struct ContentView: View {
    let fruits = ["Apple", "Banana", "Cherry"]
    
    var body: some View {
        NavigationStack {
            List(fruits, id: \.self) { fruit in
                // 這裡只傳遞字串 "Apple", "Banana"...
                NavigationLink(fruit, value: fruit)
            }
            .navigationDestination(for: String.self) { fruitName in
                // 這裡統一處理:只要收到 String,就跳轉到 FruitDetailView
                FruitDetailView(name: fruitName)
            }
        }
    }
}

這在處理複雜列表時非常有用,你不需要在 List 的每一行都實例化目標 View。

程式化導航 (Programmatic Navigation)

如果我們不是點擊按鈕跳轉,而是達到某個條件自動跳轉(例如登入成功後),或是需要一次返回多層 (Pop to Root),這時就需要管理 Path (路徑)

我們使用一個 NavigationPath[Data] 陣列來綁定 NavigationStack。

struct ContentView: View {
    // 綁定路徑,這代表當前的堆疊狀態
    @State private var path = NavigationPath()
    
    var body: some View {
        NavigationStack(path: $path) {
            VStack {
                Button("隨機推薦") {
                    // 通過程式碼加入路徑,會自動跳轉
                    path.append("Grape")
                }
            }
            .navigationDestination(for: String.self) { bg in
                ColorDetail(colorName: bg, path: $path)
            }
        }
    }
}

struct ColorDetail: View {
    let colorName: String
    @Binding var path: NavigationPath // 接收 path 以便控制返回
    
    var body: some View {
        VStack {
            Text("現在位置: \(colorName)")
            
            Button("回到首頁 (Pop to Root)") {
                // 清空路徑,就是回到最上層
                path = NavigationPath()
            }
        }
    }
}

自定義導航列 (Navigation Bar)

我們可以客製化導航列的標題、按鈕等。

.navigationTitle("設定")
.toolbar {
    ToolbarItem(placement: .topBarTrailing) {
        Button("儲存") { save() }
    }
    ToolbarItem(placement: .topBarLeading) {
        Button("取消") { cancel() }
    }
}
// 隱藏返回按鈕 (慎用)
.navigationBarBackButtonHidden(true) 

總結

  • 使用 NavigationStack 包裹最外層。
  • 簡單跳轉用 NavigationLink("Title") { Destination() }
  • 列表與複雜跳轉推薦用 .navigationDestination(for:) 分離資料與視圖。
  • 需要通過程式碼控制 (如回首頁) 時,使用 path Binding