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 惰性堆栈

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

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

Screenshot - 2025-02-13 15.08.45.png

Screenshot - 2025-02-13 15.09.04.png

ScrollView {
	LazyVStack(spacing: 10) {
	    ForEach(0..<100) {
	        CustomText("Item \\($0)")
	            .font(.title)
	    }
	}
	.frame(maxWidth: .infinity)
}

Screenshot - 2025-02-13 15.10.08.png

Screenshot - 2025-02-13 15.11.02.png

在滚动时固定指定视图

// 要在惰性堆栈中固定视图,需要使用到 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]) { ... }

Divider 分割线

Divider 用于制作分割线… 如果它被放在 VStack 视图里,它会被渲染成横线;如果它被放在 HStack 视图里,它会被渲染成竖线

Divider()
		// 设置分割线粗细
		.frame(height: 1)
		// 设置分割线颜色
    .background(Color(red: 240/255, green: 240/255, blue: 240/255))
    .padding(.horizontal)

Spacer 弹性空间

Spacer 弹性空间属于扩张型视图,主要用于填充空白。但也可以通过设置精确控制它的尺寸大小:

// 设置准确的高度
Spacer().frame(height: 50)

// 还可以指定最小和最大范围
Spacer()..frame(minHeight: 50, maxHeight: 500)

// 还可以通过 minLength 设置最小长度
Spacer(minLength: 0)

如何设置平分布局:

在一个视图中添加 N 个 Spacer ,其原理是将这个视图中的可用空间,分成N份,给到每一个Spacer。可以利用这一点来精细控制间隔。

//例如,我们可以在顶部留出三分之一的空间,在底部留出三分之二的空间,如下所示:
VStack {
    Spacer()
    Text("First")
    Text("Second")
    Text("Third")
    Spacer()
    Spacer()
}