iT邦幫忙

2023 iThome 鐵人賽

DAY 18
0
Mobile Development

SwiftUI 的大大小小系列 第 18

Day 18 - 在 SwiftUI 使用 @ViewBuilder 之 2

  • 分享至 

  • xImage
  •  

hero_18

前言

上一回,我們嘗試了怎麼透過 @ViewBuilder 來作為參數傳入一個 view 。那今天我們要用這個來解決另外一個問題。

在一些畫面中,我們需要根據條件判斷要回傳哪一個 UI 。

問題

1801

像是這樣,當寫出這樣的程式碼的地方,在 itemView 這個方法中會出錯

struct ContentView: View {
    enum DeviceType {
        case phone
        case pad
    }
    
    var body: some View {
        ScrollView {
            VStack(alignment: .center) {
                itemView(type: .pad)
                itemView(type: .phone)
            }
        }
    }
    
    /* 這邊 */
    private func itemView(type: DeviceType) -> some View {
        switch type {
        case .pad:
            // 這一行會出錯
            padView()
        case .phone:
            phoneView()
        }
    }
    /* 這邊 */
    
    private func padView() -> some View {
        HStack {
            Circle()
                .foregroundStyle(.gray)
                .frame(width: 50, height: 50)
            VStack(alignment: .leading) {
                Text("Title")
                    .font(.headline)
                Text("description")
                    .font(.subheadline)
            }
            Spacer()
        }
        .padding()
        .background(
            RoundedRectangle(cornerRadius: 10)
                .stroke()
                .foregroundStyle(.gray)
        )
        .padding()
    }
    
    private func phoneView() -> some View {
        VStack {
            Circle()
                .foregroundStyle(.gray)
                .frame(width: 50, height: 50)
            VStack {
                Text("Title")
                    .font(.headline)
                Text("description")
                    .font(.subheadline)
            }
        }
        .padding()
        .background(
            RoundedRectangle(cornerRadius: 10)
                .stroke()
                .foregroundStyle(.gray)
        )
        .padding()
    }
}

錯誤訊息:

Branches have mismatching types 'some View' (result of 'ContentView.padView()') and 'some View' (result of 'ContentView.phoneView()')

提示在條件分歧的地方,實際型別不同而產生問題

用一個 View 包起來?

private func itemView(type: DeviceType) -> some View {
    Group {
        switch type {
        case .pad:
            padView()
        case .phone:
            phoneView()
        }
    }
}

只要用 Group 或 VStack 之類的 UI 元件包起來,錯誤就會消失了。

但是這個解法不是很漂亮,因為加了這個 Group ,對 UI 建構來說可能是一個不需要存在的東西,所以看起來 workaround 感非常重。

這時候就可以使用 @ViewBuilder 來標記這個方法。

用 @ViewBuilder 標記

改寫之後就會像這樣,方法裡面就只留下必要的邏輯,乾淨許多:

@ViewBuilder private func itemView(type: DeviceType) -> some View {
    switch type {
    case .pad:
        padView()
    case .phone:
        phoneView()
    }
}

應用

除了像是根據裝置不同,提供不一樣的畫面佈局,也可以用在類似於工廠方法的情境。

例如在不同狀態、不同型態下會需要提供不同畫面佈局的時候,就可以在封裝後加上 @ViewBuilder 。

結語

以上,

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

環境

  • Xcode 15 beta 8

本篇使用到的 UI 元件和 modifiers 基本上沒有受到版本更新影響

因此 Xcode 14 等環境下使用也是沒問題的。


上一篇
Day 17 - 在 SwiftUI 使用 @ViewBuilder
下一篇
Day 19 - 用 SwiftUI 的 repeatForever 做出持續進行的動畫
系列文
SwiftUI 的大大小小30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言