SwiftUI 框架

在 SwiftUI 使用新的 NavigationStack 視圖 輕鬆構建資料導向的導航

在 iOS 開發中,導覽視圖絕對是我們最常用的組件。在 iOS 16,Apple 引入了一個新視圖 NavigationStack 來呈現堆疊視圖,讓我們可以構建資料導向的導航。在這篇文章中,我會帶大家試著使用這個新元件,處理 deep linking 和複雜的 user flow。
在 SwiftUI 使用新的 NavigationStack 視圖 輕鬆構建資料導向的導航
Photo by Taan Huyn on Unsplash
在 SwiftUI 使用新的 NavigationStack 視圖 輕鬆構建資料導向的導航
Photo by Taan Huyn on Unsplash
In: SwiftUI 框架

在 iOS 開發中,導覽視圖 (Navigation View) 絕對是我們最常用的組件。在 SwiftUI 剛推出的時侯,就已經有一個 NavigationView 視圖,讓開發者可以構建基於導覽的使用者界面。隨著 iOS 16 的發佈,Apple 棄用了舊的導覽視圖,並引入了一個新視圖 NavigationStack 來呈現堆疊視圖。更重要的是,開發者現在可以利用這個新視圖來構建資料導向 (data driven) 的導航。

舊的 Navigation Views 的操作

在 iOS 16 之前,我們會用 NavigationViewNavigationLink 如此構建導覽介面:

NavigationView {
    NavigationLink {
        Text("Destination")
    } label: {
        Text("Tap me")
    }
}

以上程式碼會構建出一個基本的導覽介面,當中有一個 Tap me 按鈕。當我們點擊按鈕,App 就會導覽到下一級,來顯示目標視圖。

swiftui-navigationstack-ios16

使用 NavigationStack

從 iOS 16 開始,我們可以用新的 NavigationStack 來取代 NavigationView。我們可以完全保留 NavigationLink,都會得到相同的結果。

NavigationStack {
    NavigationLink {
        Text("Destination")
    } label: {
        Text("Tap me")
    }
}

我們也可以這樣編寫程式碼:

NavigationStack {
    NavigationLink("Tap me") {
        Text("Destination")
    }
}

要顯示一個資料項目的列表,通常我們會使用導覽視圖來構建一個 master-detail flow。來看看以下的範例:

struct ContentView: View {
    private var bgColors: [Color] = [ .indigo, .yellow, .green, .orange, .brown ]

    var body: some View {

        NavigationStack {
            List(bgColors, id: \.self) { bgColor in
                NavigationLink {
                    bgColor
                        .frame(maxWidth: .infinity, maxHeight: .infinity)
                } label: {
                    Text(bgColor.description)
                }

            }
            .listStyle(.plain)

            .navigationTitle("Color")
        }

    }
}

以上的程式碼會建立一個導覽視圖,來顯示構建一個 master-detail flow。當使用者選擇了一個項目,App 就會導覽到 detail 視圖,並顯示 color 視圖。

swiftui-navigationstack-demo

NavigationStack 引入了一個新修飾符 navigationDestination,用來把目標視圖與呈現的資料型別連繫。我們可以這樣重新編寫上一節的程式碼:

NavigationStack {
    List(bgColors, id: \.self) { bgColor in

        NavigationLink(value: bgColor) {
            Text(bgColor.description)
        }

    }
    .listStyle(.plain)

    .navigationDestination(for: Color.self) { color in
        color
            .frame(maxWidth: .infinity, maxHeight: .infinity)
    }

    .navigationTitle("Color")
}

我們還是會使用 NavigationLinks 來顯示資料列表,並實作導覽功能;不同之處是每個 NavigationLink 都連繫著一個數值。更重要的是,我們添加了新的 navigationDestination 修飾符來捕捉數值的變化。當使用者選擇特定的 link 時,navigationDestination 修飾符就會顯示相應帶有 Color 型別資料的目標視圖。

如果我們在預覽中測試這個 App,你會發現它的操作方式與之前完全相同。但是,內部的實作已經使用了新的 navigationDestination 修飾符。

多個 Navigation Destination 修飾符

我們可以定義多於一個 navigationDestination 修飾符,來處理不同型別的 Navigation Link。在之前的範例中,我們只有一個 navigationDestination 修飾符來處理 Color 型別。現在,讓我們為 String 型別設置另一組 Navigation Link:

List(systemImages, id: \.self) { systemImage in

    NavigationLink(value: systemImage) {
        Text(systemImage.description)
    }

}
.listStyle(.plain)

systemImages 變數會儲存系統圖像名稱的陣列 (array)。

private var systemImages: [String] = [ "trash", "cloud", "bolt" ]

在這個範例中,我們有兩個型別的 Navigation Link,一個是 Color,另一個是 String 型別。讓我們把另一個 navigationDestination 修飾符嵌入到堆疊中,來處理 String 型別的導覽:

.navigationDestination(for: String.self) { systemImage in
    Image(systemName: systemImage)
        .font(.system(size: 100.0))
}

現在,如果使用者點擊其中一個系統圖像名稱,就會導覽到另一個視圖,來顯示系統圖像。

swiftui-navigation-destination

Working with Navigation States

與舊的 NavigationView 不同,新的 NavigationStack 讓我們可以輕鬆地追踪導覽的狀態。NavigationStack 視圖有另一個 initialization 方法,它有一個 path 參數 (parameter),就是堆疊導覽狀態的 binding:

init(
    path: Binding<Data>,
    root: () -> Root
) where Data : MutableCollection, Data : RandomAccessCollection, Data : RangeReplaceableCollection, Data.Element : Hashable

如果我們想儲存或管理導覽狀態,可以創建一個狀態變數。以下是範例程式碼:

struct ContentView: View {
    private var bgColors: [Color] = [ .indigo, .yellow, .green, .orange, .brown ]

    @State private var path: [Color] = []

    var body: some View {

        NavigationStack(path: $path) {
            List(bgColors, id: \.self) { bgColor in

                NavigationLink(value: bgColor) {
                    Text(bgColor.description)
                }

            }
            .listStyle(.plain)

            .navigationDestination(for: Color.self) { color in
                VStack {
                    Text("\(path.count), \(path.description)")
                        .font(.headline)

                    HStack {
                        ForEach(path, id: \.self) { color in
                            color
                                .frame(maxWidth: .infinity, maxHeight: .infinity)
                        }

                    }

                    List(bgColors, id: \.self) { bgColor in

                        NavigationLink(value: bgColor) {
                            Text(bgColor.description)
                        }

                    }
                    .listStyle(.plain)

                }
            }

            .navigationTitle("Color")

        }

    }
}

這段程式碼與之前的範例有點相似,我們添加了一個 path 狀態變數,它是 Color 的陣列,用來儲存導覽狀態。在 NavigationStack 進行 initialization 時,我們會傳遞它的 binding 來管理堆疊。當導覽堆疊的狀態發生變化時,path  變數的數值就會被自動更新。

我更改了導覽目的地,它會顯示使用者選擇的顏色,和另一個顏色列表以供使用者選擇。

swiftui-navigation-link

在上面的程式碼中,我們有一行程式碼來顯示 path 的內容:

Text("\(path.count), \(path.description)")

count 屬性 (property) 是指堆疊的 level,而 description 就是當前的顏色。舉個例子,我們首先選擇顏色 indigo,然後選擇 yellow。在這個情況下,count 的數值就應該是 2代表導覽堆疊有 2 個 level。

我們可以利用這個 path 變數,來以編程方式控制堆疊的導覽。舉個例子,我們可以添加一個按鈕,讓使用者直接跳轉到堆疊的 root level,以下是範例程式碼:

Button {
    path = .init()
} label: {
    Text("Back to Main")
}
.buttonStyle(.borderedProminent)
.controlSize(.large)

我們可以重置 path 變數的數值,來指示導覽堆疊返回 root level。

大家可能已經知道,我們可以操縱 path 變數的數值,來控制導覽堆疊的狀態。舉個例子,我們可以向 path 變數添加三種顏色,如此一來,當 ContentView 出現時,App 就會自動導覽 3 個 level:

NavigationStack(path: $path) {
  .
  .
  .
}
.onAppear {
    path.append(.indigo)
    path.append(.yellow)
    path.append(.green)
}

你可以試著執行 App,就會看到 App 自動導覽了 3 個 level。如此一來,我們就可以以編程方式控制導覽狀態,並處理 deep linking。

swiftui-navigationstack-demo-2

總結

iOS 16 推出的新 NavigationStack,可以讓開發者輕鬆地構建資料導向的導航 UI。如果你的 App 不需要支援舊版本的 iOS,就可以利用這個新元件,來處理 deep linking 和複雜的 user flow。

譯者簡介:Kelly Chan-AppCoda 編輯小姐。
作者
Simon Ng
軟體工程師,AppCoda 創辦人。著有《iOS 17 App 程式設計實戰心法》、《iOS 17 App程式設計進階攻略》以及《精通SwiftUI》。曾任職於HSBC, FedEx等跨國企業,專責軟體開發、系統設計。2012年創立AppCoda技術部落格,定期發表iOS程式教學文章。現時專注發展AppCoda業務,致力於iOS程式教學、產品設計及開發。你可以到推特與我聯絡。
評論
更多來自 AppCoda 中文版
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。