
在上一回,我們嘗試了怎麼透過 @ViewBuilder 來作為參數傳入一個 view 。那今天我們要用這個來解決另外一個問題。
在一些畫面中,我們需要根據條件判斷要回傳哪一個 UI 。

像是這樣,當寫出這樣的程式碼的地方,在 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()')
提示在條件分歧的地方,實際型別不同而產生問題
private func itemView(type: DeviceType) -> some View {
    Group {
        switch type {
        case .pad:
            padView()
        case .phone:
            phoneView()
        }
    }
}
只要用 Group 或 VStack 之類的 UI 元件包起來,錯誤就會消失了。
但是這個解法不是很漂亮,因為加了這個 Group ,對 UI 建構來說可能是一個不需要存在的東西,所以看起來 workaround 感非常重。
這時候就可以使用 @ViewBuilder 來標記這個方法。
改寫之後就會像這樣,方法裡面就只留下必要的邏輯,乾淨許多:
@ViewBuilder private func itemView(type: DeviceType) -> some View {
    switch type {
    case .pad:
        padView()
    case .phone:
        phoneView()
    }
}
除了像是根據裝置不同,提供不一樣的畫面佈局,也可以用在類似於工廠方法的情境。
例如在不同狀態、不同型態下會需要提供不同畫面佈局的時候,就可以在封裝後加上 @ViewBuilder 。
以上,
那今天的 SwiftUI 的大大小小就到這邊,明天見!
本篇使用到的 UI 元件和 modifiers 基本上沒有受到版本更新影響
因此 Xcode 14 等環境下使用也是沒問題的。