iT邦幫忙

2025 iThome 鐵人賽

DAY 15
0
Mobile Development

從零開始學習 iOS系列 第 15

從零開始學習 iOS Day14 - 導航與分頁

  • 分享至 

  • xImage
  •  

在 App 中,常常會需要「切換頁面」的行為,這時候就可以透過 導航 (Navigation)分頁 (Tab) 來達成。今天先來介紹導航的部分。

NavigationStack

在 iOS 16 之後,NavigationStack 取代了之前的 NavigationView,提供更現代化、靈活的導航方式。

它的核心概念是:用「路徑 (path)」來管理畫面的堆疊

struct ContentView: View {
    @State private var path = NavigationPath()

        var body: some View {
            NavigationStack(path: $path) {
                VStack {
                    Button("前往詳細頁") {
                        path.append("Detail")
                    }
                    Button("前往搜尋頁") {
                        path.append("Search")
                    }
                    Button("Go to Detail 2") {
                        path.append(2) // Can append different types
                    }
                    Button("登出") {
                        path.append(false)
                    }
                }
                .navigationDestination(for: String.self) { value in
                    if value == "Detail" {
                        DetailView()
                    } else if value == "Search" {
                        SearchView()
                    }
                }
                .navigationDestination(for: Int.self) { detailInt in
                                Text("Detail View for number: \(detailInt)")
                                    .navigationTitle("Number Detail")
                            }
                .navigationDestination(for: Bool.self) { value in
                    Text("登出成功")
                }
                .navigationTitle("首頁")
            }
        }
}

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

struct SearchView: View {
    var body: some View {
        VStack {
            Text("這是搜尋頁")
                .navigationTitle("搜尋資料")
        }
        
    }
}

https://github.com/jian-fu-hung/ithelp-2025/blob/main/image/Day14/%E6%88%AA%E5%9C%96%202025-09-27%20%E5%87%8C%E6%99%A81.42.18.png?raw=true

  • NavigationStack:導航容器,管理頁面切換。
  • path.append :進行跳頁行為
  • navigationDestination :根據path.append後面傳遞的參數來決定要前往哪一頁
  • .navigationTitle("首頁"):設定上方導覽列標題。

navigationDestination支援 任何型別,因此可以用不同的資料來決定跳轉目標。。

返回操作

有了 path,返回就很簡單:

返回上一頁:移除最後一個元素

router.path.removeLast()

返回首頁:清空 path

router.path = .init()

搭配 EnvironmentObject 管理 Router

如果我們希望「子頁面也能控制返回行為」,就需要建立一個 Router 類別,並透過 @EnvironmentObject 傳遞。

final class Router: ObservableObject {
    @Published var path = NavigationPath()
}

struct ContentView: View {
    @EnvironmentObject var router: Router

        var body: some View {
            NavigationStack(path: $router.path) {
                VStack {
                    Button("前往詳細頁") {
                        router.path.append("Detail")
                    }
                    Button("前往搜尋頁") {
                        router.path.append("Search")
                    }
                    Button("Go to Detail 2") {
                        router.path.append(2) // Can append different types
                    }
                    Button("登出") {
                        router.path.append(false)
                    }
                }
                .navigationDestination(for: String.self) { value in
                    if value == "Detail" {
                        DetailView()
                    } else if value == "Search" {
                        SearchView()
                    }
                }
                .navigationDestination(for: Int.self) { detailInt in
                                Text("Detail View for number: \(detailInt)")
                                    .navigationTitle("Number Detail")
                            }
                .navigationDestination(for: Bool.self) { value in
                    Text("登出成功")
                }
                .navigationTitle("首頁")
            }.environmentObject(router)
        }
}

struct DetailView: View {
    @EnvironmentObject var router: Router
    var body: some View {
        VStack {
            Text("這是詳細頁")
                .navigationTitle("詳細資料")
            Button("返回上一頁") {
                router.path.removeLast()
            }
        }

    }
}

struct SearchView: View {
    @EnvironmentObject var router: Router
    var body: some View {
        VStack {
            Text("這是搜尋頁")
                .navigationTitle("搜尋資料")
            Button("返回首頁") {
                router.path = .init()
            }
        }
        
    }
}

#Preview {
    ContentView().environmentObject(Router())
}

分頁 TabView

如果要做「底部導覽列」,就可以用 TabView

每個 Tab 就是一個獨立的畫面,可以跟 NavigationStack 搭配使用。

struct TabRootView: View {
    var body: some View {
        TabView {
            ContentView()
                .tabItem {
                    Label("首頁", systemImage: "house.fill")
                }

            SearchView()
                .tabItem {
                    Label("搜尋", systemImage: "magnifyingglass")
                }

            ProfileView()
                .tabItem {
                    Label("我的", systemImage: "person.fill")
                }
        }
    }
}

struct ProfileView: View {
    var body: some View {
        Text("這是個人頁面")
            .navigationTitle("我的資料")
    }
}

https://github.com/jian-fu-hung/ithelp-2025/blob/main/image/Day14/%E6%88%AA%E5%9C%96%202025-09-27%20%E5%87%8C%E6%99%A81.44.02.png?raw=true

  • TabView → 管理多個分頁
  • .tabItem { Label(...) } → 設定分頁名稱與圖示

今日小結

  • NavigationStack → 管理頁面跳轉,透過 path 控制堆疊。
  • TabView → 管理分頁,適合做底部導覽列。
  • Router + EnvironmentObject → 讓任何子頁面都能控制導航狀態。
    明天會介紹如何使用SwiftUI來做動畫。

上一篇
從零開始學習 iOS Day13 - ScrollView 以及 Lazy 容器
系列文
從零開始學習 iOS15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言