根据SwiftUI中每行的宽度计算项目数 [英] Calculate number of items per row based on their width in SwiftUI

查看:230
本文介绍了根据SwiftUI中每行的宽度计算项目数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我先前问题的扩展(在SwiftUI中获取视图的宽度)

This is an extension of my previous question (Get width of a view using in SwiftUI)

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

I need to implement a layout where number of items per row is determined dynamically, based on their combined width (basically, place items in a row until they no longer fit).

有人告诉我,使用GeometryReader是一种使用声明性语言进行操作的不可靠方式,这显然是正确的.

I've been told that using the GeometryReader is a hacky way to do something in a declarative language, which is obviously true.

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

I have also been directed to this CollectionView-like component https://github.com/Q-Mobile/QGrid but the solution is static as the number of rows and cells per row is determined once, before any components are rendered.

我不知道该如何处理,因此任何建议对我来说都是非常有价值的!

I have no idea how to approach this, so any advice is very valuable for me!

❤️❤️❤️

推荐答案

TL; DR

GeometryReader可能是一个棘手的"解决方案,但这是我们目前拥有的解决方案. 可以创建一种解决方案,以动态地对少量项目进行动态重排,或者对大量项目进行延迟重排. 我的演示代码在这里很笨拙,但这听起来像描述我的方法可能有用.

TL;DR

GeometryReader may be a "hacky" solution, but it is the solution we have at the moment. It is possible to create a solution that reflows a small number of items dynamically, or a large number of items with a delay. My demo code would be unwieldy here, but it sounds like describing my approach may be useful.

在后台,SwiftUI正在执行各种优化的约束解决方案,以有效地布局视图.从理论上讲,像您描述的那样重排内容可能是该约束解决方案的一部分.在今天的SwiftUI中,事实并非如此.因此,执行所描述内容的唯一方法是以下方法的某种变体:

Behind the scenes, SwiftUI is doing all kinds of optimized constraint solving to layout your views efficiently. In theory, reflowing content like you describe could be part of that constraint solving; in today's SwiftUI, it is not. Therefore, the only way to do what you are describing is some variant of the following:

  1. 让SwiftUI根据我们的数据模型布置一切.
  2. 获取SwiftUI使用Geometry阅读器和首选项/回调决定的宽度.
  3. 使用这些宽度来解决我们的回流约束.
  4. 更新数据模型,这将触发步骤1.

希望此过程收敛到稳定的布局,而不是进入无休止的循环.

Hopefully, this process converges to a stable layout, rather than entering an endless loop.

玩了之后,这就是我到目前为止所获得的.您会看到,随着宽度的改变,少量物品(在我的示例中为29)几乎立即回流.对于大量项目(在我的示例中为262),存在明显的延迟.如果内容和视图宽度没有变化并且不需要经常更新,那么这不是什么大问题.时间几乎全部花在了步骤1中,因此,直到我们在SwiftUI中获得适当的回流支持之前,我怀疑这是一样的好. (以防万一,在重排完成后,垂直滚动视图将以正常的响应速度滚动.)

After playing around with it, here's what I've gotten so far. You can see that a small number of items (29 in my example) reflow almost instantaneously as the width is changed. With a large number of items (262 in my example), there is a noticable delay. This shouldn't be much of an issue if the content and view width don't change and won't need to be updated frequently. The time is spent almost entirely in step 1, so until we get proper reflow support in SwiftUI, I suspect this is as good as it gets. (In case you're wondering, the vertical scrollview scrolls with normal responsiveness once the reflow is finished.)

从本质上讲,我的数据模型以[String]数组开头并将其转换为[[String]]数组,其中每个内部数组都对应于一条水平放置在我的视图中的线. (从技术上讲,它以String开头,在空白处拆分以形成[String],但从广义上讲,我有一个要拆分为多行的集合.)然后,可以使用VStackHStackForEach.

Essentially, my data model starts with a [String] array and transforms it to a [[String]] array, where each internal array corresponds to one line that will fit horizontally in my view. (Technically it starts with a String that is split on whitespace to form the [String], but in a generalized sense, I've got a collection I want to split into multiple lines.) Then I can lay it out using VStack, HStack, and ForEach.

我的第一种方法是尝试读取显示的实际视图的宽度.但是,我很快遇到了无限递归或奇怪的不稳定振荡​​,因为它可能会截断Text视图(例如[四] [分数] [和] [se ...]),然后在重排更改后取消截断一次,返回来回移动(或以截断状态结束.

My first approach was to try to read the widths off the actual views I'm displaying. However, I quickly ran into infinite recursions or weirdly unstable oscillations because it might truncate a Text view (e.g. [Four] [score] [and] [se...]), and then un-truncate once once the reflow changed, back and forth (or just end in a truncated state.

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

So I decided to cheat. I lay out all the words in a second, invisible horizontal scrollview. This way, they all get to take up as much space as they want, never get truncated, and most importantly, because this layout only depends on the [String] array and not the derived [[String]] array, it can never enter a recursive loop. You may think that laying each view twice (once for measuring width and once for displaying) is inefficient, but I found it to be dozens of times faster than trying to measure the widths from the displayed views, and to produce proper results 100% of the time.

+---------- 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方法

To read and save the widths, I adapted the GeometryReader/PreferenceKey approach detailed on swiftui-lab.com. The widths are saved in the view model, and updated whenever the number or size of views in the hidden scrollview change. Such a change (or changing the width of the view) then reflows the [String] array to [[String]] based on the widths saved in the model.

现在,这一切在运输应用程序中是否有用将取决于您要重排的物料的数量,以及一旦布置或经常更改它们将是静态的.但是我发现这是一个有趣的转移!

Now, whether any of this is useful in a shipping application will depend on how many items you want to reflow, and whether they will be static once laid out or changing often. But I found it to be a fascinating diversion!

这篇关于根据SwiftUI中每行的宽度计算项目数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆