IOS布局浅析
理解Update Cycle摘自《[译] 揭秘 iOS 布局》
UPdate Cycle是当应用完成了你所有的事件处理代码之后回到主RunLoop时的时间点。正是在这个时间点上开始更新布局、显示和设置约束。如果你在处理事件的代码中请求修改了一个view,那么系统会把这个view标记为重画。接下来的Update cycle中,系统就会执行view的更改。用户交互和布局更新的延迟几乎不会被用户察觉到,IOS一般以60fps的速度展示动画,也就是一个周期大概是1/60s。这个更新过程很快,用户也很难察觉到。所以用户察觉不到UI中更新的延迟,但是由于处理事件和对应view重画中间存在着间隔,RunLoop中某时刻的view更新可能不是你想要的那样。如果你的代码中的某些设计依赖于当下的view内容或者布局,那么就有在过时view信息上操作的风险。
意思就是当我们代码更新view的布局的时候,界面上的UI并不会马上更新,而是会在下一次Update Cycle的时候更新。
layoutSubviews()
这个方法的作用其实就是通知当前的view你的布局发生变化了,你要重新布局你的subviews,该方法不要手动调用,系统会在需要调用的时候自动调用。如下几种情况会触发 layoutSubviews()
- 修改view的大小
- 新增subview
- 用户在UIScrollView上滚动
- 旋转屏幕
- 更新视图的约束
setNeedsLayout()
顾名思义需要设置布局,就是告诉系统这个视图需要更新布局,这个方法会立即返回,但是view会在下一次Update cycle中更新,调用视图们的layoutSubviews()
。
layoutIfNeeded()
layoutIfNeeded()
不一定会触发view的layoutSubviews
。系统会检测layoutSubviews的触发条件,如果符合条件,那么会立即触发layoutSubviews()
,不会像setNeedsLayout()
等待下一次Update cycle。如果不符合layoutSubviews
的触发条件则不会触发。
这里可以解释自动布局在开始执行动画的时候为什么必须执行setNeedsLayout()
,如果不添加setNeedsLayout()
不会执行动画。
如下代码的结果是2s之后无动画。
self.viewOne = [[TestViewOne alloc] initWithFrame:CGRectZero];
self.viewOne.backgroundColor = UIColor.redColor;
[self.view addSubview:self.viewOne];
[self.viewOne mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.mas_equalTo(self.view);
make.width.mas_equalTo(100);
make.height.mas_equalTo(100);
}];
[UIView animateWithDuration:2 animations:^{
[self.viewOne mas_remakeConstraints:^(MASConstraintMaker *make) {
make.center.mas_equalTo(self.view);
make.width.mas_equalTo(50);
make.height.mas_equalTo(50);
}];
NSLog(@"动画打印viewOne的frame%@",NSStringFromCGRect(self.viewOne.frame));
}];
//打印结果
2019-03-13 17:44:27.115060+0800 layoutStudy[16692:523774] 动画打印viewOne的frame{{182, 423}, {100, 100}}
如果添加进去layoutIfNeeded()
;
self.viewOne = [[TestViewOne alloc] initWithFrame:CGRectZero];
self.viewOne.backgroundColor = UIColor.redColor;
[self.view addSubview:self.viewOne];
[self.viewOne mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.mas_equalTo(self.view);
make.width.mas_equalTo(100);
make.height.mas_equalTo(100);
}];
[UIView animateWithDuration:2 animations:^{
[self.viewOne mas_remakeConstraints:^(MASConstraintMaker *make) {
make.center.mas_equalTo(self.view);
make.width.mas_equalTo(50);
make.height.mas_equalTo(50);
}];
[self.viewOne layoutIfNeeded];
NSLog(@"动画打印viewOne的frame%@",NSStringFromCGRect(self.viewOne.frame));
}];
//打印结果
2019-03-13 17:44:27.115060+0800 layoutStudy[16692:523774] 动画打印viewOne的frame{{182, 423}, {50, 50}}
其实自动布局也是通过一套引擎转换成frame,但是这个过程并不是马上进行的会在下一次Update Cycle执行,调用layoutIfNeeded
会强制计算frame绘制完毕会调用layoutSubviews
,并且被animation的block截获。可以明显的看出代码1中self.viewOne的frame没有变,但是代码2的frame被提前绘制出来了,所以代码2有动画,代码1没有动画。
总结
那么我们的布局代码应该怎么写呢,我觉得不改变的静态布局直接在init的时候写就可以,把与自身视图尺寸有依赖的约束写到里面,由于layoutSubviews可能会多次调用。不要在其中写比较耗时的操作和初始化对象。