UIScrollView的contentInset和Pull-to-Refresh

iOS 6开始加入了UIRefreshControl 来帮助实现下拉刷新 效果,用现成的控件自然是方便,但不明白其实现原理心里还是有些惴惴不安。于是小探索了一下,实现一个基本的下拉效果。

原理很简单,是添加一个Subview 到目标UIScrollViewUITableView)里去,并且设置其frame 使其在显示区域外面(y 等于负的高度)即可。这样当UIScrollView 下滑超出其边界时,会显示出我们添加进去的Subview。
然后在UIScrollViewDelegate 相应的代理函数里,检测到下拉距离超过定义的高度时就可以触发刷新了。

问题

但是这时松开手指UIScrollView 会产生回滚的效果,然后我们添加的Subview 又跑到显示区域外面了。所以要在触发刷新时调整UIScrollView,让我们的Subview 继续显示在上面。方法就是调整UIScrollViewcontentInset 属性。

contentInset 可以扩大UIScrollView 的边界,让其内容即使在超过原本的边界时不会回退。来句题外话,有这种属性确实比Android 的ListView 方便不少。

通过Google 来的结果,我开始也是在UIScrollViewDelegate

1
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool)

里设置contentInset 的,这样确实能够实现效果,但出现一个问题,就是放开手指的一瞬间UITableView 会产生一个跳动。StackOverflow 了一下,确实也有人提到这个问题:
Animating UIScrollView contentInset causes jump stutter

按照他的“解决方案”,在改动contentInset 的同时也需要改动contentOffset。而且contentOffset 的改动必须contentInset 的改动一起放到animation 块里,挪到外面都没有效果(亲测)。

这很难有说服力啊。。。

分析

继续观察了几次“跳动”的表现,发现它总是先向下跳一段,再跳回原来位置,然后做正常的回退动画。结合上面问题的“解决方案”,感觉问题确实是发生在contentOffset 上:
在设置contentInset 后,和当前要做回滚动画的contentOffset 产生了一些冲突,于是UIScrollView 就重新计算并设置了contentOffset,画面上就产生了一个“跳动”。
而上面的“解决方案”,是帮助UIScrollView 重新计算了contentOffset,而且又是放在animation 块里的,所以能和最后要做的回滚动画贴合起来。

简单总结一下流程:
本来UIScrollView 要做1;你告诉它做2;UIScrollView 说我不懂2,需要重新计算从1变到2;你又告诉它我来告诉你怎么样变到2。

{怎么看都觉得别扭。。。。}

解决

重新看了看UIScrollViewDelegate 里的回调方法,又发现了一个:

1
2
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint,
targetContentOffset targetContentOffset: UnsafeMutablePointer<CGPoint>)

关键是第三个参数targetContentOffset

The expected offset when the scrolling action decelerates to a stop.

然后是关于这个回调的说明:

This method is not called when the value of the scroll view’s pagingEnabled property is YES. Your application can change the value of the targetContentOffset parameter to adjust where the scrollview finishes its scrolling animation.

也就是说,UIScrollView 在做回滚动作之前,已经通过这个回调告诉自己的代理它要移动的offset 了。我们不正好可以利用这个嘛。
于是把对contentInset 的设置放到这个回调里,并且调整targetContentOffset 的值,UITableView终于不再跳了。不过回滚动画没有了,估计是因为直接改了targetContentOffset 的缘故。这好说,把contentInset 放到一个animation 块里就好了。

现在的流程变成了:UIScrollView说我要做1了,你看这样行不行;你告诉它不如这样做2。
这样感觉顺多了。