SwiftUI的内置frame 修改器既可用于为给定的视图分配一个静态的宽度或高度,也可用于应用 "类似约束 "的界限,在该界限内,视图可根据其内容和周围环境而增长或缩小。
在最基本的层面上,这就是frame 修改器的两种常见使用方式:
// A view that displays a 30x30 fixed-sized icon using an SFSymbol:
struct Icon: View {
var name: String
var tintColor: Color = .blue
var body: some View {
Image(systemName: name)
.foregroundColor(tintColor)
.frame(width: 30, height: 30)
}
}
// A view that displays a decorative image that's resized according
// to its aspect ratio, with a maximum width of 200 points:
struct DecorativeImage: View {
var name: String
var body: some View {
Image(name)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxWidth: 200)
}
}
虽然上述两种使用frame 修改器的方式都非常有用,但有时我们在决定视图的大小时可能不想指定任何类型的固定指标。值得庆幸的是,还有一种方法可以让一个给定的视图无限地扩展,这在很多不同的情况下都会很方便。
例如,假设我们正在开发一个视图,使用SwiftUI的LazyVGrid ,以两列网格的形式显示一个类别数组:
struct CategoryGrid: View {
var categories: [Category]
var body: some View {
LazyVGrid(columns: columns) {
ForEach(categories) { category in
Text(category.name)
.padding()
.background(category.color)
.foregroundColor(.white)
.cornerRadius(10)
}
}
.padding()
}
private var columns: [GridItem] {
let item = GridItem(.flexible(minimum: 50, maximum: .infinity))
return [item, item]
}
}
正如你通过使用上面的PREVIEW 按钮所看到的,我们的网格目前看起来并不是那么好,因为每个单元格最终都会根据它所呈现的文本而有不同的尺寸。
这最初可能看起来有点奇怪--鉴于我们指定我们的两列都应该是灵活的,最大宽度是无限的--但由于Text 视图不会拉伸自己以适应其容器,每个视图的背景色最终都会占据文本本身的大小。
值得庆幸的是,这是另一个问题,使用frame 修改器结合CGFloat.infinity 常数可以轻松解决,我们在上面创建代表我们列的GridItem 值时也使用了这个常数。通过在渲染背景之前插入这样一个修改器,我们现在可以使我们的网格看起来更漂亮。
struct CategoryGrid: View {
var categories: [Category]
var body: some View {
LazyVGrid(columns: columns) {
ForEach(categories) { category in
Text(category.name)
.padding()
.frame(maxWidth: .infinity)
.background(category.color)
.foregroundColor(.white)
.cornerRadius(10)
}
}
.padding()
}
...
}
再一次,你可以使用PREVIEW 按钮来看看上面的代码样本在渲染时的样子。
因此,应用带有无限最大宽度或最大高度的frame 修改器可以是一种很好的方式来告诉一个给定的SwiftUI视图拉伸自己以填补水平或垂直轴上的所有可用空间。
让我们看看另一个例子,我们已经建立了一个InfoView ,可以用来显示一段信息文本和一个标题。我们的意图是让这些信息视图始终显示在屏幕的整个宽度上(减去一些填充),在纵向运行的iPhone上,它们很可能会这样,但在横向(或在iPad上),可能没有足够的文本来覆盖屏幕的整个宽度:
struct InfoView: View {
var title: String
var text: String
var body: some View {
HStack(alignment: .top, spacing: 15) {
Image(systemName: "info.circle")
VStack(alignment: .leading, spacing: 10) {
Text(title).font(.headline)
Text(text)
}
}
.padding()
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(20)
}
}
解决这个问题的一个方法是使用Spacer 来填补剩余的水平空间--这将有效地使我们的InfoView 始终在其容器的整个宽度上呈现。然而,虽然间隔器确实是SwiftUI布局系统的一个重要组成部分,但在这种情况下,向我们的视图添加一个Spacer ,实际上最终会破坏其纵向布局--因为间隔器在默认情况下总是占据最小的空间,而且我们的HStack 在其每个元素之间应用了15点的间隔:
struct InfoView: View {
...
var body: some View {
HStack(alignment: .top, spacing: 15) {
Image(systemName: "info.circle")
VStack(alignment: .leading, spacing: 10) {
Text(title).font(.headline)
Text(text)
}
Spacer()
}
...
}
}
就像上面的PREVIEW 所示,由于我们添加了Spacer ,我们的文本不能再在视图的所有可用宽度上呈现。
这又是frame 修改器的另一个伟大的使用案例,在这种情况下,如果我们不仅指定infinity 作为其maxWidth ,而且还告诉它根据前缘对齐其内容--像这样,它就可以充当一个间隔:
struct InfoView: View {
...
var body: some View {
HStack(alignment: .top, spacing: 15) {
Image(systemName: "info.circle")
VStack(alignment: .leading, spacing: 10) {
Text(title).font(.headline)
Text(text)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
...
}
}
请注意,我们需要指定.leading 作为我们的VStack 和它的frame 修改器的对齐方式,因为前者是对齐堆栈的内容(或两个文本),而后者是对齐堆栈本身的周围框架。
当然,与其使用.infinity 作为我们的maxWidth ,我们还可以选择指定一个固定的值,以防止我们的视图在较大的设备(如iPad)上伸展得太厉害。但这是一个决定,最终将归结于我们在实现每个视图时要做什么样的设计。