Auto Layout前世今生
Auto Layout ,是苹果公司提供的一个基于约束布局,动态计算视图大小和位置的库,并且已经集成到了 Xcode 开发环境里。
在引入 Auto Layout 这种自动布局方式之前,iOS 开发都是采用手动布局的方式。而手动布局的方式,原始落后、界面开发维护效率低,对从事过前端开发的人来说更是难以适应。所以,苹果需要提供更好的界面引擎来提升开发者的体验,Auto Layout 随之出现。但是在推出来之后,响应却平平,其原因就在于对其性能的担忧。即使后来,苹果公司推出了在 Auto Layout 基础上模仿前端 Flexbox 布局思路的 UIStackView 工具,提高了开发体验和效率,也无法解除开发者们对其性能的顾虑。
iOS 12 将大幅提高 Auto Layout 性能,使滑动达到满帧。这不得不说Cassowary 算法。
Cassowary布局算法
Cassowary 能够有效解析线性等式系统和线性不等式系统,用来表示用户界面中那些相等关系和不等关系。基于此,Cassowary 开发了一种规则系统,通过约束来描视图间的关系。约束就是规则,这个规则能够表示出一个视图相对于另一个视图的位置。由于 Cassowary 算法让视图位置可以按照一种简单的布局思路来写,这些简单的相对位置描述可以在运行时动态地计算出视图具体的位置。视图位置的写法简化了,界面相关代码也就更易于维于维护。苹果公司也是看重了这一点,将其引入到了自己的系统中。由于 Cassowary 算法本身的先进性,更多的开发者将Cassowary 运用到了各个开发语言中,比如 JavaScript、.NET、Java、Smalltalk、C++都有对应的库。
Auto Layout 的生命周期
Auto Layout 不只有布局算法 Cassowary,还包含了布局在运行时的生命周期等一整套布局引擎系统,用来统一管理布局的创建、更新和销毁。了解 Auto Layout 的生命周期,是理解它的性能相关话题的基础。
这一整套布局引擎系统叫作 Layout Engine ,是 Auto Layout 的核心,主导着整个界面布局。
每个视图在得到自己的布局之前,Layout Engine 会会将视图、约束、优先级、固定大小通过计算转换成最终的大小和位置。在 Layout Engine 里,每当约束发生变化,就会触发 Deffered Layout Pass,完成后进入监听约束变化的状态。当再次监听到约束变化,即进入下一轮循环中。整个过程如下图所示:
图中, Constraints Change 表示的就是约束变化,添加、删除视图时会触发约束变化。Activating 或 Deactivating,设置 Constant 或 Priority 时也会触发约束变化。Layout Engine 在碰到约束变化后会重新计算布局,获取到布局后调用 superview.setNeedLayout(),然后进入 Deferred Layout Pass。
Deferred Layout Pass 的主要作用是做容错错处理。如果有些视图在更新约束时没有确定或缺失布局声明的话,会先在这里做容错处理。
接下来,Layout Engine 会从上到下调用layoutSubviews() ,通过 Cassowary算法计算各个子视图的位置,算出来后将子视图的 frame 从Layout Engine 里拷贝出来。
在这之后的处理,就和手写布局的绘制、渲染过程一样了。所以,使用 Auto Layout 和手写布局的区别,就是多了布局上的这个计算过程。
Auto Layout 性能问题
Auto Layout 在 iOS 12 中优化后,它的性能得到了极大的提升,已经基本和手写布局一样可以达到性能随着视图嵌套的数量呈线性增长了。而在此之前的 Auto Layout,视图嵌套的数量对对性能的影响是呈指数级增长的。使用 Auto Layout 一定要注意多使用 Compression Resistance Priority 和 Hugging Priority,利用优先级的设置,让布局更加灵活,代码更少,更易于维护。
AutoLayout常见的问题
(1)几个更新方法的区别
- setNeedsLayout:告知页面需要更新,但是不会立刻开始更新。执行后会立刻调用layoutSubviews。
- layoutIfNeeded:如果有需要刷新的标记,立即调用layoutSubviews进行布局;如果没有标记,不会调用layoutSubviews。如果希望立刻生成新的frame需要调用此方法,利用这点一般布局动画可以在更新布局后直接使用这个方法让动画生效。
- layoutSubviews:对subviews进行布局,不能主动调用,需要的时候在子类重写,系统会在合适的时候自动调用。
- 注意 : 如果要立即刷新frame,要先调用setNeedsLayout(),把标记设为需要布局,然后马上调用layoutIfNeeded(),实现布局。
- setNeedsUpdateConstraints:告知需要更新约束,但是不会立刻开始
- updateConstraintsIfNeeded:告知立刻更新约束
- updateConstraints:系统更新约束
(2)系统调用layoutSubviews的时机
- init初始化不会触发layoutSubviews,但是使用initWithFrame进行初始化且rect不为zero时,会调用layoutSubviews。
- addSubview的时候会触发系统调用layoutSubviews。
- 当view的frame发生改变的时候触发layoutSubviews。
- 滚动一个UIScrollView会触发layoutSubviews。
- 旋转Screen会触发父UIView上的layoutSubviews事件。
- 改变一个UIView大小的时候也会调用父UIView上的layoutSubviews事件。
具体细节可以参考:http://www.code4app.com/blog-822415-3151.html
(3)什么时候使用frame布局,什么时候选用Auto Layout布局
简单的 UI 使用 Auto Layout ,复杂的 UI 使用 frame。原因如下:
- 从代码量上来看,两种布局方式相差不大。有时候发现复杂的 UI 使用 Auto Layout 的话,代码量反而会变多,因为复杂的 UI 往往会有复杂的逻辑,比如根据数据的不同,部分 UI 的显示会有变动(比如某个子视图隐藏与显示, 会影响到其它视图的布局)。
- 固定的UI简单的布局,这种情况下使用 Auto Layout 还是挺方便的,具有快速、方便、简洁的布局效果。
- 动态复杂的 UI 布局,这种情况下使用 Auto Layout 来布局,感觉就不合适。因为不管是 frame 还是 Auto Layout,都需要去计算高度,Auto Layout通过 Cassowary 算法计算各个子视图的位置,算出来后将子视图的 frame 从 Layout Engine 里拷贝出来;而frame布局,则可以快速的通过事先约定的布局计算出相应的frame,再进行相应的绘制、渲染。这种情况下,直接使用 frame 会比较精简。