澳门永利234555com纵然如此那种优化措施在近期的出力和网络环境下大概接近不那么要求,是 UIKit 中为数不多能响应滑出手势的

虽然这种优化方式在现在的机能和网络环境下可能看似不那么必要,是 UIKit 中为数不多能响应滑动手势的

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

即便那种优化措施在现行的功用和网络环境下只怕接近不那么须求,但在自家最初见到这一个措施是的
09 年(印象中是 Tweetie 小编在 08 年写的 Blog,只怕有误),遥想 三星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
不会被调用,但前3回留下的 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 方法。固然不便于发现,但化解很不难,只须求在
scrollView威尔BeginDragging: 方法里也调用二遍 loadImageForVisibleCells
即可。

实例

下边通过有个别实例,更详实地示范和讲述以上各 delegate 方法的用处。

  1. Table View 中图纸加载逻辑的优化
    虽说那种优化措施在当今的功效和网络环境下或者类似不那么须求,但在本身早期见到这些法子是的
    09 年(印象中是 Tweetie 我在 08 年写的 Blog,恐怕有误),遥想
    魅族 3G/3GS 的效应,那些主意为多图的 table view
    的本性带来非常大的升级,也成了我的秘密武器。而现行,在移动网络环境下,你仍旧值得那样做来为用户节省流量。

先说一下原稿的思绪:

当用户手动 drag table view 的时候,会加载 cell 中的图片;
在用户快捷滑动的减速进度中,不加载进程中 cell
中的图片(但文字音讯如故会被加载,只是减弱减速进程中的互联网开发和图纸加载的用度);
在减速为止后,加载全部可知 cell 的图片(借使须求的话);

再优化

上述方法在卓殊时代的确提高了table
view的performance,可是你会发将来减速进度最终最慢的那零点几秒时间,其实还是会令人等得有个别着急,尤其固然你的
App 唯有图表并未文字。在 iOS 5 引入了 scrollView威尔EndDragging:
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 方法。即便不便于发觉,但消除很简单,只需求在
scrollView威尔BeginDragging: 方法里也调用三次 loadImageForVisibleCells
即可。

再优化

上述方法在尤其时期的确进步了 table view 的
performance,可是你会意识在减速进程最后最慢的那零点几秒时间,其实依然会令人等得某个心急,越发若是你的
App 唯有图表并未文字。在 iOS 5 引入了 scrollView威尔EndDragging:
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
体验上的的差异所在(但愿她们不会看出本文并且调动他们的完成格局)。本例通过三种办法完毕相似的2个处境,你可以透过安装到手机上来感受两种完成格局的不等用户体验。为了不一样每种例子的重点,本例没有收录机制,重用相关内容见例
3。

2.1 pagingEnabled

这是系统提供的分页格局,最简便,可是有一部分局限性:

只好以 frame size 为单位翻页,减速动画阻尼大,减速进度不超过一页
亟需有的 hacking 完成 bleeding 和 padding(即页与页之间有
padding,在当前页可以见到前后页的部分情节)
萨姆ple 中 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

通过修改 scrollView威尔EndDragging: 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. 联动/视差滚动

上3个例证里 main scroll view 和 title view 里的 scroll view
就是二个联动的事例,所谓联动,就是当 A 滚动的时候,在
scrollViewDidScroll: 里依照 A 的 contentOffset 动态统计 B 的
contentOffset 并设给 B。同样对于非 scroll view 的 C,也得以动态统计 C 的
frame 或是 transform(Glow
的气泡为例)完毕视差滚动或然其余高档动画,那在后天无数施用的率领页面里会被用到。

UIScrollView代码