2.1 布局一个简单视图

原文链接: https://developer.apple.com/documentation/swiftui/laying-out-a-simple-view

2.1 布局一个简单视图

通过调整视图尺寸来创建视图布局。

概述

要为视图创建布局,先组合出一个子视图层级。然后,通过配置参数以及添加视图修饰符(例如影响视图框架和内边距的那些)来细化子视图的尺寸与间距。关于如何组合布局,请参阅 用栈视图构建布局。

建立视图层级

下面的示例创建了一个用于显示消息服务收到的消息的视图。这个视图使用 HStack 把标识发送者的视图和提供消息内容的视图组合在一起:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
struct MessageRow: View {
    let message: Message

    var body: some View {
        HStack {
            ZStack {
                Circle()
                    .fill(Color.yellow)
                Text(message.initials)
            }

            Text(message.content)
        }
    }
}

下面这张 MessageRow 视图的截图带有显示其边界的边框。请注意圆形尺寸很大,它填满了可用的空间:

一张截图,显示一个黑色矩形作为视图的边框,其中左侧有一个很大的黄色圆形,圆形中央是字母“MR”。视图右侧有五行文本。文本和圆形在视图中垂直居中。

当 SwiftUI 渲染视图层级时,它会递归地评估每个子视图:父视图向其包含的子视图提议一个尺寸,子视图则返回一个计算出的尺寸。

一张以方框表示的垂直堆叠视图层级图,每个方框标注视图名称。最上方的方框标注为 MessageRow,用一条直线连接到它正下方的 HStack 方框。HStack 通过直线连接到其下方的三个视图,分别标注为 ZStack、Spacer 和 Text。ZStack 下方用直线连接两个视图,分别标注为 Circle 和 Text。Circle 下方连接一个视图,标注为 fill。层级左侧的虚线箭头表示尺寸请求的流向下:从 MessageRow 开始,依次向下传到 HStack、ZStack 和 Circle。层级右侧的实线箭头表示返回的答复,从 Circle 开始,再向图片顶部方向依次传回 ZStack、HStack 和 MessageRow。

MessageRow 视图向其唯一的子视图 HStack 提议一个尺寸,即其自身的父视图向它提议的完整尺寸。栈按比例把这些空间分配给它的各个子视图,并在每个子视图之间使用系统默认的间距。这一过程会递归进行下去:

  • ZStack 向其子视图 Circle 和 Text 提议一个尺寸。
  • Circle 扩展到提议的尺寸,而 Text 只占用刚好够容纳其字符串的空间。
  • ZStack 返回其最大子视图的尺寸,在本例中就是 Circle。

当所有子视图都报告了自己的尺寸之后,父视图就会渲染它们。

限制视图尺寸

在上面的示例中,SwiftUI 内置视图以不同方式管理尺寸,其中包括这样几类视图:

  • 扩展以填满父视图提供的空间,例如 Color、LinearGradient 和 Circle。
  • 具有随内容变化的理想尺寸,例如 Text 和各种容器视图。
  • 具有永不变化的理想尺寸,例如 Toggle 或 DatePicker。

你可以通过添加 frame 修饰符把视图约束为固定尺寸。例如,使用 frame(width:height:alignment:) 修饰符把圆形的宽度限制为 40 点:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
struct MessageRow: View {
    let message: Message

    var body: some View {
        HStack {
            ZStack {
                Circle()
                    .fill(Color.yellow)
                Text(message.initials)
            }
            .frame(width: 40)

            Text(message.content)
        }
    }
}

当你添加 frame 修饰符时,SwiftUI 会包装受影响的视图,实际上就是向视图层级中加入了一个新视图。

一张以方框表示的视图层级图,方框内是视图名称。最上方的方框标注为 MessageRow,用一条线连接其正下方的 HStack。HStack 连接到其下方三个视图,方框分别标注为 frame、Spacer 和 Text。frame 下方连接一个视图:ZStack。ZStack 下方连接两个视图:标注为 Circle 的方框,以及标注为 Text 的方框。Circle 下方连接一个视图,标注为 fill。标注为 frame 的视图被高亮显示,表示与之前的视图层级示例相比它是新增的。层级左侧的虚线箭头表示尺寸请求的流向,从 HStack 开始,依次传到 frame、ZStack,然后传到 Circle。层级右侧的实线箭头表示返回的答复,从 Circle 开始,再依次传到 ZStack、frame 和 HStack。

当 SwiftUI 评估这个新层级时,frame 修饰符通过把你指定的参数值传递下去,固定了它所包装的 ZStack 的宽度。其余尺寸评估过程与之前相同,只是 Circle 现在扩展以填满一个更小的空间,该空间受 frame 的 40 点宽度约束。这使得 HStack 能把更多空间分给它的另一个子视图,也就是显示消息文本的那个视图。

一张图:一个黑色矩形作为视图的边框,其中左侧有一个受 40 点直径约束的小黄色圆形,圆形中央是字母“MR”。右侧有三行文本。文本和圆形垂直居中。

用对齐方式定位内容

如果你想让圆形的顶部与消息内容文本的顶部对齐,可以给 HStack 应用一种对齐方式来完善这个视图。要在栈中垂直定位内容,请把 alignment 参数指定为 top:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
struct MessageRow: View {
    let message: Message

    var body: some View {
        HStack(alignment: .top) {
            ZStack {
                Circle()
                    .fill(Color.yellow)
                Text(message.initials)
            }
            .frame(width: 40)

            Text(message.content)
        }
    }
}

应用对齐方式后,你会得到一个出乎意料的结果。这些视图的顶部看起来并没有对齐:

一张图:一个黑色矩形作为视图的边框,其中左侧有一个受 40 点直径约束的小黄色圆形,圆形中央是字母“MR”。右侧有三行文本。圆形垂直居中,而文本则对齐到视图顶部。

不过,如果你在 Xcode 中选中这个圆形,或者临时给圆形添加一个边框,就能看到这些视图的顶部实际上是对齐的。关于检查视图尺寸的更多信息,请参阅 检查视图布局。

一张图:一个黑色矩形作为视图的边框,其中左侧有一个受 40 点直径约束的小黄色圆形,圆形中央是字母“MR”。右侧有三行文本。圆形看起来垂直居中,而文本则对齐到视图顶部。另有一个蓝色方框显示包围圆形的视图边框,它占据其父视图的整个高度。

在上一节中,你应用的 frame 只有宽度约束。SwiftUI 画出的圆形受该宽度限制。但由于高度未指定,圆形的框架会另外扩展以填满可用的高度,即使这部分额外空间对渲染出的圆形没有任何可见影响。你可以通过添加显式的 height 参数来解决这个问题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
struct MessageRow: View {
    let message: Message

    var body: some View {
        HStack(alignment: .top) {
            ZStack {
                Circle()
                    .fill(Color.yellow)
                Text(message.initials)
            }
            .frame(width: 40, height: 40)

            Text(message.content)
        }
    }
}

现在 HStack 中的内容是顶部对齐的,不过如显示该行边界的边框所示,栈本身在 MessageRow 视图中是居中的。

一个黑色矩形显示视图的边框,其中左侧有一个受 40 点直径约束的小黄色圆形,圆形中央是字母“MR”。右侧有三行文本。圆形的顶部与文本行的顶部对齐。视图在圆形和文本的上下方显示出额外空间,但左右两侧没有。圆形和文本紧贴视图边框的左右边缘。

为视图添加内边距

为了避免视图外缘显得拥挤,可以添加内边距。这会在指定边缘引入固定的空间量,并相应地减少视图内容可用的空间。例如,你可以用 padding(::) 在 HStack 的水平边缘上添加额外空间:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
struct MessageRow: View {
    let message: Message

    var body: some View {
        HStack(alignment: .top) {
            ZStack {
                Circle()
                    .fill(Color.yellow)
                Text(message.initials)
            }
            .frame(width: 40, height: 40)

            Text(message.content)
        }
        .padding([.horizontal])
    }
}

padding 修饰符默认使用系统标准的间距,不过你也可以为 padding 修饰符选择其他值。 一个黑色矩形显示视图的边框,其中左侧有一个受 40 点直径约束的小黄色圆形,圆形中央是字母“MR”。右侧有三行文本。圆形的顶部与文本行的顶部对齐。视图在圆形和文本的上下方显示出额外空间,并在圆形和文本的左右两侧分别显示出内边距。