使用SwiftUI的框架修改器来调整大小和对齐视图的教程

425 阅读4分钟

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)上伸展得太厉害。但这是一个决定,最终将归结于我们在实现每个视图时要做什么样的设计。