SwiftUI 5.0 中宝藏视图修改器 containerRelativeFrame 趣谈(上)

29 阅读4分钟

在这里插入图片描述

概览

小伙伴们都知道,为了将 SwiftUI 中多如牛毛的视图井然有序、有条不紊的组织起来,我们必须借助容器(Container)伏虎降龙般地威力。而如何最大限度的让容器中的子视图能根据容器尺寸安排自己的空间,则需要一些技术手段来洞幽察微。

在这里插入图片描述

在过去,我们往往使用 GeometryReader 来干这种“力气活”,不过自从有了 containerRelativeFrame 修改器,GeometryReader 从此就可以退居二线了。

在本篇博文中,您将学到如下内容:

  1. 绝对被“秃头们”忽略的“异宝奇珍”
  2. 纵横交错:子视图对齐
  3. 分而治之:子视图分割

相信学完本课后,小伙伴们一定会对宝藏修改器 containerRelativeFrame 的使用豁然开朗、恍然大悟。那还等什么呢?让我们马上开始容器布局大冒险吧!

Let‘s go!!!;)


1. 绝对被“秃头们”忽略的“异宝奇珍”

在很久很久以前,我们想按照自己的意图来驯服 SwiftUI 容器中子视图们的布局绝对不是一件容易的事儿。虽说 GeometryReader 勉强可以达到要求,但它的缺陷也很明显:

  • GeometryReader 使用不那么自然和直观;
  • GeometryReader 会导致原本布局发生改变;
  • GeometryReader 会参与 SwiftUI 渲染树,这意味着依靠它所做的修改会“反噬”整体布局,甚至可能造成渲染死循环;

从 SwiftUI 4.0 开始,苹果推出了容器自定义布局(Custom Layout)专注于解决子视图排兵布阵的问题:

在这里插入图片描述

不过,使用自定义布局这一“重量级”武器对于某些简单的子视图布局调整来说,可能有些大炮打蚊子的感觉。

于是乎,从 SwiftUI 5.0 开始,苹果瞒着众多秃头码农们“偷偷”加入了全新的 containerRelativeFrame 视图修改器。

在这里插入图片描述

containerRelativeFrame 修改器被特别用来处理子视图相对于其最近容器(Nearest Container)布局的“调兵遣将”,它有多个重载形式可以确保满足不同场景下的使用,我们会在后面为大家娓娓道来。

2. 纵横交错:子视图对齐

containerRelativeFrame 修改器最简单的用法就是设置单个子视图在水平或垂直轴的对齐方式。

struct ContentView: View {
    var body: some View {
        Text("Hello, Hopy!")
            .containerRelativeFrame([.horizontal], alignment: .leading)
            .background(Color.pink)
    }
}

上面代码很简单,我们利用 containerRelativeFrame 方法让文本沿水平轴的前部(leading)对齐。

在这里插入图片描述

同样的,我们还可以让文本在父容器里(此处就是屏幕)居中对齐。这里还可以看到,利用 containerRelativeFrame 修改器我们能让子视图非常容易的铺满整个父容器:

Text("Hello, Hopy!")
    .font(.largeTitle.bold())
    .foregroundStyle(.white)
    .containerRelativeFrame([.horizontal, .vertical], alignment: .center)
    .background(Color.pink.gradient)

显示效果如下图所示:

在这里插入图片描述

3. 分而治之:子视图分割

除了单个子视图在容器中的布局以外,更多情况下我们会面临多个子视图“摩肩接踵”同时挤在容器里的情况,这时如何处理好它们与容器的相对尺寸就尤为重要了。

无独有偶,containerRelativeFrame 正是这方面的行家里手。

我们可以利用 containerRelativeFrame 方法的另一种重载,在可视范围内按照 count、span 以及 spacing 的值分配子视图尺寸:

extension Color {
    static var rand: Color {
        let uiColor = UIColor(red: .random(in: 0.0...1.0), green: .random(in: 0.0...1.0), blue: .random(in: 0.0...1.0), alpha: .random(in: 0.0...1.0))
        
        return Color(uiColor: uiColor)
    }
}

ScrollView(.horizontal) {
    LazyHStack(spacing: 0) {
        ForEach(0..<10) { _ in
            Color.rand
                .containerRelativeFrame(
                    .horizontal,
                    count: 3,
                    span: 1,
                    spacing: 0
                )
                .frame(height: 300)
        }
    }
}

在上面的代码中,我们就将 LazyHStack 容器的子视图在可视范围内横向“分割”成了 3 等份,每个子视占据其中 1/3 的宽度:

在这里插入图片描述

如果需要,我们还可以利用 span 实参让布局更加灵活,比如让每个子视图占据容器可见宽度的 2/3 :

Color.rand
    .containerRelativeFrame(
        .horizontal,
        count: 3,
        span: 2,
        spacing: 0
    )
    .frame(height: 300)

在这里插入图片描述

更具体的来说, containerRelativeFrame 中 count、span 和 spacing 3 个形参的关系为:

let availableWidth = (containerWidth - (spacing * (count - 1))) let columnWidth = (availableWidth / count) let itemWidth = (columnWidth * span) + ((span - 1) * spacing)

在下篇博文中,我们将继续讨论如何实现子视图滚动对齐以及更加自由的设置子视图相对父容器视口的尺寸,不见不散!

总结

在本篇博文中,我们初步介绍了 SwiftUI 5.0 中的宝藏视图修改器 containerRelativeFrame,并讨论了如何用它来进行容器内子视图的对齐与分割。

感谢观赏,我们下篇再见!8-)