SwiftUI

2024 年 1 月 14 日

SwiftUI SceneBuilder 用來組視窗的 buildBlock

已複製到剪貼板


SceneBuilder 是一種 ResultBuilder,它可以把多個 Scene 組成一個 Scene。

SwiftUI
SwiftUI

SwiftUI 的 SceneBuilder 在哪裡?

在以 SwiftUI 為主的專案中,最上層的 App 架構再下來一層,就是 Scene 了:

@main
struct Example: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

在這樣最基礎的 SwiftUI App 程式下,變數 body 會回傳 some Scene,這看起來再正常不過,也沒什麼,但它其實就是我們這一篇的重點「SceneBuilder」!

@resultBuilder
struct SceneBuilder

SwiftUI 建視窗的 SceneBuilder

SceneBuilder 的功用,就是建出 Scene。如果我們在 body 裡寫了多個 WindowGroup,那 body 這個 SceneBuilder 就會把這些 Scene 組成一個 Scene,因為它只能回傳一個東西「some Scene」。

SwiftUI Scene 的定義是這樣的:

@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
public protocol Scene {

    associatedtype Body : Scene
    
    @SceneBuilder @MainActor var body: Self.Body { get }
}

從這裡可以發現,遵從 Scene protocol 的人,會有個變數是 body,而這個 body 就被 @SceneBuilder 與 @MainActor 裝飾著。

SwiftUI SceneBuilder 用來建視窗的 buildBlock

雖然我們無法看到 Apple 是如何實作底層的程式「將多個 Scene 組成一個 Scene」,但還是可以透過 func 的定義來認識 SceneBuilder。

文章的最一開始有提到,SceneBuilder 是一種 ResultBuilder,而定義 ResultBuilder 有個必要條件就是實作 static func buildBlock(),buildBlock 的用意就是將多個傳進來的東西轉換成一個東西,並回傳。所以 SceneBuilder 的 buildBlock 就是將多個 Scene 組成一個 Scene 並回傳。

SwiftUI SceneBuilder 的定義長這樣:

@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
@resultBuilder public struct SceneBuilder {
    
    public static func buildExpression<Content>(_ content: Content) -> Content where Content : Scene
    // 只有一個 Scene 時,就回傳這個 Scene
    public static func buildBlock<Content>(_ content: Content) -> Content where Content : Scene
}

@available(*, unavailable)
extension SceneBuilder : Sendable {
}

@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
extension SceneBuilder {
    
    public static func buildOptional(_ scene: (Scene & _LimitedAvailabilitySceneMarker)?) -> some Scene
    
    @available(*, unavailable, message: "if statements in a SceneBuilder can only be used with #available clauses")
    public static func buildOptional<S>(_ scene: S?) -> where S : Scene
    
    public static func buildLimitedAvailability(_ scene: some Scene) -> Scene & _LimitedAvailabilitySceneMarker
}

@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
extension SceneBuilder {
    // 將兩個 Scene 組在一起,並回傳
    public static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) -> some Scene where C0 : Scene, C1 : Scene
    // 將三個 Scene 組在一起,並回傳
    public static func buildBlock<C0, C1, C2>(_ c0: C0, _ c1: C1, _ c2: C2) -> some Scene where C0 : Scene, C1 : Scene, C2 : Scene
    // 將四個 Scene 組在一起,並回傳
    public static func buildBlock<C0, C1, C2, C3>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3) -> some Scene where C0 : Scene, C1 : Scene, C2 : Scene, C3 : Scene
    // 將五個 Scene 組在一起,並回傳
    public static func buildBlock<C0, C1, C2, C3, C4>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4) -> some Scene where C0 : Scene, C1 : Scene, C2 : Scene, C3 : Scene, C4 : Scene
    // 將六個 Scene 組在一起,並回傳
    public static func buildBlock<C0, C1, C2, C3, C4, C5>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5) -> some Scene where C0 : Scene, C1 : Scene, C2 : Scene, C3 : Scene, C4 : Scene, C5 : Scene
    // 將七個 Scene 組在一起,並回傳
    public static func buildBlock<C0, C1, C2, C3, C4, C5, C6>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6) -> some Scene where C0 : Scene, C1 : Scene, C2 : Scene, C3 : Scene, C4 : Scene, C5 : Scene, C6 : Scene
    // 將八個 Scene 組在一起,並回傳
    public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7) -> some Scene where C0 : Scene, C1 : Scene, C2 : Scene, C3 : Scene, C4 : Scene, C5 : Scene, C6 : Scene, C7 : Scene
    // 將九個 Scene 組在一起,並回傳
    public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8) -> some Scene where C0 : Scene, C1 : Scene, C2 : Scene, C3 : Scene, C4 : Scene, C5 : Scene, C6 : Scene, C7 : Scene, C8 : Scene
    // 將十個 Scene 組在一起,並回傳
    public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9) -> some Scene where C0 : Scene, C1 : Scene, C2 : Scene, C3 : Scene, C4 : Scene, C5 : Scene, C6 : Scene, C7 : Scene, C8 : Scene, C9 : Scene
}

Apple 在 SceneBuilder 裡定義了 10 個重載(overload)的 buildBlock,我們在 body 裡寫的 Scene 在 compile 的階段就會經過這些對應的 buildBlock 並組出一個 Scene,從這些 Apple 定義的 buildBlock func 可以發現,它最多可以吃到 10 個 Scene,這也說明了為什麼我們不能在 body 裡寫超過 10 個東西,超過時就必須用 Group 包起來。

分享文章

已複製到剪貼板

主題文章

查看 SwiftUI

超級感謝

關於 XcodeProject

XcodeProject 創立於 2023,致力於協助開發者探索 Apple 的創新世界,學習在 iOS、iPadOS、macOS、tvOS、visionOS 與 watchOS 上開發 App,發現眾多技術與框架,讓開發者獲得更多能力。


Contacts

Ricky Chuang

XcodeProject

RickyChuang.xcodeproj@gmail.com

XcodeProject 聯絡

contact.xcodeproj@gmail.com

最新文章