根据 SwiftUI 中的宽度计算每行的项目数

Calculate number of items per row based on their width in SwiftUI

这是我之前问题的扩展 ()

我需要实现一个布局,其中每行的项目数根据它们的组合宽度动态确定(基本上,将项目排成一行直到它们不再适合)。

有人告诉我,使用 GeometryReader 是用声明性语言做某事的一种 hacky 方式,这显然是正确的。

我也被引导到这个类似 CollectionView 的组件 https://github.com/Q-Mobile/QGrid 但解决方案是静态的,因为行数和每行的单元格数在呈现任何组件之前确定一次。

我不知道如何处理这个问题,所以任何建议对我来说都非常有价值!

❤️❤️❤️

这是一个使用 GeometryReader 的两步过程。

  1. 测量每个项目的宽度,共content(item)
  2. 使用尺寸将项目排列成行。

唯一的问题是它必须在每次重绘时重新计算或缓存先前测量的宽度,但这不一定是少数项目的问题。

我不会 post 此处的代码,因为它使用了 GeometryReader,这不是作者想要使用的东西。

TL;DR

GeometryReader 可能是 "hacky" 解决方案,但它是我们目前拥有的解决方案。 可以创建一个动态回流少量项目或延迟回流大量项目的解决方案。 My demo code 在这里会很笨拙,但听起来描述我的方法可能会有用。

利用现有资源

在幕后,SwiftUI 正在执行各种优化的约束求解以有效地布局您的视图。从理论上讲,像您描述的那样重排内容可能是解决约束的一部分;在今天的 SwiftUI 中,它不是。因此,执行您所描述的操作的唯一方法是以下的一些变体:

  1. 让 SwiftUI 根据我们的数据模型布置所有内容。
  2. 获取 SwiftUI 使用几何 reader 和 preferences/callbacks 决定的宽度。
  3. 使用这些宽度来解决我们的回流约束。
  4. 更新数据模型,这将触发第 1 步。

希望这个过程收敛到一个稳定的布局,而不是进入死循环。

我的成绩

在试用它之后,这就是我到目前为止得到的结果。您可以看到,当宽度更改时,少量项目(在我的示例中为 29 个)几乎立即回流。对于大量项目(在我的示例中为 262),会有明显的延迟。如果内容和视图宽度不变并且不需要经常更新,这应该不是什么大问题。时间几乎全部花在了步骤 1 上,所以在我们在 SwiftUI 中获得适当的回流支持之前,我怀疑这已经很好了。 (如果你想知道,一旦回流完成,垂直滚动视图就会以正常的响应速度滚动。)

我的攻略

本质上,我的数据模型从一个 [String] 数组开始,然后 t运行 将其形成一个 [[String]] 数组,其中每个内部数组对应一条水平适合的行我的看法。 (从技术上讲,它以 String 开始,在白色 space 上拆分以形成 [String],但从广义上讲,我有一个集合,我想拆分成多行。 ) 然后我可以使用 VStackHStackForEach.

进行布局

我的第一个方法是尝试读取我正在显示的实际视图的宽度。但是,我很快 运行 陷入无限递归或异常不稳定的振荡,因为它可能会截断文本视图(例如 [Four] [score] [and] [se...]),然后一旦回流改变,来回(或只是以截断状态结束。

所以我决定作弊。我在第二个看不见的水平滚动视图中布置了所有单词。这样,它们都可以根据需要占用尽可能多的 space,永远不会被截断,而且最重要的是,因为此布局仅依赖于 [String] 数组而不依赖于派生的 [[String]]数组,它永远不会进入递归循环。您可能认为将每个视图放置两次(一次用于测量宽度,一次用于显示)效率低下,但我发现它比尝试从显示的视图测量宽度快几十倍,并且 100% 地产生正确的结果时间。

+---------- FIRST TRY - CYCLIC ----------+  +-------- SECOND TRY - ACYCLIC --------+
|                                        |  |                                      |
|    +--------+ [String] +----------+    |  |   +-------+ [String] +--------+      |
|    |                              |    |  |   |                           |      |
|    | +--------------------------+ |    |  |   v                           v      |
|    | |                          | |    |  | Hidden +-->  Widths  +--> [[String]] |
|    v v                          + v    |  | layout                        |      |
|  Display +-->  Widths  +--> [[String]] |  |                               v      |
|  layout                                |  |                            Display   |
|                                        |  |                            layout    |
+----------------------------------------+  +--------------------------------------+

为了读取和保存宽度,我采用了 GeometryReader/PreferenceKey 方法 detailed on swiftui-lab.com。宽度保存在视图模型中,并在隐藏滚动视图中视图的数量或大小发生变化时更新。这样的更改(或更改视图的宽度)然后根据模型中保存的宽度将 [String] 数组回流到 [[String]]

总结

现在,这在运输应用程序中是否有用将取决于您要回流的项目数量,以及它们在布局后是静态的还是经常更改。但我发现这是一个有趣的消遣!