iOS开发-探索scrollView的实现

序言

UIScrollView滚动视图,绝对算的上是iOS开发中最重要的控件,用来展示多于一个屏幕的内容,可以滚动显示超过屏幕外的内容的特性使其产生了更多强大的子类:UITableView、UICollectionView、UITextView等等。尽管功能如此强大,但是scrollView本质上只是一个UIView的黑魔法,本文将剖析UIScrollView这种强大特性的实现过程


图层渲染

这里不得不提到UIView和CALayer的关系。在UIKit框架中,UIView是所有界面元素的基础,我们页面上可见的控件几乎都是从这个类派生出来的。之所以说几乎意味着我们也可以不通过UIView及其子类的途径来展示一些页面效果,比如有渐变效果的进度条——通过CALayer直接完成。关于两者的具体区别以及关系,我们不在这里详说,只需要知道每一个UIView管理着一个CALayer,所有我们看到的内容都是由后者进行渲染的。
当我们添加子视图的时候,会基于当前视图的坐标系原点进行计算,然后在设置好的位置对子视图的layer进行渲染,假设现在添加一个frame为40, 40, 120, 40的按钮,那么渲染图示如下:

1.png

在按钮添加到当前视图之前,按钮自身先进行了渲染,然后在距离父视图上边40点,左边40点的位置进行组合。正是因为这种组合的渲染模式,在顶层的视图总是会遮盖下层的视图。通过下面的视图组合流程,我们也能明白为什么创建的view的bounds总是{0, 0, width, height}

1.png

根据上面的视图组合,我们试想一下,如果button的坐标是(100, 40),那么这个按钮还会显示在view上面吗?答案是肯定的,因为根据上面视图组合的实现,我们可以得出一个结论:当前的视图也存在一个父视图,父视图也存在其所在的父视图。如此循环,直到这个视图是keyWindow为止。那么我们就有下面的结构图示

1.png

因此按钮是处在我们可视的范围内的。但是,按照这种组合方式,scrollView的实现就显得非常的神奇了,因为在scrollView上面的子视图一旦超过了它的显示范围。这里需要说到view的clipsToBoundslayer的maskToBounds属性,这两个属性尽管名字不一样,但是如果你在堆栈调用的时候进行调试,会发现最终调用的是maskToBounds方法。这两个值任意一个设置YES的时候,在上面视图组合的③步骤中,超出父视图范围内的部分将不进行渲染。
那么scrollView是否跟我们猜测的一样,通过设置maskToBounds这个值来屏蔽超出其显示范围的子视图呢?如果是的话,那么scrollView就只是一个普通的UIView。我们通过下面的代码验证

UIScrollView * scrollView = [[UIScrollView alloc] initWithFrame: CGRectMake(0, 0, 200, 180)];
scrollView.backgroundColor = [UIColor orangeColor];
scrollView.center = self.view.center;
[self.view addSubview: scrollView];

UIView * subview = [[UIView alloc] initWithFrame: CGRectMake(60, 60, 180, 180)];
subview.backgroundColor = [UIColor blueColor];
[scrollView addSubview: subview];

这时候scrollView的效果是这样的:


1.png

接下来设置scrollView的maskToBounds属性

 scrollView.layer.masksToBounds = NO;

效果图


1.png

可以看到,scrollView本质上不过是一个默认遮盖范围外子视图的UIView罢了。那么,UIView到底使用了什么黑魔法来实现滚动视图呢?


contentOffset

用过scrollView的开发者对这个属性都不陌生,contentOffset决定了当前scrollView显示内容的范围,即是当前scrollView的左上角的显示位置坐标。通过图片轮播控件来探究这个属性的实现

1.png

上图中scrollView发生了滚动,使得显示的图片从1变成2。在这个过程中,contentOffset也从(0, 0)变为(width, 0) 从这张图上看更像是子视图的位置发生了移动,从右向左移动。但是在这一切发生的过程中,子视图的frame没有发生过任何变化,因此与其说是滚动,不如说是scrollView基于子视图的所在的坐标系发生了偏移:


1.png

两张图都表示了图片轮播的过程,但是第二张更加接近scrollView滚动实现的本质——基于自身的坐标系发生了位置偏移。因此,contentOffset实际上表示的是scrollView的bounds的改变,其实现大概如下

 - (void)setContentOffset: (CGPoint)contentOffset
 {
    _contentOffset = contentOffset;
    CGRect bounds = self.bounds;
    bounds.origin = contentOffset;
    self.bounds = bounds;
 }

contentSize

如果说contentOffset决定了scrollView的窗口,那么contentSize决定了这个窗口背后的风光。

1.png

contentSize决定了scrollView显示内容的尺寸范围,从上图看,我们可以知道,在contentSize的宽度或者长度任意一个尺寸大于scrollView等边长度的时候,scrollView才能实现滚动效果。当然了,单单是contentSize是不足以让我们实现scrollView的滚动范围限制的,这是contentSizecontentOffset的共同实现效果:
1.png


contentInset

contentInset是一个相当有用的属性,我在做的一个毛玻璃效果导航栏上下拉效果时就通过这个属性实现。这个属性可以在某种意义上增加或者减少我们的滚动尺寸范围:

1.png

可以看到,contentInset让我们原本contentOffsetcontentSize协同作用的滚动范围发生了改变,原本最上角(0, 0)的限制坐标变成了(-contentInset.left, -contentInset.top)
既然contentInset只是简单的改变了滚动范围的规则,为什么我们不直接通过contentSize来实现呢?这是由于更多时间,我们还需要在滚动视图的某个方向上面留下一块空白的区域进行自定义,这时候直接设置contentInset是最快的方式。而换成contentSize来实现,我们还必须同时改变bounds跟center来实现(不要直接改变frame,在组合视图时,frame最后是由bounds和center决定的)


尾话

苹果对于scrollView的实现十分的巧妙,在没有造成过多损耗的情况下赋予UIView一份强大无比的力量。解剖UIKit不仅仅是为了探索实现,这对于我们自定义控件能有更多的认识。在scrollView更上一层的UITableView通过复用队列的方式将scrollView的能力更加完美的展示出来,而这个复用机制值得我们去思考实现过程。本文demo
文集:iOS开发

转载注明链接:探索UIScrollView的实现

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容