当 scroll view 开始滚动时,scroll view 的 delegate 会不断的收到很多回调消息,这些消息告诉我们 scroll view 处于什么状态。通过官方的文档,我们知道用户在和 scroll view 交互时有两种手势,一种是 flick(轻扫),一种是 drag(拖动)。
但是在实际的操作过程中,用户很可能会带来更复杂的交互,经过实践归纳总结,我将它们大概分成四种情况,如下图:
- 橙色线,用户用手指轻扫一下屏幕,然后释放手指。
- 橙色线->绿色线->蓝色线,用户拖动 scroll view,并在 scroll view 处于静止时释放手指。
- 橙色线->红色线->…->橙色线,用户在 scroll view 停止滚动前,不断的轻扫屏幕,最后释放手指。
- 橙色线->红色线->…->橙色线->绿色线->紫色线,用户在轻扫屏幕后,手指突然定住 scroll view,最后释放手指。
Glow 的这篇博文中提到了当年 Tweetie 的方案以及自己的优化方案,大概的思路就是,当用户在快速滑动 table view 时,cell 不加载图片,对应上面的情况三。那么如何定义这个快速滑动的情况呢?
快速滑动就是用户不断的 flick 屏幕,也就是在上一次 scroll view 还未停止的时候开始下一次短暂拖动,这就导致 scroll view 一直处于滚动状态。
如果我们想单纯的让 table view 在快速滚动的过程中不加载图片的话,
分析如下:
- 当用户手动 drag table view 的时候,会加载 cell 中的图片;
- 在用户快速滑动的减速过程中,不加载过程中 cell 中的图片(但文字信息还是会被加载,只是减少减速过程中的网络开销和图片加载的开销);
- 在减速结束后,加载所有可见 cell 的图片(如果需要的话);
现在的问题就在于怎么区分判断用户是在拖动还是减速过程
因为:
前面提到,刚开始拖动的时候,dragging 为 YES,decelerating 为 NO;decelerate 过程中,dragging 和 decelerating 都为 YES;decelerate 未结束时开始下一次拖动,dragging 和 decelerating 依然都为 YES。所以无法简单通过 table view 的 dragging 和 decelerating 判断是在用户拖动还是减速过程。
解决这个问题很简单,添加一个变量如 userDragging,在 willBeginDragging 中设为 YES,didEndDragging 中设为 NO。
那么 tableView: cellForRowAtIndexPath: 方法中,是否 load 图片的逻辑就是:
if (!self.userDragging && tableView.decelerating) {
//这个条件就是说明是在快速滑动过程中,并且没有拖动的情况下
cell.imageView.image = nil;
} else {
// code for loading image from network or disk
}
问题2: 只做 decelerating 的判断可以让 table view 在快速滚动的过程中做到完全不加载图片。但是这样做还是会产生两个问题,
1,当 table view 在停止快速滑动正常减速结束后,屏幕上的 cell 都是不带图片的。
这个问题比较好解决,Glow 提到的解决方法是在 scrollViewDidEndDecelerating 里面加载当前可见 cell 的图片,你需要一个形如 loadImageForVisibleCells 的方法,加载可见 cell 的图片:
- (void)loadImageForVisibleCells
{
NSArray *cells = [self.tableView visibleCells];
for (GLImageCell *cell in cells) {
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
[self setupCell:cell withIndexPath:indexPath];
}
}
这样确实解决了问题,但是却带来了新的问题,必须要等到 scroll view 完全减速完成后才会加载图片,但是 scroll view 减速的那最后一段距离实在让人难等。Glow 通过 scrollViewWillEndDragging: withVelocity: targetContentOffset: 方法的 targetContentOffset 计算出 targetRect 来加载最后可见的那些 cell。
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
CGRect targetRect = CGRectMake(targetContentOffset->x, targetContentOffset->y, scrollView.frame.size.width, scrollView.frame.size.height);
self.targetRect = [NSValue valueWithCGRect:targetRect];
}
具体来说,就是用一个变量 targetRect 在 scrollViewWillEndDragging: withVelocity: targetContentOffset: 方法里面计算出值,在 scrollViewWillBeginDragging 和 scrollViewDidEndDecelerating 重置为 nil。这样一来,targetRect 还起到了之前 userDragging 的作用,可谓一举两得。
参考地址:
http://tech.glowing.com/cn/practice-in-uiscrollview/
http://www.jianshu.com/p/cd12c7e57996