一篇不算晚的Auto Layout入门教程

自打我混iOS圈以来,写UI就使用的是frame绝对布局。说是「绝对」,但在写的时候也已带着动态的思想了。比如,尽可能地用 autoResizingMask 。但是对于那种mask不能用的场景,在写布局时就像在做小学几何题,很是复杂。

在近期的项目中,尝试了Auto Layout,试着把自己的心得总结一下。

Auto Layout 简介

网上介绍Auto Layout的文章很多,有一点大家很少提到。就Auto Layout本身来说,它并不是什么新鲜的技术。Auto Layou系统是 Cassowary 算法的Cocoa实现。Cassowary是一个在二十世纪九十年代被发明,解析 线性 等式、不等式约束的一个算法。

开发者提供一系列的 布局规则 给Cocoa Auto Layout系统,它基于Cassowary算法,把规则转换成了View(s)的frame,完成了布局。这个所谓的「布局规则」,就是Auto Layout里的「约束」,Constraint。

前面说到,Cassowary算法解析的线性等式、不等式,因此,我们提供给Auto Layout的约束是线性的约束。可以简单把线性约束理解为用一次方程来描述的约束。

一次方程,它的基本形式(以等式为例)

y = kx + b

自然地,在Auto Layout中,一个约束该长什么样子:

view1的某个属性 = k * view2的某个属性 + b

如上面所示,一个约束描述了两个属性之间的关系,涉及到了7个量(view1、属性1、关系、view2、属性2、k、b)。

我们就是把这种形式的一条条的规则提供给Auto Layout,它综合了许许多多这样的规则,来完成布局。

「Hey,小奥,这个View要靠左显示,离它爹10像素吧!」「小奥,这个Label跟它上面的按钮垂直居中!」

怎么样?反正给我的感受是,frame布局是我们替机器思考,而Auto Layout,是为我们自己思考。

Constraint in Code

上文我们介绍了在逻辑中该怎么去表示一个「约束」。那落实到代码中,该怎么表示呢?

苹果给我们提供了这样一个类,用作代码中一个约束的抽象——NSLayoutConstraint 。

它有一个很长的构造方法,返回一个约束实例,这个方法的参数就是上面提到的7个量:

NSLayoutConstraint *c = [NSLayoutConstraint constraintWithItem:view1 attribute:attr1
                                                     relatedBy:relation
                                                        toItem:view2 attribute:attr2
                                                    multiplier:k constant:b];

通过这个方法,我们构造一个又一个的 NSLayoutConstraint 实例,通过把这些实例add给合适的View的方式提供给Auto Layout System,就完成了代码层面的添加约束的过程。

Before starting

在你迫不及待的想上手之前,有两点务必记住。

  • 代码上忘掉 frame 和 autoresizingMask
  • 父View当前的大小是不可靠的

对于第一点,由于Auto Layout System已经接管了frame的设置,如果你再来掺一脚,会有很多诡异的问题。注意我说的是在代码上忘掉 frame ,在思考某个特定View的约束时,还是要想到它的frame的概念的。回想我们的frame布局时代,一个frame:

{x, y, width, height}

有四个量,也就是说至少需要四个量才能确定一个View的布局,逻辑上想想确实是那么回事。那我在我们添加某个View的约束时,也至少需要四个约束,才能确定一个View的布局。

水平方向:我在哪?我多宽?
竖直方向:我在哪?我多高?
             ---- 一个View的独白

忘掉 autoresizingMask 同理。我们甚至 必须要 显式地设置translatesAutoresizingMaskIntoConstraints 来保证约束的正确解析。当你在Auto Layout里摸爬滚打,痛不欲生却无论如何也没有正确优雅的布局映入你眼帘的时候,一定要记得回过头来看看,是不是忘记把相关View的这个属性设置为 NO了!

对于第二点,这是为了强迫你用动态的思维去思考该怎么描述约束。走出根据父View的bounds来设置子View的frame的时代吧,enjoy Auto Layout!

Add Constraints

接下来我们就开始计划着给View添加约束了。假设给了你一块地(一个View),你要给它的子View们添加约束,用Auto Layout进行布局,大致的思路如下:

  • 给这些Views大致分分组。当子View很多时,没必要全把他们当儿子。合理地用一些Container View,把儿子变成孙子,达到简化约束的目的。
  • 确定儿子们的布局依赖等级关系。举个例子,要想确定儿子A的位置,首先我得知道儿子B的位置。换句话说,只要儿子B的位置确定了,那么儿子A的位置就能确定。
  • 找出那些只依赖父亲的儿子,也即依赖关系最顶层的儿子(一定存在),先添加它们的约束。
  • 根据依赖关系层级,逐级添加约束

Example

假设我们要实现一个TableView,它的每个Cell中有个ImageView,显示一张图。大致效果如下图所示。

按着上面的思路,因为这个父亲(TableView Cell的contentView,注意使用Auto Layout布局TableView Cell一定要把儿子加在cotnentView上,不然在iOS 7、8上会有很诡异的问题)只有一个儿子,布局依赖关系也就很清楚了,开始构思约束。

Wait,在添加约束前,要先告知Auto Layout System ImageView和self.contentView的父子关系:

_imageView = [[UIImageView alloc] init];
_imageView.backgroundColor = [UIColor orangeColor];
_imageView.translatesAutoresizingMaskIntoConstraints = NO; // Don't forget !!
[self.contentView addSubview:_imageView];

OK,首先,这个ImageView的上、左、下距离它爹各20,约束如下:

NSLayoutConstraint *c1 = [NSLayoutConstraint constraintWithItem:_imageView attribute:NSLayoutAttributeLeft
                                    relatedBy:NSLayoutRelationEqual
                                      toItem:self.contentView attribute:NSLayoutAttributeLeft
                                    multiplier:1 constant:20];
NSLayoutConstraint *c2 = [NSLayoutConstraint constraintWithItem:_imageView attribute:NSLayoutAttributeTop
                                    relatedBy:NSLayoutRelationEqual
                                      toItem:self.contentView attribute:NSLayoutAttributeTop
                                    multiplier:1 constant:20];
NSLayoutConstraint *c3 = [NSLayoutConstraint constraintWithItem:_imageView attribute:NSLayoutAttributeBottom
                                    relatedBy:NSLayoutRelationEqual
                                      toItem:self.contentView attribute:NSLayoutAttributeBottom
                                    multiplier:1 constant:-20];

来分析一下。垂直方向,当 self.contentView 的高度确定后,由于我们指定了_imageView 的上下边距,则y和height都能被确定;水平方向上,我们指定了左边距,能确定x,但是width不能确定,因此我们还需要一个约束:

NSLayoutConstraint *c4 = [NSLayoutConstraint constraintWithItem:_imageView attribute:NSLayoutAttributeWidth
                                                      relatedBy:NSLayoutRelationEqual
                                                         toItem:_imageView attribute:NSLayoutAttributeHeight
                                                     multiplier:1 constant:0];

c4 告诉了Auto Layout System, _imageView 的长和宽相等。这样,它的约束就齐活了。

接着,这四个约束要给谁加。注意,每个约束要加载这个约束涉及到的两个View的最小父View 上,Forgiveness,这是我自己提出的概念。注意到 c1 , c2 , c3 涉及到的两个View是 self.contentView 和 _imageView ,其中 _imageView是 self.contentView 的子View,因此约束就需要加给 self.contentView 。

[self.contentView addConstraints:@[c1, c2, c3]];

c4 涉及到的两个View是 _imageView 本身,约束加给它自己。

[_imageView addConstraint:c4];

运行起来,结果如下图。

Visual Format Language

继续上面的例子。或许你会抱怨,每个View都需要搞这么一发,得写多少代码啊!

苹果贴心地给我们搞了一套形象地表示约束的方法——Visual Format Language(VFL)。

上面的 c1 , c2 , c3 可以表示成:

CGFloat leftMargin = 20;

NSArray *cs = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(leftMargin)-[_imageView]"
                                                      options:0 metrics:@{@"leftMargin": @(leftMargin)}
                                                        views:NSDictionaryOfVariableBindings(_imageView)];
NSArray *cs1 = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-20-[_imageView]-20-|"
                                                      options:0 metrics:nil
                                                        views:NSDictionaryOfVariableBindings(_imageView)];

[self.contentView addConstraints:cs];
[self.contentView addConstraints:cs1];

要把VFL说完,得新写一篇文章了啊,篇幅限制,就不展开叙述了,具体请看: 官网文档 。

后续

如果认真看到这,那么相信你是真的入门了。Auto Layout的东西还有很多,值得你去花时间继续深入调研。

比如,每个约束还有优先级之分。当Auto Layout System发现你给它的约束有冲突,它会根据有冲突的约束的优先级进行仲裁。

还有一些其它的东西是入门之后需要细细体会的,最好自己写一些Demo。

参考文献

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值