深入剖析AutoLayout 之 布局过程

介入正题之前, 首先回顾一下UIKit里的定位要素.

###1. frame

Auto Layout诞生前, 布局里的绝对主角, 它表示了view在superview坐标系中的位置. frame是bounds/center/transform共同作用的产物.

###2. bounds/center

bounds表示了view在自身坐标系中的位置. 多数view bounds的origin都为CGPointZero, 但是对UIScrollView及其子类而言, bounds的origin会随着scroll而改变.

center表示了view在superview坐标系中的中心点位置.

一般而言, 改变frame会相应地改变bounds和center, 反之亦然.

###3. transform

transform表示了基于center和bounds的形变. 需要特别注意的是文档中的这一段:

@property(nonatomic) CGRect frame WARNING If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.

因而, 在初始布局完成以后, 个人习惯仅使用transform完成动画, 可以在有效避免magic number泛滥的同时, 规避frame的未定义行为.

而后回顾一下Auto Layout之前的布局过程.

###-setNeedsLayout/-layoutIfNeeded/-layoutSubviews

-setNeedsLayout invalidate当前布局, 并schedule一次布局更新. 

-layoutIfNeeded在调用过-setNeedsLayout前调用则忽略, 在-setNeedsLayout后调用则立即执行布局更新逻辑.

-layoutSubviews在iOS5.1前的默认实现都不做, 但需要时, 应在此方法内实现布局逻辑.

-setNeedsLayout/-layoutSubviews/-layoutIfNeeded职责分离减少了-layoutSubviews不必要的重复调用.

最后谈谈引入Auto Layout之后布局过程的改动.

###1. -setNeedsUpdateConstraints/-needsUpdateConstraints

-setNeedsUpdateConstraints与-setNeedsLayout相似, invalidate当前约束, 并schedule一次约束更新. 区别是-needsUpdateConstraints状态可查, 而-needsLayout不可查.

###2. +requiresConstraintBasedLayout

可视为needsUpdateConstraints的初值. 自身约束设置集中在-updateConstraints里时, 应返回YES, 以保障在外界不触发-updateConstraints时, 依然能正确布局. eg, 父视图不使用auto layout, 而自身作为容器使用auto layout.

###3. -updateConstraintsIfNeeded/-updateConstraints

-updateConstraintsIfNeeded与-layoutIfNeeded相似, 在-needsUpdateConstraints为NO时忽略, 在-setNeedsUpdateConstraints后调用则立即执行约束更新逻辑.

-updateConstraints与-layoutSubviews相似, 用于执行约束更新. 如前篇约束转译中所说, 转译过程在-updateConstraints中执行.

需要特别说明的是, override时, 禁止在实现中invalidate constraint, 也禁止将layout或者draw作为约束更新的一部分.

此外, **必须**在实现的最后一步调用[super updateConstraints].

-updateConstraintsIfNeeded/-updateConstraints会在-layoutIfNeeded/-layoutSubviews之前调用以保障布局时约束有效.

###4. -layoutSubviews

Auto Layout的执行者, 默认实现使用约束布局subviews, 转译约束的生效也依赖于此.

可以override, 但仅在auto resize和auto layout都不能满足要求时.

需要额外说明的是, Auto Layout在布局时, 依赖bounds/center属性及-alignmentRectForFrame:/-frameForAlignmentRect:, 这就意味着, 使用transform的位移或动画平行于Auto Layout, 彼此间互不影响(避坑请查阅<storyboard下使用transform的坑>).

深入剖析AutoLayout 之 约束转译

auto resizing

为了简化容器尺寸变化后的子视图布局, iOS引入了autoresizingMask, 通过设定子视图与父视图之间左距/宽/右距/上距/高/下距的关系, 让布局自动响应尺寸变化. 容器尺寸变化后, autoresizingMask中左距/宽/右距/上距/高/下距的可变成分按当前比例分摊宽高变化.

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,    // 左距可变
    UIViewAutoresizingFlexibleWidth        = 1 << 1,    // 宽可变
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,    // 右距可变
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,    // 上距可变
    UIViewAutoresizingFlexibleHeight       = 1 << 4,    // 高可变
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5     // 下距可变
};

// 是否在自身bounds改变后相应改变subview的布局, 默认为YES.
@property(nonatomic) BOOL               autoresizesSubviews;
// 指示autoresizesSubviews为YES时, view如何响应superview bounds的改变.
@property(nonatomic) UIViewAutoresizing autoresizingMask;

当autoresizingMask为UIViewAutoresizingNone时, 视同UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin, 右距和下距可变, 即origin和size都不变.

举个简单的例子:

初始布局
Container
    origin: {0, 0} size: {50, 50}
Subview
    origin: {10, 10} size: {30, 30}
    autoresizingMask: flexible left/top/height

变化布局
Container
    origin: {0, 0} size: {100, 100}
Subview
    origin: {60, 22.5} size: {30, 67.5}
    autoresizingMask: flexible left/top/height

水平方向上, 左距可变, 则宽与右距不变, 增加的50全部累积在左距上, 因此x变为60;
垂直方向上, 上距和高可变, 比例为1:3, 则增加的50按比例分摊在上距和高上, 因此y变为22.5, height变为67.5.

借助auto resizing, UIKit可以解决单一屏幕尺寸下的简单布局逻辑, 但从iPad/iPhone 5/iPhone 6以来, 它早已疲态尽显:

1. 如果容器的初始化size为{0, 0}而子视图以一定的insets内嵌于容器时, 由于frame size不能为负数, 就无法使用autoresizingMask来实现布局逻辑, 必须在-layoutSubviews中实现. 而UIView的-init方法调用-initWithFrame:时frame的默认值就是CGRectZero.

2. 传统布局只能设定父子视图间的布局应变关系(通过autoresizingMask), 而没有办法设定子视图间的应变关系, 要实现常见的布局逻辑(如子视图等宽), 就不得不借助额外的autoresizingMask和view层次, 或是在-layoutSubviews中实现.

3. 难以实现跨越视图层级的布局关系, 即使实现, 也不得不暴露过多内部实现细节或增加不必要的复杂性.

translate auto resizing mask into constraints

iOS6在引入Auto Layout布局框架的时, 为了兼容auto resizing, 可以在布局过程中将autoresizesSubviews和autoresizingMask转译为NSLayoutConstraint. 并增加了转译过程的开关:

- (BOOL)translatesAutoresizingMaskIntoConstraints;
- (void)setTranslatesAutoresizingMaskIntoConstraints:(BOOL)flag;

当translatesAutoresizingMaskIntoConstraints为YES时, -updateConstraints调用会将autoresizing mask转译为constraint, 并添加至其superview. 具体转译规则如下:

1. 转译的constraint包含一个width/height constraint 和一个midX/Y constraint;
2. 若superview的autoresizesSubviews为NO, 将使用UIViewAutoresizingNone(flex right & bottom)作为参与转译的autoresizing mask;
3. 改变frame/autoresizesSubviews/autoresizingMask, 会置needsUpdateConstraints为YES, 但不会触发-setNeedsUpdateConstraints.

以flexible width & right(h=-&&)为例, frame为{{10, 78}, {300, 300}}的view在iPhone5的ViewController上生成的约束为:

NSAutoresizingMaskLayoutConstraint: View:.midX == 0.967742*UIView:.midX + 5.16129>
NSAutoresizingMaskLayoutConstraint: View:.width == 0.967742*UIView:.width - 9.67742>

translatesAutoresizingMaskIntoConstraints规约

在使用auto layout的view中, 多数情况下translatesAutoresizingMaskIntoConstraints应设为NO(默认YES)以避免约束冲突. 但布局实现是私有细节, 使用SDK或开源容器时, 可以修改view的autoresizing mask或translatesAutoresizingMaskIntoConstraints, 但不应修改subview的translatesAutoresizingMaskIntoConstraints属性, eg UITableViewCell’s contentView.

-EOF-

深入剖析AutoLayout 之 优先级

取值偏好

前文中提及NSLayoutConstraint的relation属性有三种取值:

typedef NS_ENUM(NSInteger, NSLayoutRelation) {
    NSLayoutRelationLessThanOrEqual = -1,
    NSLayoutRelationEqual = 0,
    NSLayoutRelationGreaterThanOrEqual = 1,
};

首先, 考虑一下”H:[view(>=50)]”, view的最终宽度会是多少呢? 这需要分几种情况:

1. view的horizontal intrinsic content size为UIViewNoIntrinsicMetric, 那么view的最终宽度为50;

2. view的horizontal intrinsic content size小于50, 那么view的最终宽度为50;

3. view的horizontal intrinsic content size大于50, 那么view的最终宽度为horizontal intrinsic content size.

而后, 考虑一下”H:[view(<=100)]”, view的最终宽度会是多少呢? 这需要分几种情况:

1. view的horizontal intrinsic content size为UIViewNoIntrinsicMetric, 那么view的最终宽度为100;

2. view的horizontal intrinsic content size小于100, 那么view的最终宽度为horizontal intrinsic content size;

3. view的horizontal intrinsic content size大于100, 那么view的最终宽度为100.

最后, 再考虑一下”H:[view(>=50@250,<=100@1000)]”, view的最终宽度会是多少呢? 这需要分几种情况:

1. view的horizontal intrinsic content size为UIViewNoIntrinsicMetric, 那么view的最终宽度为50;

2. view的horizontal intrinsic content size小于50, 那么view的最终宽度为50;

3. view的horizontal intrinsic content size大于50且小于100, 那么view的最终宽度为horizontal intrinsic content size;

4. view的horizontal intrinsic content size大于100, 那么view的最终宽度为100.

规则如下:

取值与优先级无关. 当宽高约束的取值存在范围时, 若维度上的intrinsic content size为UIViewNoIntrinsicMetric, 则取用边界值; 若intrinsic content size在取值范围中, 则取用intrinsic content size; 否则, 使取值与intrinsic content size的差值的绝对值尽可能小, 即尽可能贴近intrinsic content size. 相似的, 非宽高约束取值存在范围时, 使取值的绝对值尽可能小.

拉伸与压缩

考虑如下情况: “H:|-10-[label1]-10-[label1]-10-|”, 假设label1和label2的horizontal intrinsic content size都为100, 那么容器所需的宽度就是230. 但若容器的实际宽度并不为230, 而是大于或小于230时, 会发生什么?

实际上, 为了应对这种情况, Auto Layout为UIView新增了一些方法:

- (UILayoutPriority)contentHuggingPriorityForAxis:(UILayoutConstraintAxis)axis;
- (void)setContentHuggingPriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis;
- (UILayoutPriority)contentCompressionResistancePriorityForAxis:(UILayoutConstraintAxis)axis;
- (void)setContentCompressionResistancePriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis;

其中UILayoutConstraintAxis定义如下:

typedef NS_ENUM(NSInteger, UILayoutConstraintAxis) {
    UILayoutConstraintAxisHorizontal = 0,
    UILayoutConstraintAxisVertical = 1
};

藉于此, 我们得以在借助于intrinsic content size确定视图尺寸而视图间又存在空间竞争关系时设定拉伸抗性(Hugging Priority, 直译为紧缩优先级)和压缩抗性(Compression Resistance Priority, 直译为抗压缩优先级). 与NSLayoutConstraint的优先级相似, 两种抗性的常见取值分为required/high/low/size fitting 4种.

假定在上述情况中, label1的拉伸抗性和压缩抗性都高于label2. 那么, 若容器宽度大于230, 则拉伸label2; 若容器宽度小于230且大于110, 则压缩label2; 若容器宽度小于110, 则label2宽度为0, 压缩label1.

规则如下:

当高宽无约束限定, 使用intrinsic content size确定视图尺寸时, 如多个视图间存在需要拉伸/压缩时, 拉伸拉伸抗性(Hugging Priority)最低的, 若级别一致, 拉伸最后一个提升至该级别的, 如果级别没有变更过, 拉伸第一个; 压缩压缩抗性(Compression Resistance Priority)最低的, 如果级别都一样, 压缩最后一个提升至该级别的, 如果级别没有变更过, 压缩最后一个. 若级别变更后, 若现有布局不能满足新的优先级次序将会触发重新布局, 否则将维持现有布局不变.

这也就是为什么的IB中, 约束检查器时常会要求在hug/compression resistance上加1以错开同行列间优先级次序的原因.

-EOF-

深入剖析AutoLayout 之 布局空间

定位法则

一般而言, 要确定视图的显示位置, 需要在水平上确定出左/中/右/宽中的两个、在垂直方向上确定出上/中/下/高中的两个. 对应到Auto Layout中, 要设定一个视图的约束, 水平方向上至少要在left/centerX/right/width中选用两个, 其中的left/right也可以用leading/trailing来代替; 垂直方向上至少要在top/centerY/bottom/height/baseline(first or last)中选用两个, 但只能在baseline不同于top/centerY/bottom时, 由baseline和三者中的任意确定布局位置.

tips1: 在English中leading/trailing等同于left/right, 在Hebrew或Arabic中则相反. 若要i18n, 在语言相关的视图上应使用leading/trailing.
tips2: iOS8后, 支持first baseline, 在多行文本中指定第一行文本的baseline位置.
tips3: 部分constraint attribute支持margin, 可以配合layout margins使用.

那么是否可以添加多个约束呢? 显然是可以的, 如V:|-10-[button(80)]-10-|, button上就有三条不同属性的约束.

那么是否可以添加多个相同属性的约束呢? 也是可以的, 如V:[button(>=80,<=120)], button上就添加了两条针对高度的约束.

那么是否可以添加多个相同属性的相斥约束吗? 不可以. 如V:[button(<=100@50,>=200@1000)], button上添加了两条针对高度的相斥约束, 即使>=200的优先级为1000(required), <=100的优先级为50(size fitting), 也会在布局时产生冲突.

应该说, 只要约束间不冲突, 可以任意增加约束, 但添加就同一属性添加多条约束和约束相斥的情况并不常见, 多数时候只应添加足够的约束保证布局即可.

intrinsic content size

对于知道了内容才能确定宽高的视图, 如UILabel, 约束要怎么设定呢? Auto Layout提供了除约束外的另一套工具:

- (CGSize)intrinsicContentSize;
- (void)invalidateIntrinsicContentSize;

intrinsic content size直译就是固有内容尺寸, 即展示内容所需的空间, -intrinsicContentSize返回的就是这个值; -invalidateIntrinsicContentSize则用于通知Auto Layout布局框架视图的intrinsic content size变更, 需要重新调整布局. 当视图在某一维度不存在intrinsic content size时, 应返回UIViewNoIntrinsicMetric. 以UILabel为例, -intrinsicContentSize返回text/attributedText所需的空间, 当改变text等影响intrinsic content size的属性时, UILabel会调用-invalidateIntrinsicContentSize.

有了intrinsic content size的支持, 如UILabel/UIButton/UIImageView这样的视图就可以不针对高宽设定约束, Auto Layout框架会在设定好水平/垂直位置的情况下打理好一切. 想想曾经恶心过那么多人的H:|-[image]-[label]-[button]-(>=8)-[date]-|就此迎刃而解, 妈妈再也用不担心我的-layoutSubviews, 是不是还有一些小激动?

baseline alignment

上一篇中曾提到三种与baseline相关的constraint attribute, 分别是NSLayoutAttributeBaseline(基准线), NSLayoutAttributeLastBaseline(末行基准线)和NSLayoutAttributeFirstBaseline(首行基准线). 其中NSLayoutAttributeBaseline与NSLayoutAttributeLastBaseline相等, 而NSLayoutAttributeFirstBaseline是iOS8新引入的.

由于constraint attribute适用于所有视图, 这三种baseline显然并不例外. 那么, 那些似乎并没有基准线的视图, 是怎么处理这三者的呢? 答案是NSLayoutAttributeFirstBaseline视同NSLayoutAttributeTop, NSLayoutAttributeLastBaseline视同NSLayoutAttributeBottom. 当视图作为容器依赖子视图参与baseline布局时, 应通过

- (UIView *)viewForBaselineLayout

返回相应子视图, Auto Layout框架会完成相应的转换.

alignment rect

既然提及了baseline alignment, 就把另一个极其重要, 却总是被忽略的概念 – constraint attribute到底指什么 – 说说清楚.

首先明确一点, constraint attribute里的left/right/top/bottom/width/height等确实和frame的minX/maxX/minY/maxY/width/height等一一对应, 但它们并不相等. 因为constraint attribute约束的是视觉空间. Auto Layout执行布局时, 依赖的是视觉空间(alignment rect) , 而非布局空间(frame). 视觉空间和布局空间有什么分别? 举个简单的例子, button的content inset区域属于frame, 但不属于alignment rect, 因此, 在Auto Layout下设定button的content inset不会影响button最终的视觉效果, 但frame会随着content inset而改变, 这个方法可以用于增加button的点击范围.

Auto Layout中alignment rect和frame是怎么转换的呢? Auto Layout在UIView上新增了三个方法:

- (UIEdgeInsets)alignmentRectInsets;
- (CGRect)alignmentRectForFrame:(CGRect)frame;
- (CGRect)frameForAlignmentRect:(CGRect)alignmentRect;

显而易见的, -alignmentRectForFrame:和-frameForAlignmentRect:就是alignment rect和frame之间的转换规则. -alignmentRectInsets返回的是alignment rect内嵌于frame的距离, -alignmentRectForFrame:和-frameForAlignmentRect:的默认实现就是依据这个返回值执行转换. 如果转换关系只是简单的inset, 则可以override -alignmentRectInsets, 而不override -alignmentRectForFrame:和-frameForAlignmentRect:.


Auto Layout提供的intrinsic content size和baseline alignment无疑会极大的降低布局的复杂性, 也会大幅较少override -layoutSubviews的必要性.

-EOF-

深入剖析AutoLayout 之 何为约束

开个坑讲讲Auto Layout的那点事儿. 分卷如下:

  1. 约束
  2. 布局空间
  3. 优先级
  4. 约束转译
  5. 布局过程
  6. 尺寸计算
  7. 边距

逐卷介绍Auto Layout中的部分概念, 并比对与先前布局机制的异同点.


何为约束

iOS6时, UIKit增加了Auto Layout, 使用约束描述UI对象属性间关系的方式来替代直接设定frame/bounds/center.

NSLayoutConstraint作为约束的载体, 定义了约束相关的属性如下:

  • priority 优先级, 取值越高, 优先级越高. 预设的优先级有required/high/low/size fitting四种.
  • firstItem 被约束视图
  • firstAttribute 被约束属性
  • relation 关系, 大于等于/等于/小于等于三种.
  • secondItem 参照视图
  • secondAttribute 参照属性
  • multiplier 倍数因子
  • constant 偏移因子
  • identifier 标示符
  • shouldBeArchived 是否随view一同序列化, 默认NO; 一般而言, 布局约束应序列化, 中间状态不应.

其中的attribute定义如下:

typedef NS_ENUM(NSInteger, NSLayoutAttribute) {
  NSLayoutAttributeNotAnAttribute,    // 无属性
    NSLayoutAttributeLeft,            // 左侧
    NSLayoutAttributeRight,           // 右侧
    NSLayoutAttributeTop,             // 上侧 
    NSLayoutAttributeBottom,          // 下侧
    NSLayoutAttributeLeading,         // 书写起始侧
    NSLayoutAttributeTrailing,        // 书写终止侧
    NSLayoutAttributeWidth,           // 宽
    NSLayoutAttributeHeight,          // 高
    NSLayoutAttributeCenterX,         // 水平中点
    NSLayoutAttributeCenterY,         // 垂直中点
    NSLayoutAttributeBaseline,        // 基准线
    NSLayoutAttributeLastBaseline,    // 末行基准线
    NSLayoutAttributeFirstBaseline,   // 首行基准线
    NSLayoutAttributeLeftMargin,      // 左边界
    NSLayoutAttributeRightMargin,     // 右边界
    NSLayoutAttributeTopMargin,       // 上边界
    NSLayoutAttributeBottomMargin,    // 下边界
    NSLayoutAttributeLeadingMargin,   // 书写起始边界
    NSLayoutAttributeTrailingMargin,  // 书写终止边界
    NSLayoutAttributeCenterXWithinMargins,  // 边界内水平中点
    NSLayoutAttributeCenterYWithinMargins,  // 边界内垂直中点
};

从而, UI对象间的关系可以描述为如下线性关系:

firstItem.firstAttribute >= multiplier × secondItem.secondAttribute + constant 或 firstItem.firstAttribute == multiplier × secondItem.secondAttribute + constant 或 firstItem.firstAttribute <= multiplier × secondItem.secondAttribute + constant

例如:

icon.left == 1 × cell.contentView.left + 10

就表示icon的左侧在cell.contentView左侧+10的位置.

约束的使用限制

  1. NSLayoutConstraint的属性中priority、constant、identifier和shouldBeArchived外的属性都是readonly的;
  2. firstItem和firstAttribute不能为空(nil/none), secondItem和secondAttribute则可以;
  3. 约束必须添加在firstItem和secondItem的共同先祖视图上;
  4. 约束描述的参照关系不能穿越bounds可以偏移的视图, 如UIScrollView的subview不能设定subview.top == scrollview.superview.top.

使用代码创建约束

NSLayoutConstraint提供了两种构造方法:

###1. +constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:

直接使用readonly的属性创建约束. 由于创建时不得不指定所有属性, 即使secondItem不存在、multiplier为1或constant为0也不能避免, 导致可读性和易用性都堪忧, 因而再次推荐笔者的另一篇blog.

###2. constraintsWithVisualFormat:options:metrics:views:

通过Visual Format Language一次创建水平/垂直方向上的一组约束. metrics和views分别为常量和视图映射. Visual Format的具体规则可以参阅Auto Layout Guide, 这里只简单举个例子:

V:|-10-[label(>=70@1000,>=button@250)]-[button][icon]-|

符号 含义
V: 垂直方向
-10-[label] label与容器上边距为10
(>=70@1000,>=button@250) label高大于等于70, 优先级1000; 高大于等于button高, 优先级250
[label]-[button] label与button间距为默认长度(8)
[button][icon] button与icon相邻, 无间距; button和icon使用intrinsic size高
[icon]-| icon与容器下边距为默认长度(8)

此外, 可以通过options来额外设定Visual Format中视图间的对其关系. options定义如下

enum {
   /* choose only one of these */
   NSLayoutFormatAlignAllLeft,          // Y轴方向 元素左对齐
   NSLayoutFormatAlignAllRight,         // Y轴方向 元素右对齐
   NSLayoutFormatAlignAllTop,           // X轴方向 元素上对齐
   NSLayoutFormatAlignAllBottom,        // X轴方向 元素下对齐
   NSLayoutFormatAlignAllLeading,       // X轴方向 元素头对齐
   NSLayoutFormatAlignAllTrailing,      // X轴方向 元素尾对齐
   NSLayoutFormatAlignAllCenterX,       // X轴方向 元素中线对齐
   NSLayoutFormatAlignAllCenterY,       // Y轴方向 元素中线对齐
   NSLayoutFormatAlignAllBaseline,      // Y轴方向 元素基线对齐
   NSLayoutFormatAlignAllLastBaseline,  // Y轴方向 元素末行基线对齐
   NSLayoutFormatAlignAllFirstBaseline, // Y轴方向 元素首行基线对齐
   NSLayoutFormatAlignmentMask = 0xFF,

   /* choose only one of these three */
   NSLayoutFormatDirectionLeadingToTrailing = 0 << 8, // X轴方向 头到尾
   NSLayoutFormatDirectionLeftToRight = 1 << 8,       // X轴方向 左到右
   NSLayoutFormatDirectionRightToLeft = 2 << 8,       // X轴方向 右到左
   NSLayoutFormatDirectionMask = 0x3 << 8,
};
typedef NSUInteger NSLayoutFormatOptions;

Visual Format的强大之处在于它可以清晰的说明一个方向上各视图间的关系, 缺点是:

  1. 无法使用margin

  2. 约束设定远离视图创建的位置

使用IB中创建约束

如果使用IB, 在IB中添加约束一般会比代码要便利上许多, IB自带的约束检查器也足够强大. 怎么在IB中添加约束在Raywenderlich或是Cocoachina都能找到一打教程, 就不赘述了. 即使不看教程, 在探究过Auto Layout之后, 把玩两回也不难弄懂要怎么用.

在IB中添加约束也有它的代价:

  1. 时序限定为创建时

  2. 整个XIB/storyboard内都必须使用Auto Layout

  3. 创建大量约束时难以管理

额外提示两点:

  1. 善用IB右下的工具可以给约束设定工作带来质的飞跃

  2. 使用拉拽方式设定约束时, 善用shift键也可以一次添加多条约束

-EOF-