介绍

[COSLayout][1] 是一个用来快速实现 iOS 视图动态布局的库。和 Auto Layout 一样,它使用基于约束的模型来构建布局。相对于 Auto Layout,COSLayout 具有更高的效率,以及更友好的接口。

COSLayout 是对视图自身布局的抽象,布局由一系列规则组成,规则通过 SLL (Simple Layout Language) 语言来描述。下面是一个简单的例子:

UIView *contentView = [[UIView alloc] init];

COSLayout *contentLayout = [COSLayout layoutOfView:contentView];

[contentLayout addRule:@"tt = 20, ll = bb = rr = 10"];
// ^--------- SLL (Simple Layout Language)
[self.view addSubview:contentView];

以上代码首先为视图 contentView 创建了一个 COSLayout 对象 contentLayout,接着使用 addRule: 方法为这个对象添加了一条规则,该规则包含 4 个约束,它们分别是:

  1. tt,顶部到父视图顶部的距离为 20
  2. ll,左侧至父视图左侧的距离为 10
  3. bb,底部至父视图底部的距离为 10
  4. rr,右侧至父视图右侧的距离为 10

这个例子为视图 contentView 的布局创建了 4 个约束。当父视图需要更新布局时,contentView 也会根据自身布局的约束动态地改变尺寸。

通常不需要手动要求父视图更新布局,因为系统会很好的处理。比如当有子视图添加,自身或子视图的尺寸发生改变时,系统就会更新子视图的布局。也可以手动调用 layoutSubviews 方法要求父视图及时更新布局。

SLL 语言

就像我们所看到的,SLL 的语法很简单,仅由逗号分隔的赋值语句构成。

赋值语句

每个赋值语句指定一个或多个约束。赋值语句中,左值是约束的名称,右值是该约束的值。和 C 语言一样,赋值运算符是右结合的,赋值表达式也是有值的,其值就是它的右值。

算术运算

SLL 支持基本算术运算。SSL 一共有 5 个算术运算符:

算术运算符 优先级 结合性 例子
= 1 tt = 20 ct = 50%
+ 2 10 + 20 50% + 10 %w + 5
- 2 10 - 20 50% - 10 %w - 5
* 3 50 * 2 80% * 0.5 %h * 2
/ 3 100 / 2 100% / 2 %h / 2

另外,还可以通过 () 来创建子表达式,例如 (2 + 3) × 4,来改变表达式的求值顺序。

以上就是 SLL 语言的全部语法,非常简单。

规则和约束

COSLayout 的规则使用 SLL 语言来描述,每个规则都包含若干个约束。在 SLL 的赋值语句中,左值表示约束名。COSLayout 支持以下 14 种约束:

</tr> </tbody> </table> 所有约束可以从方向上分为两类:**水平约束**和**垂直约束**。这样分类是有意义的,因为某些约束值会根据所属约束方向的不同有不同的解释,比如后面马上要介绍的百分数就是这样。判断某个约束是哪个方向非常简单,仅从字面上就可以判断。例如 `ll` 表示左侧至父视图左侧的约束,当然是水平约束;再如 `minh`,它表示最小高度,当然是一个垂直约束。 赋值语句的右值就是约束值,COSLayout 支持 4 种类型的约束值:
约束名 方向 说明
tt</td> 垂直 视图顶部至父视图顶部的约束
tb 垂直 视图顶部至父视图底部的约束
ll 水平 视图左侧至父视图左侧的约束
lr 水平 视图左侧至父视图右侧的约束
bb 垂直 视图底部至父视图底部的约束
bt 垂直 视图底部至父视图顶部的约束
rr 水平 视图右侧至父视图右侧的约束
rl 水平 视图右侧至父视图左侧的约束
ct 垂直 视图中点到父视图顶部的约束
cl 水平 视图中点到父视图左侧的约束
minw 水平 视图的最小宽度的约束
maxw 水平 视图的最大宽度的约束
minh 垂直 视图的最小高度的约束
maxh 垂直 视图的最大高度的约束
约束值 示例 说明
浮点数 5 -10 20.0f 表示固定大小的点 (point)
百分数 5% -10% 20.0% 如果所属约束是水平约束,表示父视图宽度的百分比;
如果所属约束是垂直约束,表示父视图高度的百分比
约束名 tt minw 表示视图自身布局的某个约束的约束值
格式说明符 %tt %w %f 表示外部传入的对象,例如 %tt 表示某个视图的顶部到其父视图顶部的距离
格式说明符用来指定外部传入的对象。下表列出了 COSLayout 支持的 13 种格式化字符串: </tr> </tbody> </table> 前 12 个格式说明符指定其他视图某个方向上的尺寸,它们将在约束被求解时动态计算大小。最后一个格式说明符指定一个浮点数。 ### 依赖关系 COSLayout 在设置约束时,也会同时建立视图之间的依赖关系。依赖关系决定了视图布局的求解顺序。如果视图 `A` 依赖于视图 `B`,那么 `B` 的布局比 `A` 的布局先计算。 在 COSLayout 内部,依赖关系是通过有向无环图(DAG)表示的。所以,**COSLayout 不支持视图之间的相互依赖**。所以下面的例子将会引发异常:
格式说明符 类型 说明
%tt</td> UIView * 视图顶部至父视图顶部的距离
%tb UIView * 视图顶部至父视图底部的距离
%ll UIView * 视图左侧至父视图左侧的距离
%lr UIView * 视图左侧至父视图右侧的距离
%bb UIView * 视图底部至父视图底部的距离
%bt UIView * 视图底部至父视图顶部的距离
%rr UIView * 视图右侧至父视图右侧的距离
%rl UIView * 视图右侧至父视图左侧的距离
%ct UIView * 视图中点到父视图顶部的距离
%cl UIView * 视图中点到父视图左侧的距离
%w UIView * 视图的宽度
%h UIView * 视图的高度
%f float 浮点数,固定点数
1
2
3
4
5
6
7
8
9
10
11
12
UIView *view1 = [[UIView alloc] init];
UIView *view2 = [[UIView alloc] init];

COSLayout *layout1 = [COSLayout layoutOfView:view1];
COSLayout *layout2 = [COSLayout layoutOfView:view2];

[layout1 addRule:@"rl = %ll", view2];
[layout2 addRule:@"ll = %rl", view1]; // Error

[layout1 addRule:@"rl = %ll + 50", view1]; // Error

[self.view addSubview:contentView];
以上代码有两处地方会引发循环依赖异常。在第 7 行,`view1` 的布局规则中引用了 `view2`,因此,`view1` 依赖于 `view2`。而在第 8 行,`view2` 的布局规则中反过来引用了 `view1`,这样,`view2` 依赖于 `view1`。就出现了依赖循环。在第 10 行,`view1` 的布局规则中引用了 `view1` 自身,也会导致依赖循环。 通过依赖关系来决定布局计算顺序的决定是基于实现的复杂度和性能考虑的。正是由于这种轻量的实现,它的效率会比 Auto Layout 高,因为它不会对多个约束进行多项式求解,仅仅是拓扑排序的复杂度。 尽管存在这种限制,COSLayout 足以应付绝大多数布局问题。下一节将介绍 COSLayout 的综合运用。 ## 综合运用 在现实情况中,通常会遇到子视图右侧距离父视图右侧固定距离的情况,以下代码指定视图 `view` 右侧距离其父视图右侧的距离为 10。
UIView *view = [[UIView alloc] init];

COSLayout *layout = [COSLayout layoutOfView:view];

[layout addRule:@"rr = 10"];
再比如,经常会遇到一个视图的顶部距离前一个视图的顶部固定距离,这时可以这样:
UIView *view1 = [[UIView alloc] init];
UIView *view2 = [[UIView alloc] init];

COSLayout *layout = [COSLayout layoutOfView:view1];

[layout addRule:@"tt = %bt + 10", view2];
最后举一个稍微复杂的例子,这个例子将 `view1` 左侧、底部、和右侧的距离设置为 10,顶部距离 `view2` 的底部距离为 10。
UIView *view1 = [[UIView alloc] init];
UIView *view2 = [[UIView alloc] init];

COSLayout *layout = [COSLayout layoutOfView:view1];

[layout addRule:@"ll = bb = rr = %f, tt = %bt + 10", 10.0f, view2];
## 总结 COSLayout 是一个针对 iOS 视图布局的轻量实现。它不是 Auto Layout 的替代品,因为它不处理约束之间相互依赖的情况,除此之外,它可以轻松地处理几乎所有布局。 [1]: https://github.com/tang3w/CocoaSugar