浅谈Constraints,Layout,Display的点点滴滴


前言


这篇博客完全是因为 浅谈Masonry的使用技巧 才引出来的,如果不是内容太多,也不会单独写一篇博客来记录,在9102一整年中我基本与普通UI开发无缘,大部分工作是对Layout进行操作绘制,以及使用CoreGraphics框架绘制各种图形,所以对Layout和Display的系统方法还是比较了解,近期又开始使用Masonry,所以对Constraints相关系统方法需要有所了解,而且在 浅谈Masonry的使用技巧 这篇博客中的优化部分不得不提出Constraints相关系统方法对其的影响。那么我们依照惯例,从基础的API开始进行吧。这里我基本上是从苹果的API抄录过来的,各位大佬可以自行去苹果API中心查看。


基础API方法介绍



Constraints 部分
  • needsUpdateConstraints 这个方法主要是用来判断当前View是否需要调用 updateConstraints 方法,如果在这个方法调用之前有约束发生了改变,调用这个方法返回值就是YES,但是它不会触发updateConstraints 方法的执行,只用用来判别当前控件是否需要调用更新约束方法。
- (BOOL)needsUpdateConstraints;
  • setNeedsUpdateConstraints 这个方法主要是用来标记当前控件是否需要调用updateConstraints 方法,看好了,只是标记,而不是调用。 当给控件标记上需要刷新约束,如果程序没有直接强制刷新(调用updateConstraintsIfNeeded),那么会在系统RunLoop的下个周期开始时调用 updateConstraints 方法。
- (void)setNeedsUpdateConstraints;
  • updateConstraints 这个方法是更新约束方法,在自定义控件中我们可以重写这个方法来添加我们自己的约束,对于updateConstraints 调用时机只能会有两种,一个是系统RunLoop的下个周期当发现需要更新布局(needsUpdateConstraints的值为YES)的时候,会自动调用该方法,或者是大佬们手动调用 updateConstraintsIfNeeded 来进行直接刷新。
- (void)updateConstraints;
  • updateConstraintsIfNeeded 这个方法是直接强制立即调用 updateConstraints 方法,不需要等待Runloop下个周期,也不会管 needsUpdateConstraints 的返回值是否为YES,反正就是强制执行就对了。
- (void)updateConstraintsIfNeeded;
  • updateViewConstraints 这个方法是在iOS6之后添加到UIViewController中的,具体作用和updateConstraints 方法类似。调用时机是 self.viewneedsUpdateConstraints 值为YES 或 [self.view updateConstraintsIfNeeded]; 都可。这个方法极大的方便了控制器本身的View的布局调整。
- (void)updateConstraintsIfNeeded;


Layout 部分
  • layoutSubviews 这个方法是动态调整子视图的布局,这个方法的调用时机是当前控件或者子控件的bounds 发生改变的时候就会调用。
- (void)layoutSubviews;
  • layoutIfNeeded 是立即强制执行layout操作的方法,但layoutSubviews 可能不会执行,因为如果控件或者子控件的bounds 没有发生改变时,layoutSubviews是不会执行的,所以说控件或者子控件的bounds 发生改变是 layoutIfNeeded 调起 layoutSubviews 的前提条件。
- (void)layoutIfNeeded;
  • setNeedsLayout 这个方法和上面的setNeedsUpdateConstraints作用类似,但它不是用来标记的,而是让布局失效的(可以看做间接导致了bounds的改变),所以如果在其调用下方调用 layoutIfNeeded 会立即调起 layoutSubviews 方法,或者等待Runloop下一个周期由系统调起 layoutSubviews
- (void) setNeedsLayout;


Display部分
  • drawRect 这个方法主要是当View控件需要自定义绘制内容的时候,一般会写在这个方法中。绘制上下文对象需要通过 UIGraphicsGetCurrentContext() 函数来获取,官方文档 中都写的明明白白的了(内容太多了,懒癌发作,大家自行去看吧)。这里就不过多叙述了。
- (void)drawRect:(CGRect)rect;
  • drawInContext 这个方面主要是CALayer中自定义绘制内容的时候,一般都会写在这个方法中。ctx 这个参数是绘制上下文对象,不需要额外获取了。
- (void)drawInContext:(CGContextRef)ctx;
  • setNeedsDisplay 这个方面主要是标记UIView或CALayer是否要刷新,看好了,是标记!而不是直接刷新,其作用和 setNeedsUpdateConstraints 非常的类似。
- (void)setNeedsDisplay;
  • setNeedsDisplay 这个方法主要是标记UIView或CALayer是否要刷新,看好了,是标记!而不是直接刷新,其作用和 setNeedsUpdateConstraints 非常的类似。
- (void)setNeedsDisplay;
  • displayIfNeeded 这个方法主要让CALayer直接进行强制绘制,UIView中没有该方法。所以UIView的重新绘制只能先使用setNeedsDisplay来标记,等待系统RunLoop的下一个周期开始进行重绘。
- (void)displayIfNeeded;
  • needsDisplay 这个方法判别CALayer是否需要刷新,只是用来判断,没有别的作用。UIView没有该方法。
- (BOOL)needsDisplay;
  • display 官方建议不要直接调用这个方法,CALayer对象绘制适当的时机调用此方法来绘制CALayer对象其中的内容。
- (void)display;


Auto Layout Process 自动布局过程


那么三种是如何关联起来的呢?主要是通过 Auto Layout Process 来关联在一起的,网上这个资料很多,苹果官方的我没有找到的在哪,所以我找到了最开始的版本 ,具体内容如下所示。

与使用springs and struts(autoresizingMask)比较,Auto layout在view显示之前,多引入了两个步骤:updating constraints 和laying out views。每一个步骤都依赖于上一个。display依赖layout,而layout依赖updating constraints。 updating constraints→layout→display

第一步:updating constraints,被称为测量阶段,其从下向上(from subview to super view),为下一步layout准备信息。可以通过调用方法setNeedUpdateConstraints去触发此步。constraints的改变也会自动的触发此步。但是,当你自定义view的时候,如果一些改变可能会影响到布局的时候,通常需要自己去通知Auto layout,updateConstraintsIfNeeded。

自定义view的话,通常可以重写updateConstraints方法,在其中可以添加view需要的局部的contraints。

第二步:layout,其从上向下(from super view to subview),此步主要应用上一步的信息去设置view的center和bounds。可以通过调用setNeedsLayout去触发此步骤,此方法不会立即应用layout。如果想要系统立即的更新layout,可以调用layoutIfNeeded。另外,自定义view可以重写方法layoutSubViews来在layout的工程中得到更多的定制化效果。

第三步:display,此步时把view渲染到屏幕上,它与你是否使用Auto layout无关,其操作是从上向下(from super view to subview),通过调用setNeedsDisplay触发,

因为每一步都依赖前一步,因此一个display可能会触发layout,当有任何layout没有被处理的时候,同理,layout可能会触发updating constraints,当constraint system更新改变的时候。

需要注意的是,这三步不是单向的,constraint-based layout是一个迭代的过程,layout过程中,可能去改变constraints,有一次触发updating constraints,进行一轮layout过程。示意图如下所示。

原作者也提到了另外的一个坑,那就是如果你每一次调用自定义layoutSubviews都会导致另一个布局传递,那么你将会陷入一个无限循环中。 这其中主要原因还是constraint-based layout是一个迭代的过程,在上图的 updating constraintslayout 中成了一个死循环了。


视图渲染流程


由上一个模块我们可以得知一个View视图的调用顺序为 updating constraints→layout→display,那么对应到具体方法就是 updateConstraints→layoutSubViews→drawRect:

再详细的说一下,那就是,当我们修改View视图约束的时候,会触发 setNeedsUpdateConstraints 方法,然后触发 updateConstraints 方法,随后就紧接着触发 layoutSubViews,同时苹果官方已经为我们暴露了UIViewController中本身View视图的updateConstraints上层方法 updateViewConstraints,当UIViewController中本身View视图setNeedUpdate Constraints被调用的时候,这时候就会在合适的时机自动调用updateViewConstraints方法.

反观UIViewController的生命周期流程,我们可以具体到如下表格顺序.

生命周期执行顺序
init
viewDidLoad
viewWillAppear
updateViewConstraints
viewWillLayoutSubViews
viewDidLayoutSubViews
viewDidAppear
viewWillDisappear
updateViewConstraints
viewDidDisappear
dealloc


触发时机分析


这个模块我们就触发时机再总结一下,其实在上面的基础API的方法中都介绍了,但是比较杂乱,


updateViewConstraints 与 updateConstraints

这两个的触发时机是一致的,那么就是当 调用 needsUpdateConstraints 值为YES 的时候,就肯定会调用updateViewConstraints 或者 updateConstraints.那么在View内部的这个判别布尔值又是由什么决定呢?情况一是添加,修改,删除约束的时机,二是手动调用 setNeedsUpdateConstraints 的时机.这两种时机都会造成布尔值发生改变从而调起 updateViewConstraints 或 updateConstraints .


layoutSubviews

layoutSubviews的触发时机只有一种情况,那就是 自身或者子视图的 bounds 发生了改变. .这也解释了当我们创建一个视图的时候如果使用的CGRectZero的时候实际上不会调用 layoutSubviews 方法.


drawRect 与 drawInContext

这两个方法的调用时机又和 updateViewConstraints 与 updateConstraints 非常的相似,只有当 调用 setNeedsDisplay 才会触发调用.但两者又有很大的区别.drawRect是UIView中的方法,drawInContext是CALayer中方法,drawRect调用时机智能是RunLoop的下一个周期开始,不能立即调用,但是drawInContext却可以通过直接调用displayIfNeeded开直接调用,不用等待RunLoop的下一个周期开始.而且 needsDisplay 只是CALayer中的方法,UIView没有此方法.



总结


OK,写到这里基本上系统的各种约束,布局,绘制API大家都了解的差不多了,这对我们后期代码时机的把握有着很好的帮助,欢迎各位大佬自己手动试验,欢迎大家在评论区指导批评.


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

推荐阅读更多精彩内容