SceneBuilder 是一種 ResultBuilder,它可以把多個 Scene 組成一個 Scene。
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 包起來。
關於 XcodeProject
XcodeProject 創立於 2023,致力於協助開發者探索 Apple 的創新世界,學習在 iOS、iPadOS、macOS、tvOS、visionOS 與 watchOS 上開發 App,發現眾多技術與框架,讓開發者獲得更多能力。