4.IOS(swift)-scrollView(tableView) ·图片加载逻辑的优化。iOS开发-UIScrollView实践深入上。

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代码

相关文章

admin

网站地图xml地图