Skip to content

SwiftUI 的声明式 DSL

对比声明式和命令式

SwiftUI 的写法,核心语言特性就是:

  • 函数构建器 (Function Builder / @resultBuilder) → 把 {} 内的多行代码拼成一个返回值。
  • 链式调用 (Method Chaining) → 每次返回新 View,才能点点点。
  • 协议泛型 (View 协议) → 所有控件都 conform to View,能塞进容器。

SwiftUI 声明式 DSL

swift
HStack(spacing: 12) { // 水平堆叠
    Image(systemName: "person.circle.fill")
        .resizable()
        .frame(width: 50, height: 50)
        .foregroundColor(.blue)

    VStack(alignment: .leading) {
        Text("王小明")
            .font(.headline)
        Text("iOS 开发工程师")
            .font(.subheadline)
            .foregroundColor(.gray)
    }
}

编译器糖衣剥掉

swift
HStack(spacing: 12, content: {
    // Image 返回的是一个 View
    let image = Image(systemName: "person.circle.fill")
        .resizable()
        .frame(width: 50, height: 50)
        .foregroundColor(.blue)

    // VStack 内部的内容
    let vstack = VStack(alignment: .leading, content: {
        let text1 = Text("王小明")
            .font(.headline)

        let text2 = Text("iOS 开发工程师")
            .font(.subheadline)
            .foregroundColor(.gray)

        // ViewBuilder 会把 text1 + text2 合成 TupleView<(Text, Text)>
        return TupleView((text1, text2))
    })

    // ViewBuilder 把 image + vstack 合成 TupleView<(Image, VStack)>
    return TupleView((image, vstack))
})

UIKit 命令式

swift
let hStack = UIStackView()
hStack.axis = .horizontal
hStack.spacing = 12

let imageView = UIImageView(image: UIImage(systemName: "person.circle.fill"))
imageView.contentMode = .scaleAspectFit
imageView.tintColor = .blue
imageView.widthAnchor.constraint(equalToConstant: 50).isActive = true
imageView.heightAnchor.constraint(equalToConstant: 50).isActive = true

let vStack = UIStackView()
vStack.axis = .vertical
vStack.alignment = .leading

let nameLabel = UILabel()
nameLabel.text = "王小明"
nameLabel.font = UIFont.preferredFont(forTextStyle: .headline)

let jobLabel = UILabel()
jobLabel.text = "iOS 开发工程师"
jobLabel.font = UIFont.preferredFont(forTextStyle: .subheadline)
jobLabel.textColor = .gray

vStack.addArrangedSubview(nameLabel)
vStack.addArrangedSubview(jobLabel)

hStack.addArrangedSubview(imageView)
hStack.addArrangedSubview(vStack)

理解 Swift 的函数:函数 + 尾随闭包语法 + resultBuilde

1. 这是个普通的构造函数调用

HStack 在 SwiftUI 里就是一个 结构体 (struct),实现了 View 协议。 你写的这行:

swift
HStack(spacing: 12, content: {
    // 一堆 View
})

其实等价于调用它的构造函数:

swift
init(spacing: CGFloat, content: () -> Content)
  • spacing: 12 → 普通参数
  • content: { ... } → 一个闭包参数,返回 Content(通常是多个视图)

所以本质就是个函数调用,只不过构造器也是函数


2. 闭包参数的尾随闭包语法

Swift 有个语法糖:如果函数的最后一个参数是闭包,你可以把闭包直接写在括号外面

比如:

swift
// 普通写法
HStack(spacing: 12, content: {
    Text("A")
    Text("B")
})

// 语法糖:尾随闭包
HStack(spacing: 12) {
    Text("A")
    Text("B")
}

是不是就看着顺眼多了?所以你平常见到的 HStack { ... } 其实就是省略了 content:

3. 再深一层:闭包里的多行 View 是怎么变成一个返回值的?

这就是 SwiftUI 的黑科技:@resultBuilder (ViewBuilder)

平常闭包必须返回一个东西,比如:

swift
let v = { return Text("hi") }

但在 SwiftUI 里你能这样写:

swift
VStack {
    Text("hi")
    Text("bye")
}

这是因为 ViewBuilder 会把多行语句组装成一个 TupleView((Text, Text)),编译器自动打包,帮你“骗过”闭包必须只有一个返回值的规则


4. 类比

  • 普通函数调用:cook(food: "鸡蛋", way: { fry() })
  • 尾随闭包写法:cook(food: "鸡蛋") { fry() }
  • SwiftUI 写法:HStack { Image(); Text("hi") }

所以 HStack 根本没啥妖法,就是 函数 + 尾随闭包语法 + resultBuilder