Stack 堆栈

VStack、HStack、ZStack 实际上都是 @ViewBuilder,他们是返回 view 的函数,不允许为空。ViewBuilder 不绘制任何东西,它只是将不同的View组合到一起;但是加到 ViewBuilder 的修饰符,都会传递到它所包含的视图中去。关于 ViewBuilder 详细说明,请查看:‣

VStack

// alignment 和 spacing 可以设置对齐和间距
VStack(alignment: .leading, spacing: 10){
		Text("Choose")
		Text("Your Plan")
}
// 如果 VStack 堆栈中只有一个视图,并且不带任何参数,则可以将视图的初始值设定项直接传递给 VStack 以使代码更短:
if sizeClass == .compact {
    VStack(content: UserView.init)
} else {
    HStack(content: UserView.init)
}

HStack

HStack 和 VStack 类似,但有一些需要注意的:

1. 等分内部区块

//等分内部区块:通过设置 maxWidth: .infinity, 保持不同区块的大小一致
HStack{

	VStack{}
	.frame(minWidth: 0, maxWidth: .infinity, minHeight: 100)

	VStack{}
	.frame(minWidth: 0, maxWidth: .infinity, minHeight: 100)

}.padding(.horizontal)

2. 文本基线对齐

//对齐第一个/最后一个单词
HStack(alignment: .firstTextBaseline){...}
HStack(alignment: .lastTextBaseline){...}

Screenshot - 2025-02-10 12.35.03.png

ZStack

ZStack 和 VStack和HStack一样,都是收缩视图,但有时它会被内部的 Color 视图撑开,让人误以为是扩张型视图。

1. 设置对齐

ZStack 没有间距的概念,因为视图重叠,但它确实对齐。ZStack 的对齐是跟元素中,最大的那个元素对齐;所以当有一个大东西 ZStack 和一个小东西,你可以使两个视图都对齐到顶部

2. 绝对定位

绝对定位的视图,都可以用ZStack来实现

ZStack(alignment: .topLeading) {...}

// 调整某个具体元素的位置
ZStack{
	Text("Copywriting")
		.offset(x:0,y:80)
}

3. 设置扩张

// 把 ZStack 扩张到整个手机屏幕
ZStack{}
	.edgesIgnoringSafeArea(.all)

// 如果不希望内容也扩张到安全区域外,可以只给背景视图加忽略标记,而不要在ZStack里加
ZStack {            
	Color.gray                
		.edgesIgnoringSafeArea(.all) 
	...
}

LazyStack 惰性堆栈

惰性堆栈 vs 普通堆栈

LazyVStack & LazyHStack 被称为惰性堆栈。之前 scrollView 里面的子元素,都是一开始就全部添加进视图的,即使在屏幕上看不见。SwiftUI 不会等到向下滚动才展示它们,它只会立即创建它们,所以这样有时会占用内存。

如果想避免这种情况发生,可以采用称为 LazyVStackLazyHStack 的替代方案。它们的使用方式与常规堆栈完全相同,但会按需加载其内容(在实际显示之前它们不会创建视图)。因此最大限度地减少了所使用的系统资源量。

VStack 的行为:

VStack {
    ForEach(0..<1000) { i in
        Text("Row \\(i)")
    }
}

Screenshot - 2025-02-13 15.08.45.png

这就是 “非懒加载”。

LazyVStack 的行为:

LazyVStack {
    ForEach(0..<1000) { i in
        Text("Row \\(i)")
    }
}

Screenshot - 2025-02-13 15.09.04.png

⚠️ 并不是让子视图“自动懒”,而是它“延迟调用子视图的 body”

Screenshot - 2025-02-13 15.10.08.png

Screenshot - 2025-02-13 15.11.02.png

懒到底指的是谁

LazyVStack 懒的是:它对子视图的“创建时机”,而不是子视图本身“自动变懒”。也就是说:

对象 是否“懒” 原因
LazyVStack ✅ 是 它控制子视图的创建时机
子视图(Text / 自定义 View) ❌ 否 它们本身并不知道“懒”
LazyVStack + 子视图 因为父容器延迟创建它们

<aside> 💡

</aside>

结合 ForEach 使用

当 LazyVStack 和 ForEach 结合使用时,谁放外面?记住是,ForEach 要被包在 Lazy 容器里面。

在滚动时固定指定视图

// 要在惰性堆栈中固定视图,需要使用到 Section 视图,并定义其页头页脚。例如:
ScrollView {
		LazyVStack {
				Section {
						ForEach(O..<50) {
								Text("Item \\($O)").font(.title)
						}
				} header: {
						HeaderView()
				} footer: {
						FooterView()
				}
		}
}

这时可以使用 pinnedViews 参数,设置在滚动时固定某个视图了。该参数设置后,惰性堆栈中的每个 section 的指定视图都会在滑动时固定,只是本例中只有一个 section 。

// 指定 .sectionHeaders 可以在滑动时,让每个 section 的 Header 保持顶部固定
LazyVStack(pinnedViews: .sectionHeaders) {
	Section {
		ForEach(0..<50) {
			Text("Item \\($0)").font(.title)
		} 
	} header: {
			HeaderView()
	} footer: {
			FooterView()
	}
}

// 同理可以指定 .sectionFooters 固定每个 section 的 Footer
LazyVStack(pinnedViews: .sectionFooters) { ... }

// 还可以同时指定两个视图,固定两个视图
LazyVStack(pinnedViews: [.sectionHeaders, .sectionFooters]) { ... }