浅谈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大家都了解的差不多了,这对我们后期代码时机的把握有着很好的帮助,欢迎各位大佬自己手动试验,欢迎大家在评论区指导批评.


你可能感兴趣的:(浅谈Constraints,Layout,Display的点点滴滴)