0Pricing
Swift Academy · 课时

构建流式布局

动态地将项目换到多行显示。

构建流式布局 是 CoddyKit 上的免费 Swift Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Swift Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Swift Academy 课程共包含 4 节课。

什么是流式布局

流式布局从左到右放置项目;当下一个项目会超出可用宽度时,就换到新的一行,类似标签小块或文字换行。

计划

我们将在共享辅助方法中计算换行位置,从 sizeThatFits 返回总高度,并在 placeSubviews 中重新使用相同的计算。共享逻辑可以让这两个方法保持一致。

建模放置信息

一个小型结构体记录每个子视图的放置位置。预先计算所有放置信息,可以让两个协议方法都变得简单。

struct Placement {
    let index: Int
    let point: CGPoint
    let size: CGSize
}

换行算法

遍历子视图并跟踪当前的 x。如果某个子视图会超出最大宽度,就重置 x,并按照行高加间距的距离向下移动。

func layout(_ subviews: Subviews, maxWidth: CGFloat) -> (placements: [Placement], height: CGFloat) {
    var placements: [Placement] = []
    var x: CGFloat = 0, y: CGFloat = 0, rowHeight: CGFloat = 0
    let spacing: CGFloat = 8
    for (i, s) in subviews.enumerated() {
        let size = s.sizeThatFits(.unspecified)
        if x + size.width > maxWidth, x > 0 {
            x = 0; y += rowHeight + spacing; rowHeight = 0
        }
        placements.append(Placement(index: i, point: CGPoint(x: x, y: y), size: size))
        x += size.width + spacing
        rowHeight = max(rowHeight, size.height)
    }
    return (placements, y + rowHeight)
}

实现 sizeThatFits

sizeThatFits 会针对提议的宽度运行该算法,并返回提议的宽度和计算出的总高度。

func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
    let maxWidth = proposal.width ?? .infinity
    let result = layout(subviews, maxWidth: maxWidth)
    return CGSize(width: maxWidth == .infinity ? result.placements.map { $0.point.x + $0.size.width }.max() ?? 0 : maxWidth,
                  height: result.height)
}

实现 placeSubviews

placeSubviews 会运行相同的算法,然后将每个放置位置偏移 bounds.origin,并调用 place。

func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
    let result = layout(subviews, maxWidth: bounds.width)
    for p in result.placements {
        let pt = CGPoint(x: bounds.minX + p.point.x, y: bounds.minY + p.point.y)
        subviews[p.index].place(at: pt, anchor: .topLeading,
                                proposal: ProposedViewSize(p.size))
    }
}

完整的 FlowLayout 类型

将这些部分组合起来,就得到了一个完整且可重复使用的流式布局。共享的 layout 辅助方法可以确保尺寸和放置结果一致。

struct FlowLayout: Layout {
    var spacing: CGFloat = 8
    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
        let r = layout(subviews, maxWidth: proposal.width ?? .infinity)
        return CGSize(width: proposal.width ?? 0, height: r.height)
    }
    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
        let r = layout(subviews, maxWidth: bounds.width)
        for p in r.placements {
            subviews[p.index].place(at: CGPoint(x: bounds.minX + p.point.x, y: bounds.minY + p.point.y),
                                    anchor: .topLeading, proposal: ProposedViewSize(p.size))
        }
    }
}

使用流式布局

像使用任何容器一样将它放入即可。随着宽度缩小,标签会自动换行。

var body: some View {
    FlowLayout {
        ForEach(tags, id: \.self) { tag in
            Text(tag)
                .padding(8)
                .background(.blue.opacity(0.2))
                .clipShape(Capsule())
        }
    }
}

处理每行的第一个项目

条件 x > 0 可以防止在一行的第一个项目之前换行。没有这个条件时,宽于容器的项目可能会导致无限循环,或者无谓地移到空行中。

if x + size.width > maxWidth, x > 0 {
    // wrap only if the row already has content
}

间距选择

这里的间距是一个带有默认值的存储属性。在实际应用中,请读取每个子视图的 spacing 偏好,以遵循系统度量值,而不是使用硬编码的常量。

var spacing: CGFloat = 8 // configurable per instance

测试边界情况

请使用零个项目、一个超大的项目以及许多很小的项目进行测试。正确的流式布局会为没有内容的情况报告零高度,并且绝不会裁剪单个超大子视图。

快速检查:流式布局

请测试您对换行算法的理解。

回顾:构建流式布局

流式布局通过跟踪不断变化的 x,并在下一个子视图会溢出时重置到新行,从而实现项目换行。共享辅助方法会计算所有放置位置和总高度,让 sizeThatFits 与 placeSubviews 保持一致。

请使用 bounds.origin 偏移放置位置,为每行的第一个项目添加保护条件,并测试空内容、超大项目和大量项目的情况。最终得到的是一个可重复使用的换行容器。

常见问题解答

「构建流式布局」课时是免费的吗?

是的 — 「构建流式布局」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Swift Academy 课程的其余内容,请升级到 CoddyKit PRO。 Swift Academy 课程共包含 4 节课。

「构建流式布局」这节课中我会学到什么?

动态地将项目换到多行显示。 你通过在浏览器中直接运行的动手代码来练习 Swift Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Swift Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Swift Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「构建流式布局」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Swift Academy 课中编写并运行代码吗?

能。每节 Swift Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. Layout 协议基础
  2. 测量子视图
  3. 构建流式布局
  4. 布局缓存与性能
← 返回 Swift Academy