深入理解 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)采用类似模式。
相关推荐
HarderCoder9 小时前
Swift 中的可调用类型:彻底搞懂 `callAsFunction`、`@dynamicCallable` 与 `@dynamicMemberLookup`
swift
CuiXg9 小时前
iOS XML 处理利器:CNXMLParser 与 CNXMLDocument 深度解析
ios·swift
HarderCoder10 小时前
Swift 中 Enum 与 Struct:如何为状态建模选择最合适的工具
swift
大熊猫侯佩11 小时前
韦爵爷闯荡 Swift 6 江湖:单例秘籍新解(上)
swift·编程语言·apple
大熊猫侯佩11 小时前
韦爵爷闯荡 Swift 6 江湖:单例秘籍新解(中)
swift·敏捷开发·apple
大熊猫侯佩12 小时前
韦爵爷闯荡 Swift 6 江湖:单例秘籍新解(下)
swift·敏捷开发·apple
浅浅一笑^*^12 小时前
ArcGIS 4.x 绘图
开发语言·arcgis·swift
Swift社区15 小时前
Swift 解法详解:LeetCode 368《最大整除子集》
开发语言·leetcode·swift
东坡肘子16 小时前
写给这段旅程,也写给未来的自己 | 肘子的 Swift 周报 #0100
swiftui·swift·apple