正常情况写布局的两种方式
一、手写布局
就是直接设置view的frame属性,不详细说明了
二、自动布局
自动布局Auto Layout,苹果为我们提供了一整套布局系统(layout Engine),这套系统会将视图、约束、优先级、大小通过计算转换成对应的frame,而且当约束改变的时候,会再次触发该系统重新计算。整个过程如下图:(图片出自WWDC2015 地址)
image.png- Constraints Change
激活,失活
创建约束,优先级
添加,移除视图
检测到改变后系统(Layout Engine) 会重新计算出布局,就会调用superview.setNeedsLayout()
- Deferred Layout Pass
容错处理
从上往下调用layoutSubviews()
从 Layout Engine 拷贝出子视图 frame
到此,系统就计算好frame了,接下来就是渲染了,和手写布局是一样的
- Application Run Loop
Layout Engine 通过系统Run Loop 循环cycle
整个流程可以理解为,当修改约束的时候,会触发Layout Engine去计算出view的frame,然后从上而下布局,最后在下一个运行循环中更新界面
整个布局流程可以分为三个阶段:
计算frame,布局,渲染
1.计算阶段
- (void)updateConstraints;
用xib或者NSLayoutConstraint自动布局都会调用该方法,是通过手布局(创建view,设置frame)不会调用此方法
补充:基于约束的布局是懒加载触发的,所以只有设置了约束系统才会调用updateConstraints,如果把基于frame的布局写到updateConstraints,系统是不知道你的布局方式,通过重写下面这个方法,返回YES,系统就会调用updateConstraints
+ (BOOL)requiresConstraintBasedLayout{
return YES;
}
布局可以写在updateConstraints中,并且必须调用 [super updateViewConstraints]
苹果不建议把初始化的约束写在这个方法里,原因如下:
(1) 当视图约束被更新的时候(一般是被setNeedsUpdateConstraints标记更新) updateConstraints这个方法会被调用,如果里面包含大量的约束,系统就需要去判断是否已经存在相同的约束,
(2) 当前view不一定拥有所有的约束,其他view可能已经向该view添加了部分约束
(3) 如果在点击事件中触发修改约束的行为,修改布局的代码和触发更新的代码不再同一处,这会让逻辑变得难以遵循
仅需要更新约束的这部分代码写到updateViewConstraints,大量的初始化约束写到类似于 init,viewDidLoad中.
- (void)updateViewConstraints{
[self.subView mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view).offset(100);
make.left.equalTo(self.view).offset(0);
make.right.equalTo(self.view).offset(-100);
make.bottom.equalTo(self.view).offset(-100);
}];
[super updateViewConstraints];
}
此外还会通过以下方式触发系统调用: updateConstraints
- setNeedsUpdateConstraints 标记更新约束,会在下次 Cycle中自动调用updateConstraints,
[self.subView setNeedsUpdateConstraints];
ViewDemo[19068:895608] -[subView updateConstraints]
- updateConstraintsIfNeeded 如果有被setNeedsUpdateConstraints标记的更新,立即在当前Cycle调用updateConstraints
[self.subView updateConstraintsIfNeeded];
2.布局阶段
- (void)layoutSubviews;
此方法由系统调用,被调用时,系统已经计算好view对应的frame,如果需要修改布局,通过重写这个方法,并在方法体里修改frame,但是在方法里需要注意
(1) 在方法里必须调用 super.layoutSubviews()
(2) 不能在方法里修改约束,修改约束会触发系统重新计算布局,可能会导致布局错乱
(3) 不能在方法里调用setNeedsUpdateConstraints(),setNeedsLayout如,可能会导致布局错乱
除此之外,还会通过以下方式触发:
- 第一次addSubview的时候,改变frame的时候(view已经addSubview 且两次变化值不能一样)
UIView *subView = [[UIView alloc] init];
[self.view addSubview:subView];
- setNeedsLayout,标记更新布局,调用此方法标记,会在下一次runloop来到layoutSubviews,上文的 Layout Engine 也会在检测到约束变化后,通过super. setNeedsLayout()标记,最后从上往下调用layoutSubviews()
[self.subView setNeedsLayout];
- layoutIfNeeded,如果有标记的更新,立即在当前Drawing Cycle调用layoutSubviews更新视图
[self.subView layoutIfNeeded];
3.渲染阶段
- (void)drawRect:(CGRect)rect;
当视图在屏幕上出现的时候 -drawRect:方法就会被自动调用。-drawRect:方法里面的代码利用Core Graphics去绘制一个寄宿图,然后内容就会被缓存起来直到它需要被更新
- (void)setNeedsDisplay,标记需要显示,在下个drawing cycle 调用
在UIViewController中
- (void)updateViewConstraints;
控制器的view.updateConstraints()方法调用时,对应控制器的updateViewConstraints就会被调用,控制器view的子view约束改变是不会触发的.
- (void)viewWillLayoutSubviews
当视图控制器的视图的边界发生变化时,该视图将调整其子视图的位置,然后系统调用此方法。但是,调用此方法并不表示该视图的子视图的各个布局已调整。每个子视图负责调整其自己的布局。
视图布局子视图后,视图控制器可以重写此方法以进行更改。此方法的默认实现不执行任何操作。
- (void)viewDidLayoutSubviews
当视图控制器的视图的边界发生变化时,视图将调整其子视图的位置,然后系统调用此方法。但是,调用此方法并不表示该视图的子视图的各个布局已调整。每个子视图负责调整其自己的布局。
视图布局子视图后,视图控制器可以重写此方法以进行更改。此方法的默认实现不执行任何操作。
三个方法在控制器view的子view约束改变时是不会触发的.
在viewController中这三个方法是系统为我们提供的便利,方便我们在控制器自带的view发生变化的时候做相应的操作:
updateViewConstraints->viewWillLayoutSubviews->viewDidLayoutSubviews
总结一下:
在同一代码处即有自动布局又有手动布局
- 从以上分析得出,自动布局会触发Layout Engine 去计算view的frame,最后重新赋值给view,所以手写的布局(直接设置frame)一般是没有任何效果的;
获取,修改view.frame
- 自动布局系统在计算出view的frame以后,会自上而下(父视图-子视图)调用layoutSubviews,要获取期望的frame,因该在layoutSubviews里获取,修改frame
translatesAutoresizingMaskIntoConstraints
- 设置为YES,系统会把autoresizingMask 转换为 Constraints,通常我们给代码添加自动布局,记得把该属性设置为NO,这样会避免出现布局不一致
在xib中,如果设置了auto layout 该属性默认是NO
在手写代码中,该属性默认是YES
-
初始化约束,尽量写在init,viewDidLoad中,修改.删除约束,可以直接写在对应的事件中,如果对性能有考虑,写在updateConstraints()以获得最佳性能。
-
不要手动调用layoutSubviews, updateConstraints,这些方法系统会自己触发,若要更新约束,布局,通过setNeedsUpdateConstraints,setNeedsLayout标记更新
网友评论