- (void)updateViewConstraints NS_AVAILABLE_IOS(6_0);
UIViewController中也新增了一个更新布局约束的方法,在AutoLayoutUIView相关API的笔记中,详细讲述了UIView的一组更新布局约束的方法。
这个方法默认的实现是调用对应View的
-updateConstraints 。ViewController的View在更新视图布局时,会先调用ViewController的updateViewConstraints 方法。我们可以通过重写这个方法去更新当前View的内部布局,而不用再继承这个View去重写-updateConstraints方法。我们在重写这个方法时,务必要调用 super 或者 调用当前View的-updateConstraints 方法。
通过代码为xib或sb中view增加约束时,尽量避免在viewDidLoad中执行,最好放在updateViewConstraints [UIViewController]或者updateConstraints[UIView]中,记得调用[super updateViewConstraints]或者[super updateConstraints];
- (void)updateViewConstraints
{
// 在这里为你的view添加约束,请确保该view的translatesAutoresizingMaskIntoConstraints属性已设置为NO
[super updateViewConstraints];
}
如果你真的写在viewDidLoad里了,那么可能会遇到这种崩溃错误。
Terminating app due to uncaught exception"NSInternalInconsistencyException"
二、layoutSubviews在以下情况下会被调用:
1、init初始化不会触发layoutSubviews
但是是用initWithFrame 进行初始化时,当rect的值不为CGRectZero时,也会触发
2、addSubview会触发layoutSubviews
3、设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化
4、滚动一个UIScrollView会触发layoutSubviews
5、旋转Screen会触发父UIView上的layoutSubviews事件
6、改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件
layoutSubviews, 当我们在某个类的内部调整子视图位置时,需要调用。 反过来的意思就是说:如果你想要在外部设置subviews的位置,就不要重写。
三、刷新子对象布局
-layoutSubviews方法:这个方法,默认没有做任何事情,需要子类进行重写
-setNeedsLayout方法:标记为需要重新布局,异步调用layoutIfNeeded刷新布局,不立即刷新,但layoutSubviews一定会被调用
-layoutIfNeeded方法:如果,有需要刷新的标记,立即调用layoutSubviews进行布局(如果没有标记,不会调用layoutSubviews)。
如果要立即刷新,要先调用[view setNeedsLayout],把标记设为需要布局,然后马上调用[view layoutIfNeeded],实现布局
四、重绘
-
drawRect:(CGRect)rect方法:重写此方法,执行重绘任务 ;
-setNeedsDisplay方法:标记为需要重绘,异步掉用drawRect;
-setNeedsDisplayInRect:(CGRect)invalidRect方法:标记为需要局部重绘;
sizeToFit会自动调用sizeThatFits方法;
sizeToFit不应该在子类中被重写,应该重写sizeThatFits;
sizeThatFits传入的参数是receiver当前的size,返回一个适合的size;
sizeToFit可以被手动直接调用;
sizeToFit和sizeThatFits方法都没有递归,对subviews也不负责,只负责自己。
layoutSubviews对subviews重新布局;
layoutSubviews方法调用先于drawRect;
setNeedsLayout在receiver标上一个需要被重新布局的标记,在系统runloop的下一个周期自动调用layoutSubviews
layoutIfNeeded方法如其名,UIKit会判断该receiver是否需要layout.根据Apple官方文档,layoutIfNeeded方法应该是这样的。
layoutIfNeeded遍历的不是superview链,应该是subviews链;
drawRect是对receiver的重绘,能获得context。
setNeedDisplay在receiver标上一个需要被重新绘图的标记,在下一个draw周期自动重绘,iphone device的刷新频率是60hz,也就是1/60秒后重绘。
五、setNeedsUpdateConstraints,needsUpdateConstraints等基于约束的AutoLayer的方法
1、setNeedsUpdateConstraints
当一个自定义view的某个属性发生改变,并且可能影响到constraint时,需要调用此方法去标记constraints需要在未来的某个点更新,系统然后调用updateConstraints.
2、needsUpdateConstraints
constraint-based layout system使用此返回值去决定是否需要调用updateConstraints作为正常布局过程的一部分。
3、updateConstraintsIfNeeded
立即触发约束更新,自动更新布局。
4、updateConstraints
自定义view应该重写此方法在其中建立constraints.注意:要在实现在最后调用[super updateConstraints]
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,被称为测量阶段,其从下向上(fromsubview to super view),为下一步layout准备信息。可以通过调用方法setNeedUpdateConstraints去触发此步。constraints的改变也会自动的触发此步。但是,当你自定义view的时候,如果一些改变可能会影响到布局的时候,通常需要自己去通知Autolayout,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 tosubview),通过调用setNeedsDisplay触发,
因为每一步都依赖前一步,因此一个display可能会触发layout,当有任何layout没有被处理的时候,同理,layout可能会触发updating constraints,当constraint system更新改变的时候。
需要注意的是,这三步不是单向的,constraint-based layout是一个迭代的过程,layout过程中,可能去改变constraints,有一次触发updating constraints,进行一轮layout过程。
注意:如果你每一次调用自定义layoutSubviews都会导致另一个布局传递,那么你将会陷入一个无限循环中。
六、AutoLayout相关的几个易混淆的方法
l setNeedsLayout
l layoutIfNeeded
l layoutSubViews
l setNeedsUpdateConstraints
l updateConstraitsIfNeed
l updateConstraints
子视图在界面上的显示大概经过了:更新约束-通过约束依赖关系得到具体的frame-展示到界面。上面几个是和autolayout相关的方法,有必要大概了解一下这些方法具体是怎么用的以及在什么情况下触发。
1.[layoutView setNeedsUpdateConstraints]:告诉layoutView需要更新约束,在下次计算或者更新约束会更新约束
2.[layoutView updateConstraintsIfNeeded]:告诉layoutView立即更新约束,
3.updateConstraints:系统更新约束的实际方法
总结上面的3点就是,setNeedsUpdateConstraints
确保了在将来某一时刻调用updateConstraintsIfNeeded
之后会接着调用updateConstraints
,从而达到更新view的约束的目的。但是要注意的是,如果仅仅单独调用2,不一定能够保证会调用updateConstraints
,因为如果view上的约束是没有变动的且没有标记需要update的,这时就不会调用updateConstraints
。
4.[layoutView setNeedsLayout]:告诉layoutView页面需要更新,但不立即执行
5.[layoutView layoutIfNeeded]:告诉layoutView页面布局立即更新
6.layoutSubviews:系统重写布局的实际方法
总结以上3点,setNeedsLayout
确保了在将来某个时刻通过调用layoutIfNeeded
之后会调用系统的layoutSubviews
,从而重写对view重新布局。同样的如果单独调用5,不一定能够保证调用layoutSubviews
。[注:笔者写了个demo发现,调用setNeedsLayout
会直接调用layoutSubviews
]。如果想要每次都能立即更新布局,那就要把两个方法一起用,同样也适用于1和2。
系统调用layoutSubViews
时,就会调用updateConstraintsIfNeeded
,通过更新约束,用superView到subView的层次顺序,来计算frame,反向确定布局。
stackoverflow上有关于上面几个方法的深入解答并分享了作者的实用经验:
- 如果仅想要立即改变约束,调用
setNeedsLayout
- 如果改变view的一些属性(如
offsets
)可能会导致布局的改变,那么调用setNeedsUpdateConstraints
,更多的时候后面需要加setNeedsLayout
。 - 如果想要立即改变布局,如会形成新的frame,那么需要在调用
layoutIfNeeded
。