如何使用 VStack 和 HStack 创建堆栈?
我们的 SwiftUI 内容视图必须包含一个或多个视图,这是我们希望它们显示的布局。 当我们一次想要在屏幕上显示多个视图时,通常需要告诉 SwiftUI 如何安排它们,这就是堆栈的存放位置。
堆栈-等同于 UIKit 中的 UIStackView - 分为三种形式:水平(HStack),垂直(VStack)和基于深度(ZStack),当您要放置子视图以使其重叠时可以使用后者。
需要将其放置在垂直堆栈中,以便我们的文本视图彼此重叠放置:
VStack {
Text("SwiftUI")
Text("rocks")
}
水平并排放置标签,则将 VStack 替换为 HStack,如下所示:
HStack {
Text("SwiftUI")
Text("rocks")
}
如何使用对齐和间距自定义堆栈布局?
您可以通过在初始化程序中提供一个值来在 SwiftUI 堆栈内添加 spacing,如下所示:
VStack(spacing: 50) {
Text("SwiftUI")
Text("rocks")
}
您可以在项目之间创建分隔线,以便 SwiftUI 在堆栈中的每个项目之间进行小的视觉区分,如下所示:
VStack {
Text("SwiftUI")
Divider()
Text("rocks")
}
堆栈中的项目在中心对齐。 对于 HStack,这意味着项目在中间垂直对齐,因此,如果您有两个高度不同的文本视图,则它们都将在其垂直中心对齐。 对于 VStack,这意味着项目在中间居中对齐,因此,如果您有两个不同长度的文本视图,则它们都将与其水平中心对齐。
VStack(alignment: .leading) {
Text("SwiftUI")
Text("rocks")
}
这样会将 SwiftUI 和 rocks 都对齐到其左边缘,但是它们最终仍将位于屏幕的中间,因为堆栈仅占用所需的空间。
当然,您可以同时使用对齐和间距,如下所示:
VStack(alignment: .leading, spacing: 20) {
Text("SwiftUI")
Text("rocks")
}
如何使用 Spacer 将视图强制移到堆栈中的一侧?
SwiftUI 默认情况下将其视图居中,这意味着如果在 VStack 中放置三个文本视图,则这三个视图将在屏幕上垂直居中。 如果要更改此设置(如果要强制视图朝屏幕的顶部,底部,左侧或右侧),则应使用 Spacer 视图。
要将文本视图推到父视图的顶部,我们需要在其下方放置一个空格,如下所示:
VStack {
Text("Hello World")
Spacer()
}
如果您希望在 HStack 的前端和后端有两段文字,可以使用这样的间隔符:
HStack {
Text("Hello")
Spacer()
Text("World")
}
如何使用 zIndex 更改视图分层的顺序?
默认情况下,SwiftUI 的 ZStack 使用绘画者的算法来确定视图的深度,以决定视图的深度:首先绘制到 ZStack 中的所有内容都会被首先绘制,然后在其上分层放置随后的视图。
尽管这通常是您想要的,但有时您需要更多的控制权-例如,您可能希望在应用运行时将一个视图推到另一个视图的后面,或者在点击某个视图时将其移到最前面。
为此,您需要使用 zIndex() 修饰符,该修饰符允许您确切指定视图如何在单个 ZStack 中分层。 视图的默认Z索引为0,但是您可以提供正值或负值,分别将它们放置在其他视图的上方或下方。
例如,此 ZStack 包含两个重叠的矩形,但是绿色矩形仍然可见,因为它使用的 Z 索引值为 1:
ZStack {
Rectangle()
.fill(Color.green)
.frame(width: 50, height: 50)
.zIndex(1)
Rectangle()
.fill(Color.red)
.frame(width: 100, height: 100)
}
⚠️ 注意:zIndex() 修饰符仅影响当前 ZStack 内的绘制顺序。 这意味着,如果您有两个重叠的堆栈,则需要考虑堆栈的 Z 索引以及堆栈内部的视图。
如何使用尺寸类创建不同的布局?
SwiftUI 通过将大小型暴露在环境中供我们阅读来原生支持大小类。 要使用它们,首先创建一个 @Environment 对象,该对象将存储其值,然后在需要时检查该属性的值,查找 .compact 或 .regular 尺寸类。
struct ContentView: View {
#if !os(macOS)
@Environment(.horizontalSizeClass) var horizontalSizeClass
#endif
var body: some View {
#if !os(macOS)
if horizontalSizeClass == .compact {
Text("Compact")
} else {
Text("Regular")
}
#else
Text("Regular2")
#endif
}
}
如何使用 ScrollView 添加水平和垂直滚动?
SwiftUI 的 ScrollView 允许我们相对轻松地创建视图的滚动容器,因为它会自动调整自身大小以适合我们放置在其中的内容,并且还会自动添加额外的插图以避免出现安全区域。
例如,我们可以创建一个包含十个文本视图的滚动列表,如下所示:
ScrollView {
VStack(spacing: 20) {
ForEach(0..<10) {
Text("Item ($0)")
.foregroundColor(.white)
.font(.largeTitle)
.frame(width: 200, height: 200)
.background(Color.red)
}
}
}
.frame(height: 350)
滚动视图默认情况下是垂直的,但是您可以通过传入 .horizontal 作为第一个参数来控制轴。 因此,我们可以将前面的示例翻转为水平,如下所示:
ScrollView(.horizontal) {
HStack(spacing: 20) {
ForEach(0..<10) {
Text("Item ($0)")
.foregroundColor(.white)
.font(.largeTitle)
.frame(width: 200, height: 200)
.background(Color.red)
}
}
}
您可以使用 [.horizontal,.vertical] 同时指定两个轴。
最后,您可以决定是否在滚动动作发生时显示滚动指示器,如下所示:
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 20) {
ForEach(0..<10) {
Text("Item ($0)")
.foregroundColor(.white)
.font(.largeTitle)
.frame(width: 200, height: 200)
.background(Color.red)
}
}
}
如何使用 ScrollViewReader 使滚动视图移动到某个位置?
如果您想以编程方式使 SwiftUI 的 ScrollView 移至特定位置,则应在其中嵌入 ScrollViewReader。 这提供了一个 scrollTo() 方法,仅通过提供其锚即可将其移动到父 scrollview 内部的任何视图。
例如,这将在垂直滚动视图中创建 100 个彩色框,并且当您按下按钮时,它将直接滚动到 ID 为 8 的框:
struct ContentView: View {
let colors: [Color] = [.red, .green, .blue]
var body: some View {
ScrollView {
ScrollViewReader { value in
Button("Jump to #8") {
value.scrollTo(8)
}
.padding()
ForEach(0..<100) { i in
Text("Example (i)")
.font(.title)
.frame(width: 200, height: 200)
.background(colors[i % colors.count])
.id(i)
}
}
}
.frame(height: 350)
}
}
为了更好地控制滚动,可以指定另一个参数称为 anchor ,以控制滚动完成后应将目标视图放置在何处。
例如,这将滚动到与以前相同的视图,只是这次将视图置于顶部:
struct ContentView: View {
let colors: [Color] = [.red, .green, .blue]
var body: some View {
ScrollView {
ScrollViewReader { value in
Button("Jump to #8") {
value.scrollTo(8, anchor: .top)
}
.padding()
ForEach(0..<100) { i in
Text("Example (i)")
.font(.title)
.frame(width: 200, height: 200)
.background(colors[i % colors.count])
.id(i)
}
}
}
.frame(height: 350)
}
}
如果在 withAnimation() 中调用 scrollTo() ,则将对动画进行动画处理。
提示:scrollTo() 也适用于列表!
如何使用 ScrollView 和 GeometryReader 创建3D效果(如Cover Flow)?
如果将 GeometryReader 与任何可以改变位置的视图(例如具有拖动手势或位于 List 内的视图)结合使用,我们可以创建在屏幕上看起来很棒的3D效果。GeometryReader 允许我们读取视图的坐标,并将这些值直接输入到 rotation3DEffect() 修饰符中。
例如,我们可以通过在滚动视图中水平堆叠许多文本视图,然后应用 rotation3DEffect() 来创建 Cover Flow 样式的滚动效果,这样当它们在滚动视图中移动时,它们会缓慢地旋转,如下所示:
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 0) {
ForEach(1..<20) { num in
VStack {
GeometryReader { geo in
Text("Number (num)")
.font(.largeTitle)
.padding()
.background(Color.red)
.rotation3DEffect(.degrees(-Double(geo.frame(in: .global).minX) / 8), axis: (x: 0, y: 1, z: 0))
.frame(width: 200, height: 200)
}
.frame(width: 200, height: 200)
}
}
}
}
如何使用 LazyVGrid 和 LazyHGrid 在网格中放置视图?
SwiftUI 的 LazyVGrid 和 LazyHGrid 为我们提供了相当多的灵活性的网格布局。 最简单的网格由三部分组成:原始数据,描述所需布局的 GridItem 数组以及将数据和布局组合在一起的 LazyVGrid 或 LazyHGrid。
例如,这将使用大小为80点的像元创建垂直网格布局:
struct ContentView: View {
let data = (1...100).map { "Item ($0)" }
let columns = [ GridItem(.adaptive(minimum: 80)) ]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(data, id: .self) { item in
Text(item)
}
}
.padding(.horizontal)
}
.frame(maxHeight: 300)
}
}
使用 GridItem(.adaptive(minimum:80) 意味着我们希望网格可以容纳每行尽可能多的项目,每个网格的最小大小为 80点。
如果要控制列数,可以改用 .flexible(),它还可以让您指定每个项目的大小,但现在可以控制有多少列。 例如,这将创建五列:
struct ContentView: View {
let data = (1...100).map { "Item ($0)" }
let columns = [ GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()) ]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(data, id: .self) { item in
Text(item)
}
}
.padding(.horizontal)
}
.frame(maxHeight: 300)
}
}
第三种选择是使用固定大小。 例如,这将使第一列的宽度恰好为 100 点,并允许第二列填充所有剩余空间:
struct ContentView: View {
let data = (1...100).map { "Item ($0)" }
let columns = [ GridItem(.fixed(100)), GridItem(.flexible()), ]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(data, id: .self) { item in
Text(item)
}
}
.padding(.horizontal)
}
.frame(maxHeight: 300)
}
}
您还可以使用 LazyHGrid 制作水平滚动的网格,除了在初始化程序中接受行之外,其工作方式几乎相同。
例如,我们可以创建 10 个并排的标题图像,它们像这样水平滚动:
struct ContentView: View {
let items = 1...50
let rows = [ GridItem(.fixed(50)), GridItem(.fixed(50)) ]
var body: some View {
ScrollView(.horizontal) {
LazyHGrid(rows: rows, alignment: .center) {
ForEach(items, id: .self) { item in
Image(systemName: "(item).circle.fill")
.font(.largeTitle)
}
}
.frame(height: 150)
}
}
}
如何使用 LazyVStack 和 LazyHStack 延迟加载视图?
默认情况下,SwiftUI的VStack和HStack会预先加载其所有内容,如果在滚动视图中使用它们,可能会很慢。 如果您要延迟加载内容(即,仅当内容滚动到视图中时),则应适当使用LazyVStack和LazyHStack。
struct ContentView: View {
var body: some View {
ScrollView {
LazyVStack {
ForEach(1...100, id: .self) { value in
Text("Row (value)")
}
}
}
.frame(height: 300)
}
}
警告:这些惰性堆栈自动具有灵活的首选宽度,因此它们将以常规堆栈不会占用的方式占用可用空间。 要查看它们之间的区别,请尝试上面的代码,您会发现可以使用文本周围的空白来拖拉,但是如果您切换到常规 VStack,则会看到需要使用文本本身进行滚动。
使用惰性堆栈时,SwiftUI将在首次显示时自动创建视图。 之后,视图将保留在内存中,因此请注意显示的内容。
如果您想了解延迟加载的工作原理,请在iOS应用中尝试以下示例:
struct SampleRow: View {
let id: Int
var body: some View {
Text("Row (id)")
}
init(id: Int) {
print("Loading row (id)")
self.id = id
}
}
struct ContentView: View {
var body: some View {
ScrollView {
LazyVStack {
ForEach(1...100, id: .self, content: SampleRow.init)
}
}
.frame(height: 300)
}
}