TL;DR
GeometryReader 可能是一个“hacky”解决方案,但它是我们目前拥有的解决方案。 可以创建一个动态重排少量项目或延迟重排大量项目的解决方案。 My demo code 在这里会很笨拙,但听起来描述我的方法可能很有用。
利用现有资源工作
在幕后,SwiftUI 正在执行各种优化的约束求解,以有效地布局您的视图。从理论上讲,像您描述的那样重排内容可能是该约束解决的一部分;在今天的 SwiftUI 中,它不是。因此,执行您所描述的操作的唯一方法是以下一些变体:
- 让 SwiftUI 根据我们的数据模型布置一切。
- 获取 SwiftUI 决定使用几何阅读器和首选项/回调的宽度。
- 使用这些宽度来解决我们的回流限制。
- 更新数据模型,这将触发步骤 1。
希望这个过程收敛到一个稳定的布局,而不是进入一个无限循环。
我的结果
在玩弄它之后,这就是我到目前为止所得到的。您可以看到,随着宽度的更改,少量项目(在我的示例中为 29 个)几乎瞬间回流。对于大量项目(在我的示例中为 262 个),存在明显的延迟。如果内容和视图宽度没有改变并且不需要经常更新,这应该不是什么大问题。时间几乎都花在了第 1 步中,所以在我们在 SwiftUI 中获得适当的回流支持之前,我怀疑这已经是最好的了。 (如果您想知道,一旦回流完成,垂直滚动视图会以正常的响应速度滚动。)
我的策略
基本上,我的数据模型从[String] 数组开始,然后将其转换为[[String]] 数组,其中每个内部数组对应于一条水平线,可以在我的视图中水平放置。 (从技术上讲,它以String 开头,在空格上拆分形成[String],但从广义上讲,我有一个要拆分为多行的集合。)然后我可以使用@ 对其进行布局987654329@、HStack 和 ForEach。
我的第一种方法是尝试从我正在显示的实际视图中读取宽度。但是,我很快遇到了无限递归或奇怪的不稳定振荡,因为它可能会截断文本视图(例如 [Four] [score] [and] [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 方法detailed on swiftui-lab.com。宽度保存在视图模型中,并在隐藏的滚动视图中的视图数量或大小发生变化时更新。此类更改(或更改视图的宽度)然后根据模型中保存的宽度将 [String] 数组重排为 [[String]]。
总结
现在,这在运输应用程序中是否有用将取决于您要重排的项目数量,以及它们在布局后是静态的还是经常更改。但我发现这是一种迷人的消遣!