在Swift中高效滚动UIStackView

如何构建滚动堆栈容器并保持内存的正常使用。

如今,移动用户界面已成为一项复杂的工作。 列表(表,或更常见的是集合)可能包含不同种类的项目,从而在单个滚动交互中显示了大量数据。

以IMDB应用程序为例; 主页包含:

  • 带有突出显示的电影的水平列表
  • 重点新闻
  • 带有照片库的水平列表
  • 与附近电影的水平列表
  • 即将推出的电影的另一个水平列表
  • 新闻垂直列表
  • …是的,还有更多东西

单个垂直滚动视图中的所有内容!

通常,如果您在编写此类代码时仍然缺乏注意力,则视图控制器可能会成为大量的意大利面条式代码,将多个职责组合在一起,并使您的应用程序更脆弱且更不可测试。
那就是Massive View Controllers的世界,以及诸如Viper,MMVM和其他几种替代体系结构背后的主要原因。

关注点分离(以及“单一责任原则”)是一种将计算机程序分为不同部分的设计原理,这样每个部分都可以解决一个单独的关注点。

天真的方法

将这类布局带回家的最简单方法是使用表或集合,然后将对象放入内部(即,内部布局复杂的富单元格); 尽管它实际上只适用于很少的对象,但具有相当数量的异构对象,但最终的体系结构却是您可以避免的疯狂庇护。

JustEat UK的ScrollingStackViewController类使用UIScrollView内的UIStackView的方法来模拟Android的垂直布局。
正如自述文件所述,它是在构建数量有限或动态且内容丰富的滚动控制器的情况下的一种更合适的方法。

但是,当您的布局变得复杂时,可能导致相关的内存占用或不可持续。
在一个或多个子视图控制器包含垂直表或集合的情况下。
实际上,为了使它们正常工作,您需要扩展这些视图以显示整个内部表/集合的内容,并以内部单元格的巨大分配作为结尾(即使当前不可见):您到表的缓存。

请参阅以下示例:

高效方法

以下工作的灵感来自Ole Begamnn的一篇旧文章“ Scroll Views Inside Scroll Views”,我几年前在CreoLabs上为CreoKIT制作UITableView克隆时曾大量使用它。
主要区别在于它可以与UIViewControllers并可以正确管理,然后这些容器是UIScrollView子类的容器(如UITableViewUICollectionView ); 是的,它适用于Swift 3+。

基本上,这是一个UIViewController ,它期望在其中包含UIScrollView (您可以通过情节提要进行设计并链接插座,因此可以自由保留自己的自定义布局)。
它允许实现以下目的:

  • 将多个UIViewControllers (视图)彼此放在下面,以使它们的滚动行为仍然感觉完全正常。 如果视图是表集合或垂直集合,则内置单元重用功能不会受到影响,并且可以按您期望的方式直接使用。
  • 将一个复杂的UITableViewDataSource/UICollectionViewDataSource转换为几个不同的UIViewControllers ,这些UIViewControllers独立管理其同类(?)数据。

这是第一个版本; 我的主要目标是拥有一个高效的堆栈容器; 没有小饰品或特殊功能(但是,即使有您的帮助,迭代后它也会变得更好)。
例如,它目前仅支持垂直堆叠。

UITableViewUICollectionView都是UIScrollView子类; 它们的行为基本上类似于标准滚动视图,但主要区别在于内部单元进行的回收操作。
滚动视图时,屏幕滚动单元会从视图层次结构中删除,并添加到内部池中。 每当将新的单元格滚动到视图中时,表视图都会使该单元格从池中出队,并将其重新添加到视图层次结构中(这是您可以覆盖prepareForReuse()函数背后的原因)。

高效的设计:

  • 仅呈现可见的单元格(您的表可能包含成千上万个单元格,并且不影响应用程序的性能)
  • 避免不必要的单元分配

怎么运行的

当您在父滚动视图中堆叠表或集合时,为了保持标准行为,您需要扩展内容(您的表高度与它提供的内容相同) ,这意味着您已经放弃了回收器算法。

我在ScrollingStackContainer使用的技巧使我能够保持标准的单元回收器算法不变,同时我可以在父UIScrollView内将多个滚动视图一个层叠在另一个下面,从而产生一次连续滚动的错觉。

事实上:

  • 简单的UIView被完全分配和堆叠( frame UIView
  • 对包含UIScrollView UIViewController进行了特殊考虑。

在最后一种情况下,当父级UIScrollView (最外面的 )滚动时,该算法将遍历每个堆叠的UIViewControllerview ; 如果看不见,则将内部 UIScrollView的高度设置为零(这意味着:“您无需在滚动内分配任何内容!”)。

当视图变为部分可见时, 内部 UIScrollView的框架将设置为仅覆盖父滚动视图内的可见区域(还自动调整偏移量以模拟连续滚动,就好像它是单个表格一样)。

内部UIScrollView可以最大程度地覆盖整个父级。 同样在这种情况下,仅分配内部滚动的可见区域。
口头禅是:只分配可见的单元格; 您看不到的所有内容(可以滚动)都不占用设备的内存。

借助这种精确的布局管理,您可以自由创建复杂的布局,而不必担心不必要的内存使用。

如何使用滚动堆栈容器

ScrollingStackContainer非常易于使用。
对于固定高度视图,应设置有效高度(小于零)。 可以通过以下方式完成此操作:

  • UIViewController.view上设置高度限制
  • …或返回一个值到preferredContentSize函数中
  • …或通过self.frame设置高度
  • …或通过实现StackContainable协议并返回带有有效值的.view(height: )

对于包含内部滚动视图(表或集合)的UIViewController,您必须通过返回.scroll(, ).来强制实现StackContainble协议.scroll(, ).

就这样! 所有的业务逻辑都由班级管理,您可以尽情享受!

像往常一样,我的GitHub帐户上提供了ScrollingStackContainer以及一个小型示例项目。 随时发布您的PR和问题,我很乐意与您讨论课程的进一步发展。