
概述
在 WWDC 24 新推出的 SwiftUI 6.0 中,苹果对于容器内部子视图的布局有了更深入的支持。为了能够未雨绸缪满足实际 App 中所有可能的情况,我们还可以再接再厉,将 Sections 的支持也考虑进去。

SwiftUI 6.0 对容器子视图布局的增强支持可以认为是一个小巧的容器自定义布局(Custome Layout)简化器。
在本篇博文中,您将学到如下内容:
-
- 再进一步:支持段(Sections)的容器子视图布局
- 5.1 更小的容器组织单位:Section
- 5.2 支持 Sections 的 ForEach
- 5.3 支持 Sections 的 Group
有了全新容器子视图布局机制的加持,现在对于任何需要适配自定义容器行为的情况我们都可以游刃有余、从容应对了!
那还等什么呢?Let's go!!!
5. 再进一步:支持段(Sections)的容器子视图布局
5.1 更小的容器组织单位:Section
在实际 App 布局的"摆放"中,我们往往会进一步用段(Sections)来划分容器中的海量内容,这在 List 或 Form 等 SwiftUI 内置容器里是司空见惯的事情:
swift
struct ContentView: View {
var body: some View {
Form {
Section("概述") {
Color.yellow
.frame(height: 100)
Text("Hello World")
}
Section("高级") {
Color.red
.frame(height: 100)
Toggle("成为黑悟空", isOn: .constant(true))
.tint(.pink)
.padding(.horizontal)
}
Section("其它") {
Color.green
.frame(height: 100)
}
}
.font(.largeTitle.bold())
}
}
在上面的代码中,我们将之前容器中所有子视图都划分到 3 个独立的 Section 中去了:

那么问题来了:我们的自定义容器能否识别传入内容中的 Sections?
答案是肯定的!
可能聪明的小伙伴们都已经猜到了,之前我们讨论过的 SwiftUI 6.0 中 ForEach 和 Group 的新构造器都有支持 Sections 的重载形式:
下面我们就看看如何利用它们让自定义容器也支持 Sections 吧。
5.2 支持 Sections 的 ForEach
说出来大家可能不信,对之前 Carousel 容器的实现稍加修改,我们即可从容让它支持 Sections:
swift
struct Carousel<Content: View>: View {
@ViewBuilder var content: Content
var body: some View {
ScrollView {
LazyVStack {
ForEach(sections: content) { section in
VStack {
section.header
ScrollView(.horizontal) {
LazyHStack {
section.content
.containerRelativeFrame(.horizontal)
.frame(minHeight: 100)
}
.scrollTargetLayout()
}
.scrollIndicators(.hidden)
.scrollTargetBehavior(.viewAligned)
.contentMargins(16)
section.footer
}
}
}
}
}
}
从上面代码中我们可以看到,ForEach(sections:) 闭包传入的实参是一个 SectionConfiguration 类型。如果我们希望的话,还可以使用 SectionConfiguration 的 content 属性进一步分离段中的每一个子视图:
下面的代码演示了这种用法:
swift
ForEach(sections: content) { section in
VStack {
section.header
ScrollView(.horizontal) {
LazyHStack {
ForEach(section.content) { subview in
subview
.containerRelativeFrame(.horizontal)
.frame(minHeight: 100)
}
}
.scrollTargetLayout()
}
.scrollIndicators(.hidden)
.scrollTargetBehavior(.viewAligned)
.contentMargins(16)
section.footer
}
}
现在,对于容器中包含若干 Sections 的情况,我们的 Carousel 也可以"应付自如"了:
swift
struct ContentView: View {
var body: some View {
Carousel {
Section("概述") {
Color.yellow
.frame(height: 100)
Text("Hello World")
}
Section("高级") {
Color.red
.frame(height: 100)
Toggle("成为黑悟空", isOn: .constant(true))
.tint(.pink)
.padding(.horizontal)
}
Section("其它") {
Color.green
.frame(height: 100)
}
}
.font(.largeTitle.bold())
}
}
编译并在预览中看一看效果吧:

5.3 支持 Sections 的 Group
类似的,Group 同样对 Sections 提供了完善的支持。
如您所愿,我们也只需在 Magazine 容器的原有实现上"略施小计",即可大功告成:
swift
struct Magazine<Content: View>: View {
@ViewBuilder var content: Content
var body: some View {
ScrollView {
LazyVStack {
Group(sections: content) { sections in
if !sections.isEmpty {
GroupBox(label: sections[0].header) {
ZStack {
sections[0].content.frame(minHeight: 100)
}
}
}
if sections.count > 1 {
ForEach(sections[1...]) { section in
section.header
ScrollView(.horizontal) {
LazyHStack {
section.content
.containerRelativeFrame(.horizontal)
.frame(minHeight: 100)
}
}
.contentMargins(16)
section.footer
}
}
}
}
}
}
}
从上面的代码可以看到,我们对容器中的第一个段做了特殊对待。
还是那个 ContentView,只不过里面的容器现在是 Magazine "当家做主"了:
swift
struct ContentView: View {
var body: some View {
Magazine {
Section("概述") {
Color.yellow
.frame(height: 100)
Text("Hello World")
}
Section("高级") {
Color.red
.frame(height: 100)
Toggle("成为黑悟空", isOn: .constant(true))
.tint(.pink)
.padding(.horizontal)
}
Section("其它") {
Color.green
.frame(height: 100)
}
}
.font(.largeTitle.bold())
}
}
运行代码,看看我们支持 Sections 崭新的 Magazine 吧:

至此,我们自定义容器已然完全支持 Sections 啦!小伙伴们还不赶紧给自己一个大大的赞!棒棒哒!💯
总结
在本篇博文中,我们介绍了 SwiftUI 6.0(iOS 18)如何让自定义容器支持 Sections 布局,超简单的哦!
感谢观赏,再会吧!8-)