什么是 @ViewBuilder?
@ViewBuilder
是 SwiftUI 提供的结果构建器(Result Builder) 之一,它让我们可以用 声明式语法 组合多个视图,而无需显式使用 return
。
在 SwiftUI 中,所有的视图协议 View
的 body
属性 都隐式使用了 @ViewBuilder
,这也是为什么我们能在 body
里直接写多个视图而不需要 return
。
为什么需要 @ViewBuilder?
没有 @ViewBuilder
时会发生什么?
假设我们定义了一个 没有 @ViewBuilder
的协议:
swift
protocol NonResultBuilderView {
associatedtype Body: View
var body: Self.Body { get }
}
如果我们尝试写一个 if-else
语句:
swift
struct SalesFooterView: NonResultBuilderView {
let isPro: Bool
var body: some View {
if isPro {
Text("Hello, PRO user!")
} else {
Text("Hi there, don't you want to become PRO?")
}
}
}
如果 if-else
返回不同类型(如 Text
和 Button
),会报错:
swift
struct SalesFooterView: NonResultBuilderView {
let isPro: Bool
var body: some View {
if isPro {
return Text("Hello, PRO user!")
} else {
return Button("Become PRO") {
startPurchase()
}
}
}
func startPurchase() {
// 购买逻辑
}
}
错误:
Function declares an opaque return type, but the return statements in its body do not have matching underlying types.
修复方式:使用 @ViewBuilder
swift
struct SalesFooterView: NonResultBuilderView {
let isPro: Bool
@ViewBuilder
var body: some View {
if isPro {
Text("Hello, PRO user!")
} else {
Button("Become PRO") {
}
}
}
}
@ViewBuilder
会自动将多个视图合并成一个 TupleView
,从而解决类型不一致的问题。
@ViewBuilder 的 3 种使用方式
在初始化器中使用(类似 VStack
/HStack
)
swift
struct VHStack<Content: View>: View {
@Environment(\.horizontalSizeClass) var horizontalSizeClass
let content: Content
init(@ViewBuilder _ content: () -> Content) {
self.content = content()
}
var body: some View {
if horizontalSizeClass == .compact {
VStack { content } // 竖直布局
} else {
HStack { content } // 水平布局
}
}
}
使用方式:
swift
struct ContentView: View {
var body: some View {
VHStack {
Text("Hello, World!")
Text("Result Builders are great!")
}
}
}
在属性上使用(更紧凑的写法)
swift
struct VHStack<Content: View>: View {
@Environment(\.horizontalSizeClass) var horizontalSizeClass
@ViewBuilder var content: () -> Content // 直接标记属性
var body: some View {
if horizontalSizeClass == .compact {
VStack(content: content) // 直接传入
} else {
HStack(content: content)
}
}
}
在方法上使用(条件修饰符)
swift
extension Slider {
@ViewBuilder
func minimumTrackColor(_ color: Color) -> some View {
if #available(macOS 11.0, *) {
accentColor(color) // macOS 11+ 支持
} else {
self // 低版本保持原样
}
}
}
总结与扩展
核心要点
@ViewBuilder
是 结果构建器,用于组合多个视图。- SwiftUI 的
View.body
默认使用@ViewBuilder
,所以无需手动添加。 - 可以用于:
- 初始化器(如
VStack { ... }
) - 属性(
@ViewBuilder var content: () -> Content
) - 方法(
@ViewBuilder func makeView() -> some View
)
- 初始化器(如
进阶使用场景
✅ 动态列表(根据条件返回不同视图)
swift
struct DynamicList: View {
let items: [String]
let isGrid: Bool
@ViewBuilder
var body: some View {
if isGrid {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))]) {
ForEach(items, id: \.self) { Text($0) }
}
} else {
List(items, id: \.self) { Text($0) }
}
}
}
✅ 空状态处理
swift
struct SafeView<Content: View>: View {
let isEmpty: Bool
@ViewBuilder let content: () -> Content
var body: some View {
if isEmpty {
Text("No data")
.foregroundColor(.gray)
} else {
content()
}
}
}
✅ 条件动画
swift
extension View {
@ViewBuilder
func conditionalAnimation(_ animate: Bool) -> some View {
if animate {
self.transition(.scale) // 带动画
} else {
self // 无动画
}
}
}
我的思考
@ViewBuilder
不仅仅是 语法糖,它让 SwiftUI 的 声明式语法 更加自然。
- 对比 UIKit:以前我们用
if-else
动态布局时,需要手动管理视图层级,而@ViewBuilder
让这一切变得 自动且类型安全。 - 性能:虽然
if-else
可能会影响视图更新(SwiftUI 会重建视图),但在大多数情况下,它的开销可以忽略。 - 未来方向:Swift 的 结果构建器 已经扩展到
Swift DSL
(如SwiftData
、Regex
),未来可能会有更多领域(如服务端 Swift)采用类似模式。