TL;DR
GeometryReader 可能是一个“hacky”解决方案,但它是我们目前拥有的解决方案。它is可以创建一个动态回流少量项目或延迟回流大量项目的解决方案。我的演示代码 https://github.com/john-mueller/SwiftUI-Examples/tree/master/SwiftUI-Examples/FlowLayout在这里会很笨拙,但听起来描述我的方法可能会有用。
运用我们现有的资源
在幕后,SwiftUI 正在执行各种优化的约束求解,以有效地布局视图。理论上,像您所描述的那样重排内容可能是约束解决的一部分;在今天的 SwiftUI 中,情况并非如此。因此,执行您所描述的操作的唯一方法是以下某些变体:
- 让 SwiftUI 根据我们的数据模型来布置一切。
- 获取 SwiftUI 使用几何读取器和首选项/回调决定的宽度。
- 使用这些宽度来解决我们的回流约束。
- 更新数据模型,这将触发步骤1。
希望这个过程能够收敛到稳定的布局,而不是进入无限循环。
我的结果
玩过之后,这就是我到目前为止所得到的。您可以看到,当宽度发生变化时,少量项目(在我的示例中为 29 个)几乎立即回流。对于大量项目(在我的示例中为 262 个),会出现明显的延迟。如果内容和视图宽度不发生变化并且不需要经常更新,这应该不是什么大问题。时间几乎全部花在步骤 1 上,因此,在我们在 SwiftUI 中获得适当的回流支持之前,我怀疑这已经是最好的了。 (如果您想知道,一旦回流完成,垂直滚动视图就会以正常的响应速度滚动。)
我的策略
本质上,我的数据模型始于[String]
数组并将其转换为[[String]]
数组,其中每个内部数组对应于在我的视图中水平放置的一行。 (从技术上讲,它始于String
在空白处分割以形成[String]
,但从广义上讲,我有一个想要分成多行的集合。)然后我可以使用它来布局它VStack
, HStack
, and ForEach
.
我的第一个方法是尝试读取我正在显示的实际视图的宽度。然而,我很快遇到了无限递归或奇怪的不稳定振荡,因为它可能会截断文本视图(例如[四] [分数] [和] [se...]),然后一旦回流改变,就取消截断一次,返回来回(或者只是以截断状态结束。
所以我决定作弊。我将所有单词放在第二个不可见的水平滚动视图中。这样,它们就可以占用尽可能多的空间,并且永远不会被截断,最重要的是,因为这种布局仅取决于[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 方法详细信息请参见 swiftui-lab.com https://swiftui-lab.com/communicating-with-the-view-tree-part-1/。宽度保存在视图模型中,并在隐藏滚动视图中的视图数量或大小发生变化时更新。这样的更改(或更改视图的宽度)然后会回流[String]
数组到[[String]]
基于模型中保存的宽度。
Summary
现在,这些在运输应用程序中是否有用将取决于您想要回流的项目数量,以及它们在布局后是否是静态的或经常更改。但我发现这是一个令人着迷的消遣!