布局系统
问题
SwiftUI 的布局流程是什么?Stack、LazyStack、GeometryReader 各自的作用?
答案
SwiftUI 布局三步骤
- 父视图提议:向子视图建议一个可用尺寸
- 子视图决定:子视图根据自身内容决定实际大小(可以忽略建议)
- 父视图放置:父视图根据子视图返回的尺寸进行定位
Stack 布局
VStack(alignment: .leading, spacing: 12) { // 垂直排列
HStack { // 水平排列
Image(systemName: "person")
Text("Name")
Spacer() // 占满剩余空间
}
Text("Description")
.font(.caption)
}
.padding()
LazyStack — 懒加载
// 普通 VStack:一次性创建所有子视图
VStack { ForEach(0..<10000) { Text("\($0)") } }
// LazyVStack:只创建可见区域的子视图
ScrollView {
LazyVStack {
ForEach(0..<10000) { i in
Text("\(i)")
.onAppear { print("Visible: \(i)") }
}
}
}
VStack vs LazyVStack
VStack:一次性计算所有子视图,适合少量视图LazyVStack:按需创建,适合大量数据(类似 UITableView 的复用)LazyVGrid/LazyHGrid:懒加载网格布局
GeometryReader
获取视图的几何信息(尺寸和位置):
struct AdaptiveView: View {
var body: some View {
GeometryReader { geometry in
if geometry.size.width > 500 {
HStack { content } // 宽屏横向排列
} else {
VStack { content } // 窄屏纵向排列
}
}
}
}
GeometryReader 的陷阱
GeometryReader 会占满所有可用空间(类似 Spacer),可能导致布局意外变大。尽量减少使用,优先用 containerRelativeFrame(iOS 17+)或具体的布局容器。
frame 修饰符
Text("Hello")
.frame(width: 200, height: 50) // 固定尺寸
.frame(maxWidth: .infinity) // 最大宽(配合 alignment)
.frame(minHeight: 44) // 最小高
.frame(idealWidth: 300, idealHeight: 200) // 理想尺寸
对齐
VStack(alignment: .leading) {
Text("Title").font(.title)
Text("Subtitle").font(.caption)
}
ZStack(alignment: .bottomTrailing) {
Image("background")
Text("Badge")
.padding(4)
.background(.red)
}
常见面试问题
Q1: SwiftUI 的布局和 AutoLayout 有什么区别?
答案:
- AutoLayout:基于约束方程组求解(Cassowary 算法),从约束推导 frame
- SwiftUI:基于提议-决定模型,父子视图协商尺寸,更直观和高效
- SwiftUI 不需要设置约束优先级、解决冲突等,布局逻辑更声明式
Q2: Spacer 和 Color.clear 的区别?
答案:
Spacer():占满可用空间,只在 Stack 中生效Color.clear:是一个 View,会占满所有可用空间(宽+高),适合做占位
Q3: ScrollView 内使用 VStack 还是 LazyVStack?
答案:数据量大(几十个以上)用 LazyVStack。注意 LazyVStack 的子视图在首次出现时创建、滑出后可能被销毁(由系统决定),不适合保存状态。