深入理解 SwiftUI 中的 @ViewBuilder:从语法糖到实战

原文链接:www.avanderlee.com/swiftui/vie...

什么是 @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 // 低版本保持原样
        }
    }
}

总结与扩展

核心要点

  1. @ViewBuilder 是 结果构建器,用于组合多个视图。
  2. SwiftUI 的 View.body 默认使用 @ViewBuilder,所以无需手动添加。
  3. 可以用于:
    • 初始化器(如 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)采用类似模式。
相关推荐
茶底世界之下5 小时前
时间线效果层,为什么不能只复用一个处理器?
ios·swift
Cc.Y7 小时前
Swift深度链接的极简之道
ios·cocoa·swift
传奇开心果编程18 小时前
【SwiftUI提高练中学】第9课 安全与隐私
swiftui·移动开发·swift·编程语言理论·开发范式·编程框架·声明式ui
茶底世界之下1 天前
动态切换 nearest / linear 时,为什么不能只替换 sampler
ios·swift
应用市场1 天前
把旧 Pixel 变成相册备份中转站(上):Mac 到安卓的照片传输工具设计——流式上传、sha256 校验、adb forward 与 Bonjour
android·macos·adb·kotlin·swift
东坡肘子1 天前
Swift Server,又多了一个 Google -- 肘子的 Swift 周报 #156
人工智能·swiftui·swift
打工仔折腾 AI2 天前
从零写一个CAD 05:以鼠标为中心的滚轮缩放,招法能复用但顺序不能反
开发语言·后端·python·性能优化·计算机外设·swift·ai agent 实战
茶底世界之下2 天前
视频预览切换为何会闪回旧帧:用 generation + mode identity 管住异步回调
ios·swift
茶底世界之下3 天前
类型擦除之后,Metal 滤镜组合为什么不能只执行一个 Filter?
ios·swift