dragging和decelerating依旧都为true,是 UI基特(Kit) 中为数不多能响应滑出手势的澳门皇冠官网app

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
是 UI基特 中为数不多能响应滑出手势的
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 方法。尽管不便于觉察,但解决很简单,只需要在
scrollView威尔BeginDragging: 方法里也调用三次 loadImageForVisibleCells
即可。

实例

下面通过一些实例,更详尽地示范和讲述以上各 delegate 方法的用处。

  1. Table View 中图纸加载逻辑的优化
    虽说这种优化措施在当今的效果和网络环境下或者接近不那么必要,但在我最初见到这多少个点子是的
    09 年(影像中是 Tweetie 作者在 08 年写的 Blog,可能有误),遥想
    OPPO 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
体验上的的歧异所在(但愿她们不会合到本文并且调动他们的贯彻格局)。本例通过二种艺术实现相似的一个场景,你可以因此安装到手机上来感触二种实现模式的例外用户体验。为了区别每个例子的要害,本例没有收录机制,重用相关内容见例
3。

2.1 pagingEnabled

这是系统提供的分页形式,最简便,不过有一对局限性:

只好以 frame size 为单位翻页,减速动画阻尼大,减速过程不超越一页
急需部分 hacking 实现 bleeding 和 padding(即页与页之间有
padding,在当前页可以看出前后页的局部情节)
山姆(Sam)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

通过修改 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 时,记得 add蔡尔德(Childe)(Childe)ViewController
  • 当 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代码

相关文章