使用UIScrollView进行分页的无限滚动

看似简单的技术,它得益于良好的心理模型来解开纠结

《无限滚动》,乔什和伊丽莎,WWDC 2011

iOS的无限滚动技术已众所周知多年,尽管有时甚至在今天也被认为很困难。 我第一次听说这件事是在2011年Josh&Eliza的WWDC演讲中。

通常,当您在滚动视图上向左滑动时,您会得到屏幕向左流动的印象。 但是,iOS只是通过将视口移到相反的方向来给您那种印象。 在下一个示例中,在向左滑动显示黄色部分之前,仅滚动视图的柠檬绿色部分可见。 在此示例中,整个滚动视图没有更改,但是只有视图端口向右移动以显示黄色部分。

通过这种正常的滚动行为,您将陷于Content View的边缘。

Josh&Eliza引入的技术的关键思想是在UIScrollViewDelegate.scrollViewDidScroll(...)执行以下操作

  • 将内容视图移向滑动方向,并使视口始终位于内容视图的中心
  • 在内容视图的新开放空间中添加新内容

使用这种技术,我们永远不会卡在Content View的边缘,因为View端口始终位于中心。 只要我们的程序将新内容正确地输入到开放空间中,我们就可以永远滚动。

UIScrollViewDelegate.scrollViewDidScroll(...)是描述滚动调整动作的最佳位置之一,因为它在每次查看端口移动时以及在实际布局/渲染发生之前都会被调用。

翻页滚动

问题

上面的技术很容易实现,非常有效。 除了需要分页时。

UIScrollView有一个名为isPagingEnabled的标志。 通过仅将此标志设置为true ,滚动可以在每一页上完美地捕捉。 请查看以下“动物偷看”示例应用程序示例。 您希望每个滚动都像在gif中看到的那样捕捉每个动物。 否则,它不是peek-a-boo🙂

我敢打赌,如果滚动仅停在某一点,孩子们将不会高兴,因此这是将其作为无限滚动视图的动机。

如果我们在此应用程序中天真地实现Josh&Eliza的技术, 它将破坏 UIScrollView 的分页功能 。 请记住,Josh&Eliza的主要想法是将视口保持在Content View的中心。 这使iOS认为滚动视图根本不在页面上移动。 结果,页面捕捉将永远不会正确发生。 所以……休斯顿,我们有一个问题。

与这个世界上的一切一样,有一个解决方案。 这是一个相当容易的过程。 解决方案的关键思想是

我们不需要经常调整视口。 Josh&Eliza为简化代码做了太多事情。

每次 调用 scrollViewDidScroll(...) 时, Josh&Eliza 都会进行视口调整。 但是实际上,当您考虑它时,我们真正需要它只有两个时间

  • 当视口移至右侧时, 下一页内容将占据视口的大部分。 我们需要将视口移回左侧,以使视口在不久的将来不会卡在右侧。
  • 当视口移到左侧时, 上一页内容占据了视口的大部分。 我们需要将视口向前移至右侧,以使视口在不久的将来不会卡在左侧。

您只需要针对页面大小调整视口,然后填写下一个内容。 这项调整的妙处在于,它使普通滚动都停留在每页的边缘,因此页面捕捉也很愉快。

我正在展示我上面演示的“ Animal peek-a-boo”示例的示例代码。 在示例中,滚动视图的每一页都有一个UIImageView和一个UIImage 。 如上面多次说明, 无限滚动视图只需要三个页面 ,因此我们准备了三个UIImageView的数组UIImageView用它们(称为imageViews )。 每当我们调整页面的位置时,我们都会调整那些UIImageView的框架,并将新图像设置为UIImageView

相应的代码如下:

摘要

当您想使用UIScrollView实现无限滚动视图时,可以通过调整contentOffset及其内容视图的框架来实现。

当您需要分页时,vanilla方法将不起作用,但是您可以通过限制调整时间来应用相同的概念 我阅读的某些文章需要5页,但实际上,您只需要在页面中间进行调整就可以准备3页。

我认为您也可以使用UIPageViewController做类似的事情,但是它将需要UIViewController成为页面成员,并且在许多情况下,您不想为每个成员创建UIViewController 。 还需要注意的是, UICollectionView UITableView 继承自 UIScrollView 并且在使用这些常见UI元素时,该技术应直接适用。

“ Animal peek-a-boo”示例代码在github中。 如果您有兴趣,请随时查看,如果有任何反馈等,请让我知道。