SwiftUI 框架

利用 SwiftUI 的ViewBuilder 輕鬆創建複雜佈局和視圖層次結構

我們可以利用 SwiftUI 的 ViewBuilder 工具,來在使用者界面中構建和組織視圖或視覺元素。在這篇文章中,Arc 會帶大家利用 ViewBuilder 來創建子視圖,看看這個工具如何簡化創建複雜佈局和視圖層次結構的過程。
利用 SwiftUI 的ViewBuilder 輕鬆創建複雜佈局和視圖層次結構
Photo by sarab hussain on Unsplash
利用 SwiftUI 的ViewBuilder 輕鬆創建複雜佈局和視圖層次結構
Photo by sarab hussain on Unsplash
In: SwiftUI 框架
‌本篇原文(標題:SwiftUI Tutorial: Create Child Views with ViewBuilder)刊登於作者 Medium,由 Arc Sosangyo 所著,並授權翻譯及轉載。

我們可以利用 SwiftUI 的 ViewBuilder 工具,來在使用者界面中構建和組織視圖或視覺元素 (visual element)。有了 ViewBuilder,我們就可以創建視圖的層次結構 (hierarchy);也就是說,其中一個視圖會是父視圖 (parent view),而其他視圖則是子視圖 (child view)。ViewBuilder 通常用於在 SwiftUI 中創建可重用元件,像是可以在 App 中多次使用的某組視圖。要使用 ViewBuilder,我們需要定義一個回傳一個或多個視圖的函式或閉包,然後就可以呼叫這個函式或閉包,來構建我們的使用者界面,並把產生的視圖添加到佈局中。ViewBuilder 讓我們可以輕鬆地定義及重用元件,大大簡化了創建複雜佈局和視圖層次結構的過程。

要宣告 (declare) ViewBuilder,我們需要使用 @ViewBuilder 來包裝屬性 (property) 或函式:

// Property example
@ViewBuilder var content: () -> Content

// Function(method) example
@ViewBuilder private func mainView() -> some View {}

// Some prefer this format to be cleaner for function(method)
@ViewBuilder 
private func mainView() -> some View {}

在函式中使用 ViewBuilder

要在 SwiftUI 中編寫 clean code,就需要培養把視圖分成不同小函式的習慣。我們可以用 ViewBuilder 包裝這些函式,讓程式碼更有組織、更加可讀。讓我們看看以下例子:

struct ContentView: View {

    var body: some View {
        VStack(spacing: 25) {
            sfSymbolWithLabel("person.fill")
            sfSymbolWithLabel("house.fill")
        }
    }
    
    @ViewBuilder
    private func sfSymbolWithLabel(_ name: String) -> some View {
        HStack {
            Group {
                Image(systemName: name)
                Text(name)
            }
            .font(.system(size: 30))
            .foregroundColor(.blue)
        }
    }

} // ContentView

以上範例使用 ViewBuilder 創建了一個函數,來回傳 View 協定 (protocol)。

SwiftUI:在屬性中存儲視圖

在這個部分,我們會看看如何在結構屬性中存儲 SwiftUI 視圖。這次,我們會利用 ViewBuilder 創建一個可重用的 SwiftUI 視圖。也就是說,我們會定義一個回傳視圖的結構,並使用 ViewBuilder 來包裝構成視圖的各種組件。

在範例中,我們會構建這個可重用的 SwiftUI 視圖:‌

長方形的視圖和右邊箭頭 (arrow) 的圖像是這個範例的固定元素,但你可以在左邊添加任何型別的視圖。

首先,讓我們這樣建立一個結構:

struct SelectionItem<Content: View>: View {
    
}

我們也需要使用一個泛型限制 (Generic Constraint),來告訴結構它可以接受任何符合 View 協定的型別。

在定義符合 View 協定的泛型型別時,Content 通常是標準命名規則 (naming convention);但我們也可以利用其他有效的 identifier 來取代它。

在剛建立的結構中,讓我們為之前宣告的泛型資料型別 (data type) 添加一個新屬性,並用 ViewBuilder 包裝它:

@ViewBuilder var content: () -> Content

接下來,讓我們編寫視圖 body 的程式碼。我們會創建一個 HStack,並設置所需要的修飾符:

var body: some View {
    HStack {

    }
    .frame(height: 60)
    .frame(maxWidth: .infinity)
    .background(Color.white)
    .border(.black)
    .shadow(radius: 5)
    .padding()
}

在上面的程式碼中,我們建立了一個有邊框和陰影的白色長方型。

在 HStack 內添加 Content 之前,讓我們先建立另一個函式來添加箭頭。在 body 下方添加以下程式碼:

@ViewBuilder
private func arrowToRight() -> some View {
    Image(systemName: "arrow.right")
        .font(.system(size: 25))
        .foregroundColor(.black)
        .padding()
}

在以上的程式碼中,我們把一個簡單的系統圖像 (system image) 放置了在 HStack 中。

接著,我們就可以繼續編寫 HStack Content 的程式碼。我們會從左到右放置 content 屬性、Spacer()、和 arrowToRight 函式。Spacer() 可以讓我們以適當的對齊方式分隔兩個視圖。在 HStack 中編寫以下程式碼:

content().padding()
Spacer()
arrowToRight()

以下是完整的源程式碼:

struct SelectionItem<Content: View>: View {
    
    @ViewBuilder var content: () -> Content
    
    var body: some View {
        HStack {
            content().padding()
            Spacer()
            arrowToRight()
        }
        .frame(height: 60)
        .frame(maxWidth: .infinity)
        .background(Color.white)
        .border(.black)
        .shadow(radius: 5)
        .padding()
    }
    
    @ViewBuilder
    private func arrowToRight() -> some View {
        Image(systemName: "arrow.right")
            .font(.system(size: 25))
            .foregroundColor(.black)
            .padding()
    }
    
}

我們已經完成了一個可重用的 SwiftUI 視圖,可以在主視圖中使用。比如說,如果我們在使用 ContentView,就可以這樣編輯:

struct ContentView: View {

    var body: some View {
        VStack(spacing: 0) {
            SelectionItem {
                Text("You can place any View here.")
            }
            SelectionItem {
                VStack {
                    Text("You can place")
                    Text("any View here.")
                }
            }
            SelectionItem {
                HStack {
                    Group {
                        Image(systemName: "sunrise")
                        Image(systemName: "sun.max")
                        Image(systemName: "sunset")
                    }
                    .font(.system(size: 25))
                }
            }
        }
    }

} // ContentView

讓我們來測試一下吧!當你執行程式碼時,應該會得到以下的視圖:

你可以在 GitHub 上參考這部分的源程式碼。

如果不使用 ViewBuilder ⋯⋯

可能大家會問:如果不使用 ViewBuilder,我們可不可以達到相同的結果呢?理論上是可以的,但這個方法有些缺點。具體來說,如果不使用 ViewBuilder,我們就需要在函式內手動創建和組合各個視圖。讓我們看看以下的螢幕截圖:

雖然有很多方法可以解決這個錯誤,像是把程式碼嵌入到 Group 視圖中、或是添加 return。但如果我們從一開始就使用 ViewBuilder,就可以防止發生這個錯誤了。

‌本篇原文(標題:SwiftUI Tutorial: Create Child Views with ViewBuilder)刊登於作者 Medium,由 Arc Sosangyo 所著,並授權翻譯及轉載。
作者簡介:AppStore 上 Adfectus 和 ML Lingo 的開發者,獨立作者,熱愛編程和旅遊,現居於日本。
譯者簡介:Kelly Chan-AppCoda 編輯小姐。
作者
AppCoda 編輯團隊
此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。
評論
更多來自 AppCoda 中文版
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。