iOS基础--布局

802 阅读5分钟

还是这种基础知识简单轻松😢

布局流程

更新约束、布局、绘制,这三个步骤有十分相近的3个方法,对比着更加容易理解:

// 设置为需要更新约束
setNeedsUpdateConstraints
// 立即更新约束
updateConstraintsIfNeeded
// 自定义更新约束   
updateConstraints
// 设置为需要更新布局
setNeedsLayout
// 立即更新布局
layoutIfNeeded
// 自定义更新子视图的布局
layoutSubviews
// 设置为需要更新绘制
setNeedsDisplay
// 自定义绘制
drawRect:

它们3个都有设置为需要更新、自定义更新这两个方法,除了绘制之外,都有立即更新的方法。之所以这样,跟它们的流程有关。

布局并不是在对约束有变化时就立即更新的,而是在 runloop 循环周期的最后检查是否有约束被标记为需要更新,如果有,就会执行更新操作。

更新约束

1. 更新约束里具体做了什么?

我没有找到权威的文档说明这个问题,而且说到这个问题的文章也都含糊不清。所以以下都是我的猜测:

约束是一个系统,一旦其中有一个变化,会引起一连串的约束可能的变化,我觉得这个步骤单单去处理这些约束的关系,就已经很复杂了:

(1)约束之间有冲突,要做决断

(2)有模糊的约束,要做出假设

(3)通过某些数据结构把约束的关系表示出来,方便接下来布局

我觉得这步骤是不包括生成 frame 的,这样更符合分层,但是这样的问题意义不大。

2. 更新约束过程

(1)setNeedsUpdateConstraints - 约束被标记为需要更新:修改约束时自动会把约束标记为需要更新,或者用户根据场景可以自己标记

(2)runloop 开始检查是否有约束需要更新

(3)Auto Layout Engine 内部更新约束

(4)updateConstraints:并同时在合适的时机给我们机会自己更新约束,通过调用 updateConstraints 方法

如果这个约束的更新影响接下来的代码,也可以提前更新:updateConstraintsIfNeeded。

注意:这个方法只是把更新提前了,但是如果没有约束被标记为更新,还是不会执行更新操作的。

3. 更新约束的过程是从下往上的

这个我也不能说出为什么。虽然很多时候,子视图决定了父视图的约束,但是也会有很多反过来的情况。

布局

1. 更新布局的过程

这个过程是从上往下的,先更新父视图的,再更新子视图的。

(1)setNeedsLayout - 布局被标记为需要更新:约束变化时或者我们直接修改 frame 等时,自动会把布局标记为需要更新,或者用户根据场景可以自己标记

(2)runloop 开始检查是否需要更新布局

(3)layoutSubviews:系统更新布局,并同时在合适的时机给我们机会自己更新布局

有文章列举了什么情况下会调用 layoutSubviews,但我觉得这些不需要去记忆,因为这些都是为我们开发者设计的,我们什么时候想要去调用,苹果就会设计成什么时候去调用:

(1)调用addSubView方法时会触发

(2)设置view的frame时会触发,但是需要frame发生变化

(3)滚动scrollView会触发(创建的View的父视图时scrollView,并且进行滚动时)

(4)改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件

(5)init初始化不会触发layoutSubviews,但是initWithFrame初始化时,rect的值不是zero时也会触发

(6)直接调用[self setNeedsLayout](这个方法不会立即刷新,如果需要立即刷新还需要调用[self layoutIfNeeded])

(7)旋转Screen会触发父UIView上的layoutSubviews事件。

这些事件总结来看就是:

1. 本身的布局发生变化:比如(2)(3)(5)(6)(7),需要说明的是:

(1)[self setNeedsLayout] 本身并不会直接造成调用 layoutSubviews,而是约束确实变化了才会,如果约束只是被标记为需要更新,但是自动布局系统更新完了之后发现,约束没有变化,那当然就不需要更新布局了,当然也就不会调用 layoutSubviews 了

(2)旋转屏幕会不会触发 layoutSubviews 跟上面也是同理,旋转屏幕之所以会触发,是因为父视图布局变了,如果父视图的布局没变,那就不会触发子视图的了

2. 苹果认为你可能也会需要的:比如(1)(4)

(1)当被加入到某个父视图上时,可能会需要做一些修改

(2)当兄弟视图布局变化时,可能会需要做一些修改

绘制

我们的 UI 元素能交给 GPU 的有:颜色,布局,边框,图,我们的控件都是由这些东西组合而成:

(1) label:把字绘制成图,加上背景色等

(2) button:把字绘制成图,加上背景图前景图等

(3) UISwitch:把图形绘制成图

这里面都有个绘制的过程,绘制也就是把矢量图转成图片。

GPU 擅长的是矩阵的处理,因为它有大量的管线,可以同时开工,要求就是不同管线里的数据和代码没有依赖关系。

CPU 更加通用,它计算能力没有 GPU 强悍,但是它能处理依赖关系。

矢量图转成图片,适合用 CPU。所以我们得把这些东西处理好了,再给 GPU 处理。

drawRect:就是给开发者一个机会自定义矢量图,它的调用当然也是在视图的布局发生变化时。

提交给 GPU 显示

交给 GPU 之前,还需要 CPU 解压图片、颜色格式转换、打包数据等。

GPU 收到数据之后,要做顶点处理等一系列的操作。