Table View 中图片加载逻辑的优化
虽然这种优化方式在现在的机能和网络环境下可能看似不那么必要,但在我最初看到这个方法是的 09 年(印象中是 Tweetie 作者在 08 年写的 Blog,可能有误),遥想 iPhone 3G/3GS 的机能,这个方法为多图的 table view 的性能带来很大的提升,也成了我的秘密武器。而现在,在移动网络环境下,你依然值得这么做来为用户节省流量。
先说一下原文的思路:
- 当用户手动 drag table view 的时候,会加载 cell 中的图片;
- 在用户快速滑动的减速过程中,不加载过程中 cell 中的图片(但文字信息还是会被加载,只是减少减速过程中的网络开销和图片加载的开销);
- 在减速结束后,加载所有可见 cell 的图片(如果需要的话);
问题 1:
前面提到,刚开始拖动的时候,dragging 为true,decelerating为false;decelerate过程中,dragging和decelerating都为true;decelerate 未结束时开始下一次拖动,dragging和decelerating依然都为true。所以无法简单通过table view的dragging和decelerating判断是在用户拖动还是减速过程。
解决这个问题很简单,添加一个变量如userDragging,在 willBeginDragging中设为true,didEndDragging中设为false。那么tableView: cellForRowAtIndexPath: 方法中,是否load 图片的逻辑就是:
if (!self.userDragging && tableView.decelerating) {
cell.pictureView.image = nil;
println("拖动中和减速中,不显示图片")
} else {
// code for loading image from network or disk
println("拖动和减速结束,显示图片")
}
问题 2:
这么做的话,decelerate结束后,屏幕上的 cell 都是不带图片的,解决这个问题也不难,你需要一个形如loadImageForVisibleCells的方法,加载可见cell的图片:
func loadImageForVisibleCells(){
var cells:NSArray = self.tableView.visibleCells()
for cell in cells {
var indexPath:NSIndexPath = self.tableView.indexPathForCell(cell as! UITableViewCell)!
self.setupCell(cell as! TableViewCell, widthIndexPath: indexPath)
}
}
问题 3:
这个问题可能不容易被发现,在减速过程中如果用户开始新的拖动,当前屏幕的cell并不会被加载(前文提到的调用顺序问题导致),而且问题 1 的方案并不能解决问题 3,因为这些 cell 已经在屏上,不会再次经过 cellForRowAtIndexPath 方法。虽然不容易发现,但解决很简单,只需要在 scrollViewWillBeginDragging: 方法里也调用一次 loadImageForVisibleCells 即可。
再优化
上述方法在那个年代的确提升了table view的performance,但是你会发现在减速过程最后最慢的那零点几秒时间,其实还是会让人等得有些心急,尤其如果你的 App 只有图片没有文字。在 iOS 5 引入了 scrollViewWillEndDragging: withVelocity: targetContentOffset: 方法后,配合 SDWebImage,我尝试再优化了一下这个方法以提升用户体验:
- 如果内存中有图片的缓存,减速过程中也会加载该图片
- 如果图片属于 targetContentOffset 能看到的 cell,正常加载,这样一来,快速滚动的最后一屏出来的的过程中,用户就能看到目标区域的图片逐渐加载
- 你可以尝试用类似 fade in 或者 flip 的效果缓解生硬的突然出现(尤其是像本例这样只有图片的 App)
核心代码:
func scrollViewWillBeginDragging(scrollView: UIScrollView) {
self.userDragging = true
self.targetRect = nil;
self.loadImageForVisibleCells()
}
func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
var targetRect:CGRect = CGRectMake(targetContentOffset.memory.x, targetContentOffset.memory.y, scrollView.frame.size.width, scrollView.frame.size.height)
self.targetRect = NSValue(CGRect: targetRect)
}
func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
println("结束减速")
self.targetRect = nil;
self.loadImageForVisibleCells()
}
是否需要加载图片的逻辑:
var shouldLoadImage:Bool = true
//判断是否重叠
if(self.targetRect != nil && CGRectIntersectsRect(self.targetRect!.CGRectValue(), cellFrame)){
//判断是否有缓存,加载缓存
var manager:SDWebImageManager=SDWebImageManager.sharedManager()
var cache:SDImageCache = manager.imageCache
var key:String = manager.cacheKeyForURL(targetURL)
if((cache.imageFromMemoryCacheForKey(key)) != nil){
shouldLoadImage = false
}
}
//如果没有缓存,缓存图片
if(shouldLoadImage){
}
更值得高兴的是,通过判断是否 nil,targetRect 同时起到了原来 userDragging 的作用。