Table View 中图纸加载逻辑的优化

虽然这种优化措施于今天之效力和网环境下可能接近不那么必要,但每当自头见到这个艺术是的
09 年(印象中是 Tweetie 作者在 08 年形容的 Blog,可能产生误),遥想 iPhone
3G/3GS 的意义,这个办法也多图的 table view
的属性带来十分可怜的升级换代,也改为了自之秘密武器。而现在,在移动网环境下,你依旧值得这样做来吧用户节省流量。

先行说一下原稿的思绪:

  1. 当用户手动 drag table view 的早晚,会加载 cell 中的图纸;
  2. 于用户迅速滑动的减速过程中,不加载过程中 cell
    中之图纸(但言信息还是会见被加载,只是减少减速过程中之网络开发和图纸加载的付出);
  3. 以减速结束后,加载所有可见 cell 的图(如果需要的话);

UIScrollView (包括它的子类
UITableView和UICollectionView)是iOS开发中极其有扩展性的UI控件。UIScrollView
是 UIKit 中为数不多能响应滑动手势的
view,相比自己用UIPanGestureRecognizer 实现部分因滑动手势的成效,用
UIScrollView 的优势在 bounce 和 decelerate 等特点可叫 App
的用户体验与 iOS 系统的用户体验保持一致。本文通过有些实例讲解
UIScrollView 的风味与事实上行使受到的经历。

问题 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("拖动和减速结束,显示图片")
}

UIScrollView 和 Auto Layout

UIScrollView 于 Auto Layout 是一个特别突出的 view,对于 UIScrollView 的
subview 来说,它的 leading/trailing/top/bottom space 是对立于
UIScrollView 的 contentSize 而非是 bounds 来确定的,所以当您品尝用
UIScrollView 和它 subview 的 leading/trailing/top/bottom
来互相决定大小的时段,就会冒出「Has ambiguous scrollable content
width/height」的 warning。正确的姿态是为此 UIScrollView 外部的 view 或
UIScrollView 本身的 width/height 确定 subview 的尺寸,进而确定
contentSize。因为 UIScrollView 本身的 leading/trailing/top/bottom
变得不好用,所以我习惯的做法是在 UIScrollView 和她原本的 subviews
之间加一个 content view,这样做的补益有:

不会在 storyboard 里留下 error/warning
为 subview 提供 leading/trailing/top/bottom,方便 subview 的布局
由此调整 content view 的 size(可以是 constraint 的 IBOutlet)来调整
contentSize
免欲 hard code 与屏幕尺寸相关的代码
更好地支持 rotation

问题 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)
        }
}

UIScrollViewDelegate

UIScrollViewDelegate 是 UIScrollView 的 delegate protocol,UIScrollView
有意思的功能都是通过它们的 delegate
方法实现的。了解这些艺术为触发的规则与调用的各个对于以 UIScrollView
是特别有必不可少之,本文主要谈拖动相关的力量,所以 zoom
相关的章程跳了无取,拖动相关的 delegate 方法以调用顺序分别是:

  • (void)scrollViewDidScroll:(UIScrollView *)scrollView

是主意在旁方式触发 contentOffset
变化的上都见面受调用(包括用户拖动,减速过程,直接通过代码设置等),可以用来监控
contentOffset 的转变,并根据当前之 contentOffset 对其它 view
做出随动调整。

  • (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView

用户开始拖动 scroll view 的早晚让调用。

  • (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
    withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint
    *)targetContentOffset

欠方式从 iOS 5 引入,在 didEndDragging 前吃调用,当 willEndDragging
方法中 velocity 为
CGPointZero(结束拖动时少独趋势还无速度)时,didEndDragging 中之
decelerate 为 NO,即没有放慢过程,willBeginDecelerating 和
didEndDecelerating 也便非会见叫调用。反之,当 velocity 不为 CGPointZero
时,scroll view 会以 velocity 为新速度,减速直到
targetContentOffset。值得注意的凡,这里的 targetContentOffset
是只指针,没错,你可改减速运动的目的地,这当一些成效的实现时颇发生因此,实例章节中会具体涉及她的用法,并与其余实现方式作比较。

  • (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
    willDecelerate:(BOOL)decelerate

以用户结束拖动后受调用,decelerate 为 YES
时,结束拖动后会见出减速过程。注,在 didEndDragging
之后,如果产生减速过程,scroll view 的 dragging 并无会见马上置为
NO,而是一旦对等交减速结束之后,所以这 dragging 属性的莫过于语义更接近
scrolling。

  • (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView

放慢动画开始前受调用。

  • (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

放慢动画结束时吃调用,这里产生同等种奇特情形:当一软减速动画尚未终了的时光还
drag scroll view,didEndDecelerating 不会见受调用,并且这 scroll view 的
dragging 和 decelerating 属性都是 YES。新的 dragging 如果有加速度,那么
willBeginDecelerating 会再同赖让调用,然后才是
didEndDecelerating;如果无加速度,虽然 willBeginDecelerating
不会见于调用,但前面同一不行留下的 didEndDecelerating
会被调用,所以总是快速轮转一个 scroll view 时,delegate
方法给调用的依次(不包含 didScroll)可能是这么的:

scrollViewWillBeginDragging:
scrollViewWillEndDragging: withVelocity: targetContentOffset:
scrollViewDidEndDragging: willDecelerate:
scrollViewWillBeginDecelerating:
scrollViewWillBeginDragging:
scrollViewWillEndDragging: withVelocity: targetContentOffset:
scrollViewDidEndDragging: willDecelerate:
scrollViewWillBeginDecelerating:

scrollViewWillBeginDragging:
scrollViewWillEndDragging: withVelocity: targetContentOffset:
scrollViewDidEndDragging: willDecelerate:
scrollViewWillBeginDecelerating:
scrollViewDidEndDecelerating:

尽管可怜少发盖这导致的
bug,但是你需要了解这种非常广阔的用户操作会招致的中间状态。例如你尝试以
UITableViewDataSource 的 tableView:cellForRowAtIndexPath: 方法中冲
tableView 的 dragging 和 decelerating
属性判断是当用户拖拽还是放慢过程遭到之说话也许会见误判。

问题 3:

以此题目恐怕未容易受发现,在减速过程被而用户开始新的拖动,当前屏幕的cell并无见面吃加载(前文提到的调用顺序问题造成),而且题目
1 的方案并无可知缓解问题 3,因为这些 cell 已经在屏上,不见面重经过
cellForRowAtIndexPath 方法。虽然非易于发觉,但解决那个简单,只待以
scrollViewWillBeginDragging: 方法里为调用同样软 loadImageForVisibleCells
即可。

实例

下通过有些实例,更详尽地示范与描述以上每 delegate 方法的用。

  1. Table View 中图纸加载逻辑的优化
    虽这种优化措施在现今的效果和网环境下或接近不那么必要,但在自我早期见到此措施是的
    09 年(印象中凡 Tweetie 作者以 08 年描绘的 Blog,可能有误),遥想
    iPhone 3G/3GS 的机能,这个法子吗多图的 table view
    的性带来大十分的升级,也成了自我之秘密武器。而现,在运动网环境下,你还值得这样做来啊用户节省流量。

事先说一下原稿的笔触:

当用户手动 drag table view 的时段,会加载 cell 中之图纸;
当用户快速滑动的减速过程遭到,不加载过程遭到 cell
中的图纸(但言信息还是会于加载,只是减少减速过程遭到的纱开发和图表加载的开);
以减速结束晚,加载所有可见 cell 的图纸(如果需要的话);

再优化

上述措施在很年代的确提升了table
view的performance,但是若会发觉在减速过程最后极缓慢的那零点几秒时,其实还是碰头让人口齐得多少急,尤其要你的
App 只发生图片并未文字。在 iOS 5 引入了 scrollViewWillEndDragging:
withVelocity: targetContentOffset: 方法后,配合
SDWebImage,我尝试重新优化了一下以此方式为提升用户体验:

  1. 倘内存中产生图片的缓存,减速过程遭到也会加载该图
  2. 一经图片属于 targetContentOffset 能见到的
    cell,正常加载,这样一来,快速轮转的末尾一屏出的之经过中,用户就能看到目标区域的图纸逐渐加载
  3. 您可品尝用接近 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
的图。

Paste_Image.png

问题 1:

前方提到,刚开拖动的下,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:

如此做的话,decelerate 结束晚,屏幕及之 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];
    }
}

问题 3:

这题目或许无容易为发现,在减速过程遭到使用户开始新的拖动,当前屏幕的
cell 并无见面让加载(前文提到的调用顺序问题导致),而且题目 1
的方案并无克迎刃而解问题 3,因为这些 cell 已经当屏上,不见面又经过
cellForRowAtIndexPath 方法。虽然不便于觉察,但解决那个简短,只待以
scrollViewWillBeginDragging: 方法里也调动用同样次于 loadImageForVisibleCells
即可。

再优化

上述措施在深年代的确提升了 table view 的
performance,但是你见面发觉在减速过程最后太缓慢的那么零点几乎秒时,其实还是会见为人口顶得多少着急,尤其要你的
App 只出图表并未文字。在 iOS 5 引入了 scrollViewWillEndDragging:
withVelocity: targetContentOffset: 方法后,配合
SDWebImage,我尝试还优化了瞬间以此方式以升级用户体验:

若内存中产生图表的缓存,减速过程遭到吗会见加载该图
而图片属于 targetContentOffset 能见到底
cell,正常加载,这样一来,快速轮转的末尾一屏出的之长河遭到,用户就能看到目标区域的图形逐渐加载
而可品尝用接近 fade in 或者 flip
的效用缓解生硬的豁然出现(尤其是像本例这样只是生图表的 App)
核心代码:

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
    self.targetRect = nil;
    [self loadImageForVisibleCells];
}

- (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];
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    self.targetRect = nil;
    [self loadImageForVisibleCells];
}

是不是需要加载图片的逻辑:

BOOL shouldLoadImage = YES;  
if (self.targetRect && !CGRectIntersectsRect([self.targetRect CGRectValue], cellFrame)) {  
    SDImageCache *cache = [manager imageCache];
    NSString *key = [manager cacheKeyForURL:targetURL];
    if (![cache imageFromMemoryCacheForKey:key]) {
        shouldLoadImage = NO;
    }
}
if (shouldLoadImage) {  
    // load image
}

重值得高兴之凡,通过判断是否 nil,targetRect 同时由及了本 userDragging
的企图。

2. 分页的几乎种植实现方式

运 UIScrollView
有多方法实现分页,但是个别的机能和用不尽相同,其中措施 2 跟章程 3
的分别吧多亏有同类 App 在法 Glow 的首页 Bubble 翻转效果时跟 Glow
体验及之底反差所在(但愿她们不见面看本文并且调动他们之落实方式)。本例通过三栽方法实现相似之一个现象,你可以透过安装至手机上来感触三栽实现方式的差用户体验。为了区别每个例子的严重性,本例没有选用机制,重用相关内容见例
3。

2.1 pagingEnabled

就是系统提供的分页方式,最简易,但是生一些局限性:

仅能够因为 frame size 为单位翻页,减速动画阻尼大,减速过程不跳同样页
亟待有些 hacking 实现 bleeding 和 padding(即页与页里时有发生
padding,在此时此刻页可以看看前后页的片情节)
Sample 中 Pagination 有大概实现 bleeding 和 padding
效果的代码,主要的思绪是:

让 scroll view 的小幅为 page 宽度 + padding,并且安装 clipsToBounds 为
NO
诸如此类尽管会见到前后页的情节,但是力不从心响应
touch,所以用另一个掩要之可触摸区域之 view 来促成类似 touch
bridging 的效用
适用场景:上述局限性同时为是这种实现方式的独到之处,比如一般 App
的带页(教程),Calendar 里的月视图,都可据此这种办法实现。

2.2 Snap

这种方式就是当 didEndDragging 且不论减速动画,或以减速动画就时,snap
到一个整数页。核心算法是透过时 contentOffset
计算最近之整数页及其相应的 contentOffset,通过动画 snap
到该页。这个措施实现的机能还出只短,就是终极之 snap 会在 decelerate
结束以后才发生,总觉得特别突兀。

2.3 修改 targetContentOffset

通过改 scrollViewWillEndDragging: withVelocity: targetContentOffset:
方法中之 targetContentOffset 直接修改目标 offset
为整数页位置。其中基本代码:

- (CGPoint)nearestTargetOffsetForOffset:(CGPoint)offset
{
    CGFloat pageSize = BUBBLE_DIAMETER + BUBBLE_PADDING;
    NSInteger page = roundf(offset.x / pageSize);
    CGFloat targetX = pageSize * page;
    return CGPointMake(targetX, offset.y);
}

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
    CGPoint targetOffset = [self nearestTargetOffsetForOffset:*targetContentOffset];
    targetContentOffset->x = targetOffset.x;
    targetContentOffset->y = targetOffset.y;
}

适用场景:方法 2 跟 方法 3
的规律近似,效果呢相近,适用场景也基本相同,但方 3
的体会会好广大,snap 到整数页的历程十分自然,或者说用户完全感知不交 snap
过程的留存。这半种植方法的放慢过程流畅,适用于一屏发差不多页,但要以整数页滑动的现象;也适用于要图中自动
snap 到整数龙的状况;还适用于每页大小不等的状况下 snap
到整数页的光景(不开举例,自行发挥,其实只是需要修改计算目标 offset
的方法)。

3. 重用

绝大多数的 iOS 开发相应都清楚 UITableView 的 cell
重用机制,这种用机制减少了内存开销也增长了 performance,UIScrollView
作为 UITableView 的父类,在过剩状况被吗深合乎用收录机制(其实不单纯是
UIScrollView,任何场景中会反复出现的素都应当适度地引入重用机制)。

汝可以参见 UITableView 的 cell 重用机制,总结重用机制如下:

  • 维护一个录取队列
  • 当元素离开可见范围时,removeFromSuperview 并加入重用队列(enqueue)
  • 当需要投入新的素时,先品尝从录取队列爱博体育获取可选用元素(dequeue)并且于录取队列移除
  • 比方帮列为空,新建元素
  • 这些相似还在 scrollViewDidScroll: 方法中得

实质上利用中,需要小心的触及是:

  • 当用对象呢 view controller 时,记得 addChildeViewController
  • 当 view 或 view controller 被圈定但该对应 model
    发生变化的时光,需要马上清理用前留下的内容
  • 多少好当做缓存,在录用的时段尝试从缓存中读取数据甚至之前的状态(如
    table view 的 contentOffset),以博重新好的用户体验
  • 当 on screen 的因素数量而确定的时光,有时候可以提前 init
    这些元素,不见面以 scroll 过程被碰到因 init 开销带来的卡顿(尤其是坐
    view controller 为用对象的时段)

例 2 中之景十分适合因 view 为用单位,本例新增一个以 view controller
为用对象的例子,该例子同时演示了联动效应,具体见下单例。

4. 联动/视差滚动

达成一个例里 main scroll view 和 title view 里的 scroll view
就是一个联动的事例,所谓联动,就是当 A 滚动的早晚,在
scrollViewDidScroll: 里根据 A 的 contentOffset 动态计算 B 的
contentOffset 并设被 B。同样对非 scroll view 的 C,也堪动态计算 C 的
frame 或是 transform(Glow
的气泡为条例)实现视差滚动或者其它高档动画,这当现行多运用的引导页面里会受用到。

UIScrollView代码

相关文章