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

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

什么是 @ViewBuilder?

@ViewBuilder 是 SwiftUI 提供的结果构建器(Result Builder) 之一,它让我们可以用 声明式语法 组合多个视图,而无需显式使用 return

在 SwiftUI 中,所有的视图协议 Viewbody 属性 都隐式使用了 @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 返回不同类型(如 TextButton),会报错:

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(如 SwiftDataRegex),未来可能会有更多领域(如服务端 Swift)采用类似模式。
相关推荐
用户794572239541313 小时前
【AFNetworking】OC 时代网络请求事实标准,Alamofire 的前身
objective-c·swift
报错小能手16 小时前
SwiftUI 框架 认识 SwiftUI 视图结构 + 布局
ui·ios·swift
东坡肘子17 小时前
被 Vibe 摧毁的版权壁垒,与开发者的新护城河 -- 肘子的 Swift 周报 #131
人工智能·swiftui·swift
报错小能手1 天前
ios开发方向——swift错误处理:do/try/catch、Result、throws
开发语言·学习·ios·swift
小夏子_riotous1 天前
openstack的使用——5. Swift服务的基本使用
linux·运维·开发语言·分布式·云计算·openstack·swift
mCell2 天前
MacOS 下实现 AI 操控电脑(Computer Use)的思考
macos·agent·swift
用户79457223954132 天前
【DGCharts】iOS 图表渲染事实标准——8 种图表类型、高度可定制,3 行代码画出一条折线
swiftui·swift
chaoguo12342 天前
Any metadata 的内存布局
swift·metadata·value witness table
tangweiguo030519873 天前
SwiftUI布局完全指南:从入门到精通
ios·swift
用户79457223954134 天前
【RxSwift】Swift 版 ReactiveX,响应式编程优雅处理异步事件流
swift·rxswift