美文网首页布局 autolayout收藏ios
Auto Layout 中的 setNeedsUpdateCon

Auto Layout 中的 setNeedsUpdateCon

作者: WellCheng | 来源:发表于2017-06-04 22:23 被阅读801次

    先抛出结论:

    setNeedsUpdateConstraints 保证之后肯定会调用 updateConstraintsIfNeeded .

    SetNeedsLayout 保证之后肯定会调用 layoutIfNeeded .

    AutoLayout 的本质

    AutoLayout 是指,用一套规则(约束)来定义视图之间的位置。

    AutoLayout 能够让每个 view 有唯一的 frame。

    其实,这样子解释,还是让人很难理解,所以接下来会简单介绍下 AutoLayout ,在对其有所了解和深入后,再解释下面这几个问题:

    • 保证之后调用 的之后是在什么时候?
    • 这些方法的调用时序大概是怎么样的?
    • 为什么要先 set 一下,而不是直接 updateConstraint 和 layout

    UIView 生命周期

    
    init=>start: InitWithFrame
    
    layout=>operation: setNeedsDisplay: 
    setNeedsUpdateConstraint:  
    setNeedsDisplay:
    
    ifUpdateCons=>condition: update constraints ?
    updateCons=>operation: updateConstraints
    ifLayout=>condition: update layout ?
    layoutSubview=>operation: layoutSubviews
    ifDisplay=>condition: needs display ?
    draw=>operation: Draw in Rect
    e=>end: Event Loop
    
    init->layout->ifUpdateCons
    ifUpdateCons(yes)->updateCons->ifLayout
    ifUpdateCons(no)->ifLayout
    ifLayout(yes)->layoutSubview->ifDisplay
    ifLayout(no)->ifDisplay
    ifDisplay(yes)->draw(right)->e(right)
    ifDisplay(no)->e(right)
    e->ifUpdateCons
    

    在 iOS 中,AutoLayout Engine 是一个迭代机。

    为什么这样说呢?先回到手写布局时代,我们通过计算 view 与屏幕之间的相对距离,得出 view 实际的 frame,然后赋值给 view,这样子每个 view 都会按照我们设置的位置正确地显示。

    接着我们来到当下的 iOS 开发,先列出几个问题:

    • 需要适配多种屏幕
    • 既可以在 iPhone 上使用,又可以在 iPad 上使用
    • 可以在横屏下使用
    • 在不同的屏幕尺寸下,有不同的布局方式(比如屏幕小,就一行放一个,屏幕大一行放两个)

    如果仍然在原始的手写布局下去完成上述工作,势必累死,也不一定能够很好的完成。

    搞个机器人

    人类能够发展到现在的文明,就是因为 善假于物也

    我希望现在有一套东西,我只需要告诉它,我希望视图表现成什么样子,然后它就会按照我的期望去计算出每个 view 的 frame,我只需要把 frame 拿来用就行了。

    AutoLayout 就是这样的一套东西,它接收视图与视图之间的规则,生成最终的 frame。

    这里有个误区,AutoLayout 的确是最终生成了 frame,不过生成之后自动给 view 赋值上去了,所以我们没有看到 setFrame 这个过程。

    这个规则就是约束 constraint。

    为什么需要 update constraint

    在 UIView 显示之前,先判断 view 是否能根据当前约束计算出唯一的 Frame。如果可以,那么就根据这个 Frame 去布局。同时,在这里我们认为 view 是满足约束的。

    严谨一些,是指在 AutoLayout Engine 中,该 view 的 constraint 是否为最新。

    AutoLayout Engine 是一个单独的约束处理系统,在绝大多数操作中,比如:

    • Activating或Deactivating 启用和停用
    • 设置constant或priority
    • 添加和删除视图

    AutoLayout Engine 本身都会标记 view 的约束不是最新,即调用 setNeedsUpdateConstraint

    但是也有例外,AutoLayout Engine 并不知道你又修改了约束。因此在这种 case 下,需要手动调用 setNeedsUpdateConstraint 来标记约束需要更新。

    setNeedsUpdateConstraint

    setNeedsUpdateConstraint 控制 view 的约束是否需要更新。当一个自定义view的某个属性发生改变,并且可能影响到constraint时,需要调用此方法去标记constraints需要在未来的某个点更新,系统然后会调用 updateConstraints,. 以解决这个由属性改变带来的影响。

    updateConstraintsIfNeeded

    updateConstraintsIfNeeded立即触发约束更新,自动更新布局。

    updateConstraints

    当 Custom View 发现属性或者其他的改变导致它的所有约束中有一个失效时,首先应该删除这个失效的约束,然后调用 setNeedsUpdateConstraints 表示当前的约束需要更新,然后在 updateConstraints 中恰当地地方检查当前 content 所需的必要约束。

    注意:要在实现在最后调用[super updateConstraints]

    layoutSubviews

    在确定了 view 的约束后,AutoLayout 通过计算可以得出 view 的 frame,计算的过程就是 layout 的过程。

    Layout 的顺序,是由最外层向里递进,所以子视图只需要相对于父视图做好布局就可以。

    在调用 layoutSubviews 的同时,也会调用 setNeedsUpdateConstraint。

    Auto Layout Process 自动布局过程(引用自Objccn.io)

    与使用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都会导致另一个布局传递,那么你将会陷入一个无限循环中。

    就是说,layout 和 updateConstraints 不断迭代最终确立了整个布局和显示,然后交给屏幕去显示

    实践出真知

    layoutIfNeeded 调用导致 Crash

    在调用 layoutIfNeeded 时,view 必须要被 setNeedsLayout 后,才会理解执行 layoutSubviews。

    一个视图缺少高宽约束,在设置完了约束后执行layoutIfNeeded,然后设置宽高,这种情况在低配机器上可能会出现崩问题。原因在于layoutIfNeeded需要有标记才会立刻调用layoutSubview得到宽高,不然是不会马上调用的。页面第一次显示是会自动标记上需要刷新这个标记的,所以第一次看显示都是看不出问题的,但页面再次调用layoutIfNeeded时是不会立刻执行layoutSubview的(但之前加上setNeedsLayout就会立刻执行),这时改变的宽高值会在上文生命周期中提到的Auto Layout Cycle中的Engine里的Deferred Layout Pass里执行layoutSubview,手动设置的layoutIfNeeded也会执行一遍layoutSubview,但是这个如果发生在Deferred Layout Pass之后就会出现崩的问题,因为当视图设置为setTranslatesAutoresizingMaskIntoConstraints:NO时会严格按照约束->Engine->显示这种流程,如在Deferred Layout Pass之前设置好是没有问题的,之后强制执行LayoutSubview会产生一个权重和先前一样的约束在类似动画block里更新布局让Engine执行导致Ambiguous Layouts这种权重相同冲突崩溃的情况发生。

    RemoveFromSuperView

    将多个有相互约束关系视图removeFromSuperView后更新布局在低配机器上出现崩的问题。这个原因主要是根据不含视图项的约束不合法这个原则来的,同时会抛出野指针的错误。在内存吃紧机器上,当应用占内存较多系统会抓住任何可以释放heap区内存的机会视图被移除后会立刻被清空,这时约束如果还没有被释就满足不含视图项的约束会崩的情况了。

    remove 之前最好能够 clearConstraint。

    我的一些疑问

    第一步:updating constraints,被称为测量阶段,其从下向上(from subview to super view),为下一步layout准备信息。

    始终不明白为什么要从下往上更新约束,我的理解是,先是父视图确定自己位置,子视图才确认自己视图。当然这个是 layout 的过程。

    我当前的理解是这样,子视图的约束先更新,再逐步向上触发父视图更新约束,猜测的原因是子视图的约束可能会导致父视图约束更改。

    参考引用

    相关文章

      网友评论

        本文标题:Auto Layout 中的 setNeedsUpdateCon

        本文链接:https://www.haomeiwen.com/subject/nvsbfxtx.html