跳到主要内容

布局系统

问题

SwiftUI 的布局流程是什么?Stack、LazyStack、GeometryReader 各自的作用?

答案

SwiftUI 布局三步骤

  1. 父视图提议:向子视图建议一个可用尺寸
  2. 子视图决定:子视图根据自身内容决定实际大小(可以忽略建议)
  3. 父视图放置:父视图根据子视图返回的尺寸进行定位

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 的子视图在首次出现时创建、滑出后可能被销毁(由系统决定),不适合保存状态。

相关链接