Appearance
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