SwiftUI 新容器视图 API 深度解析:轻松构建自定义布局
前言
自 SwiftUI 的第一个版本发布以来,它就拥有了几种容器视图。最常用的有 HStack、VStack、List 等。今年,Apple 引入了新的 API,使我们能够以全新的方式构建自定义容器视图。本周,我们将学习 SwiftUI 新的分解 API 的优势。
容器视图
容器视图就是一个可以包含其他视图的视图。我们可以使用 @ViewBuilder
闭包轻松定义一个容器视图。以下是一个示例:
struct Card<Content: View>: View {
@ViewBuilder var content: Content
var body: some View {
VStack {
content
}
.padding()
.background(Material.regular, in: .rect(cornerRadius: 8))
.shadow(radius: 4)
}
}
如上面的例子所示,我们创建了 Card
视图,它是一个用于容纳任何 SwiftUI 视图的容器视图。它使用 @ViewBuilder
闭包包裹了内容,并添加了一个圆角背景和阴影。
struct ContentView: View {
var body: some View {
Card {
Text("Hello, World!")
Text("My name is Majid Jabrayilov")
}
}
}
这个 Card
类型使用起来非常简单。你只需创建一个 Card
,并使用闭包提供内容。通过在 Card
容器视图内嵌入不同的视图,你可以在应用的多个屏幕中复用它。
这是使用容器视图的主要优势之一:你可以通过将共享的功能封装在容器视图中,在应用的不同地方重复使用它们。
想了解更多关于 @ViewBuilder
闭包的内容,可以查看我关于 “SwiftUI 中 @ViewBuilder
的强大功能” 的文章。
使用 ViewBuilder
@ViewBuilder
闭包让我们可以轻松地组合多个视图,并将一个视图嵌入到另一个视图中。但是如何从 @ViewBuilder
闭包中提取子视图呢?SwiftUI 引入了新的 API,允许我们重新组合视图。例如,我们可以从通过 @ViewBuilder
闭包构建的内容视图中提取子视图,并根据需要将它们放置。
struct Carousel<Content: View>: View {
@ViewBuilder var content: Content
var body: some View {
ScrollView(.horizontal) {
LazyHStack {
ForEach(subviews: content) { subview in
subview
.containerRelativeFrame(.horizontal)
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
.contentMargins(16)
}
}
如上面的示例所示,我们使用了带有 subviews
参数的 ForEach
视图,这使我们能够提取内容视图的子视图并对它们进行迭代。
struct ContentView: View {
var body: some View {
Carousel {
Color.yellow
Color.orange
Color.red
Color.blue
Color.green
}
}
}
SwiftUI 使用特定的 Subview
类型来公开提取视图的实例。它符合 View
协议,因此我们仍然可以附加额外的 SwiftUI 视图修饰符。它还为我们提供了 id
属性,这是一个唯一标识符,以及与特定视图关联的容器值。我们将在接下来的文章中更多讨论容器值。
访问子视图
另一种新的 API 允许我们通过索引访问子视图,而不是使用 ForEach
视图进行迭代。
struct Magazine<Content: View>: View {
@ViewBuilder var content: Content
var body: some View {
ScrollView {
Group(subviews: content) { subviews in
if !subviews.isEmpty {
subviews[0]
.padding(.horizontal)
.containerRelativeFrame(.vertical) { length, _ in
return length / 3
}
}
if subviews.count > 1 {
ScrollView(.horizontal) {
LazyHStack {
ForEach(subviews[1...], id: \.id) { subview in
subview
.containerRelativeFrame([.horizontal, .vertical])
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
.contentMargins(16)
}
}
}
}
}
在上面的示例中,我们使用了带有 subviews
参数的 Group
视图,它允许我们将子视图提取到一个名为 SubviewsCollection
的集合类型中。SubviewsCollection
类型符合 RandomAccessCollection
协议,并为我们提供了通过索引访问的功能。
组合子视图
如你所见,我们使用 Group
视图来分解内容视图,然后以另一种方式组合子视图。我们还利用了 id
参数的功能,允许我们使用 ForEach
视图与普通数据一起工作。
struct ContentView: View {
var body: some View {
Magazine {
Color.yellow
Color.orange
Color.red
Color.blue
Color.green
}
}
}
可运行的 Demo
根据文章内容,我将提供一个可以展示如何使用 SwiftUI 新的容器视图 API 构建自定义视图的简单示例,包含 Card
、Carousel
和 Magazine
容器视图。
import SwiftUI
// 定义 Card 视图,作为一个基本的容器视图
struct Card<Content: View>: View {
@ViewBuilder var content: Content
var body: some View {
VStack {
content
}
.padding()
.background(Material.regular, in: RoundedRectangle(cornerRadius: 8))
.shadow(radius: 4)
}
}
// 定义 Carousel 视图,横向滚动的自定义容器视图
struct Carousel<Content: View>: View {
@ViewBuilder var content: Content
var body: some View {
ScrollView(.horizontal) {
LazyHStack {
ForEach(subviews: content) { subview in
subview
.containerRelativeFrame(.horizontal)
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
.contentMargins(16)
}
}
// 定义 Magazine 视图,具有垂直和水平组合布局的自定义容器视图
struct Magazine<Content: View>: View {
@ViewBuilder var content: Content
var body: some View {
ScrollView {
Group(subviews: content) { subviews in
// 第一个子视图为大图
if !subviews.isEmpty {
subviews[0]
.padding(.horizontal)
.containerRelativeFrame(.vertical) { length, _ in
return length / 3
}
}
// 其余子视图为横向滚动小图
if subviews.count > 1 {
ScrollView(.horizontal) {
LazyHStack {
ForEach(subviews[1...], id: \.id) { subview in
subview
.containerRelativeFrame([.horizontal, .vertical])
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
.contentMargins(16)
}
}
}
}
}
// 主视图,使用自定义容器视图
struct ContentView: View {
var body: some View {
VStack {
// 使用 Card 视图
Card {
Text("SwiftUI 容器视图示例")
.font(.headline)
Text("使用 Card 容器轻松复用视图")
}
.padding()
// 使用 Carousel 视图
Carousel {
Color.yellow
Color.orange
Color.red
Color.blue
Color.green
}
.frame(height: 100)
.padding()
// 使用 Magazine 视图
Magazine {
Color.pink
Color.purple
Color.teal
Color.mint
}
.frame(height: 300)
}
.padding()
}
}
// 主应用入口
@main
struct ContainerViewDemoApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
功能概述
- Card:一个简单的容器视图,可以包裹任何内容并添加背景和阴影。你可以在应用中的多个地方使用该容器来保持一致的样式。
- Carousel:一个横向滚动的容器视图,可以自动排列并展示内容,适合展示横向滑动的图像或视图。
- Magazine:一个自定义的容器视图,允许你将第一个子视图设置为大图,其他子视图横向排列展示。类似于杂志布局。
运行这个Demo
此代码展示了如何在 SwiftUI 中构建自定义的容器视图,灵活地将不同的布局封装在容器中,以便在应用中多次复用这些布局模式。
总结
通过使用 SwiftUI 新引入的 API 以及容器视图,你可以轻松构建具有良好复用性的自定义布局,提升应用的开发效率和代码可维护性。