iT邦幫忙

2023 iThome 鐵人賽

DAY 22
0
Mobile Development

SwiftUI 的大大小小系列 第 22

Day 22 - SwiftUI 的 NavigationStack 與 path - 1

  • 分享至 

  • xImage
  •  

hero

這個系列與 NavigationStack 將關的文章如下

今天的程式碼會從

Day 21 - 在 SwiftUI 中使用 navigationDestination 封裝開啟畫面

的最終程式碼接續開始進行

struct Flavor: Identifiable, Hashable {
    let id = UUID().uuidString
    let name: String
}

struct ContentView: View {
    private let flavors = ["綠豆", "紅豆", "芋粿", "愛玉"].map(Flavor.init)
    var body: some View {
        NavigationStack {
            ForEach(flavors) { flavor in
                NavigationLink(flavor.name, value: flavor).padding()
            }
            .navigationDestination(for: Flavor.self) { flavor in
                Text(flavor.name)
                    .navigationTitle(flavor.name)
            }
        }
    }
}

NavigationStack 的 path

init(path: Binding<Data>, root: () -> Root)

Data 必須為一個有序的 collection 例如陣列,而元素必須為 Hashable

以及

init(path: Binding<NavigationPath>, root: () -> Root)

當有多樣性物件的情形就可以直接使用 NavigationPath 。

path 的作用

加入 path 之後,NavigationStack 的畫面堆疊 (stack) 就會跟 path 同步了。

例如往 path 推入一個值, NavigationStack 就會根據 path 變化。步驟如下:

  1. 往 path 推入一個值
  2. 根據推入的值的「型別」,從 navigationDestination 中找到對應的型別,接著推出所設定好的畫面

應用

像是 deep link 可以用利用這個機制,從外部傳入網址、經過分析就可以利用這個機制觸發開啟期望的頁面。

不過為了簡化和專注在 path 的用法,今天的範例就先以隨機的方式決定要開啟的畫面吧!

加入 path

這一篇先用第一個方法,陣列,作為範例

  • Flavor 已經是 Hashable ,所以不需要變更
struct ContentView: View {
    @State private var path = [Flavor]()
    private let flavors = ["綠豆", "紅豆", "芋粿", "愛玉"].map(Flavor.init)
    var body: some View {
        NavigationStack(path: $path) {
            ForEach(flavors) { flavor in
                NavigationLink(flavor.name, value: flavor).padding()
            }
            .navigationDestination(for: Flavor.self) { flavor in
                Text(flavor.name)
                    .navigationTitle(flavor.name)
            }
        }
    }
}

這時候的畫面還沒有變化,畫面操作如下

2201

加入觸發的按鈕

struct ContentView: View {
    private let flavors = ["綠豆", "紅豆", "芋粿", "愛玉"].map(Flavor.init)
    @State private var path = [Flavor]()
    var body: some View {
        NavigationStack(path: $path) {
            Button {
                if let flavor = flavors.randomElement() {
                    path.append(flavor)
                }
            } label: {
                Text("好手氣")
            }
            .foregroundColor(.purple)
            .padding()
            ForEach(flavors) { flavor in
                NavigationLink(flavor.name, value: flavor).padding()
            }
            .navigationDestination(for: Flavor.self) { flavor in
                Text("\(flavor.name)")
                    .navigationTitle(flavor.name)
            }
            // 加個標題吧 :)
            .navigationTitle("點心")
        }
    }
}

主要的邏輯在這裡,當點下「好手氣」取得一個隨機的口味,像這樣推入 path 。

path.append(flavor)

在 NavigationStack 接受到 path 更新的事件之後,就會去看 navigationDestination 中 Flavor 的定義,推出一個已經定義好的畫面:

2202

可以從動畫中看到,每一次點擊好手氣出現的口味都不一樣。

推入兩個以上

在一些情境之內會有像是這樣的需求

先前往一個列表頁面,再把其中一項的詳細列開起來

也可以用 path 達成

同樣的這篇也簡化需求,就以一次推入兩個 flavors 來示範一下:

struct ContentView: View {
    private let flavors = ["綠豆", "紅豆", "芋粿", "愛玉"].map(Flavor.init)
    @State private var path = [Flavor]()
    var body: some View {
        NavigationStack(path: $path) {
            Button {
                if let flavor1 = flavors.randomElement(),
                   let flavor2 = flavors.randomElement() {
                    path.append(contentsOf: [flavor1, flavor2])
                }
            } label: {
                Text("好手氣")
            }
            .foregroundColor(.purple)
            .padding()
            ForEach(flavors) { flavor in
                NavigationLink(flavor.name, value: flavor).padding()
            }
            .navigationDestination(for: Flavor.self) { flavor in
                Text("\(flavor.name)")
                    .navigationTitle(flavor.name)
            }
            .navigationTitle("點心")
        }
    }
}

像是這樣,推入兩個 flavors

path.append(contentsOf: [flavor1, flavor2])

效果如下

2203

結語

最近不斷在尋找 deep link 跳轉的實作方法時就發現 NavigationStack 原來能夠那麼活用,接著應該還會有幾篇以這個需求為中心發布。

以上,那今天的 SwiftUI 的大大小小就到這邊,明天見!

環境

  • Xcode 15

本篇使用到的 UI 元件和 modifiers 基本上沒有受到版本更新影響。若要在 Xcode 14 等環境下使用也是沒問題的。


上一篇
Day 21 - 在 SwiftUI 中使用 navigationDestination 封裝開啟畫面
下一篇
Day 23 - 在 SwiftUI 中如何獲取和解析 Deep Link 並跳轉
系列文
SwiftUI 的大大小小30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言