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 中文版
如何在 SwiftUI App 中開發 Live Activities
SwiftUI 框架

如何在 SwiftUI App 中開發 Live Activities

Live Activities 首次於 iOS 16 推出,是 Apple 最令人興奮的更新之一,能讓 App 與使用者在即時互動上更有連結。它不再需要使用者不斷打開 App,Live Activities 可以讓資訊直接顯示在鎖定畫面和 Dynamic Island 上。
使用 Tool Calling 強化 Foundation Models 功能
AI

使用 Tool Calling 強化 Foundation Models 功能

在前幾篇教學中,我們介紹了 Foundation Models 在 iOS 26 中的運作方式,以及如何使用這個全新框架打造具備 AI 功能的應用。我們也介紹了 @Generable 巨集,它能輕鬆地將模型回應轉換為結構化的 Swift 類型。 現在,在這個 Foundation
活用 Foundation Models 的 @Generable 與 @Guide 製作測驗 App
AI

活用 Foundation Models 的 @Generable 與 @Guide 製作測驗 App

在前一篇教學中,我們介紹了 Foundation Models 框架,並示範了如何用它來進行基本的內容生成。那個過程相當簡單——你提供一個提示詞(prompt),等幾秒鐘,就能獲得自然語言的回應。在我們的範例中,我們建立了一個簡單的問答 App,讓使用者可以提問,App 則直接顯示生成的文字。 但如果回應變得更複雜——你需要把非結構化文字轉換為結構化的物件呢? 舉例來說,
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。