iOS-Auto Layout[1]-Constraints

Auto Layout可以根据View之间的Constraints(约束)来自动计算View的大小以及位置。这种基于Constraints的设计方法可以帮助开发者搭建起用户界面来应对外部或者内部的变化。

Auto Layout

外部变化

外部变化发生于SuperView的大小或者形状发生变化时,此时,必须更新View的Layout来尽可能好地利用空间。外部变化一般发生于运行时。下面是一些常见的外部变化:

  • 用户改变窗口的大小(OS X)
  • 用户进入或者离开iPad上的Split View(iOS)
  • 设备旋转(iOS)
  • 电话或者录音的Bar出现和消失(iOS)
  • 需要支持不同的大小Class
  • 需要支持不同的屏幕大小

内部变化

内部变化发生于View或者Control自身大小变化时。下面是一些常见的内部变化:

  • App变化时展示的内容(例如刷新)
  • App支持国际化(例如语言变化、日期数字变化、布局方向(英国从左到右,阿拉伯从右到左))
  • App支持动态类型(例如按钮按下时,文字变化等)

Auto Layout对比Frame-Based Layout

布局的方式有三种,分别是代码布局、使用AutoResizing Masks以及AutoLayout。

  • 传统的代码布局,使用Frame进行布局,当其中一个View发生变化时,需要重新计算相关的View的Frame。
  • AutoResizing Masks只支持外部变化,支持少数的变化布局,例如设备旋转。
  • Auto Layout考虑View之间的关系,而不是考虑单个View的Frame,Auto Layout使用一系列的Constraints,计算在这些Constraints下每个View的大小和位置。

AutoLayout-Constraints

Auto Layout属性

AutoLayout-Attributes

Attributes Value Notes
Height, Width 大小 可以为常量或者跟其他Height和Width组合
Top, Bottom, Baseline 沿着屏幕往下递增 可以跟Center Y组合
Leading, Trailing 往Trailing方向递增 可以跟Center X组合
Left, Right 往右递增 可以跟Center X组合
Center X, Center Y 取决于其他属性 Center X可以跟Leading, Trailing, Left, Right组合, Center Y可以跟Top, Bottom, BaseLine组合

尽量避免使用Left和Right,用Leading和Trailing代替,因为Left和Right是单方向地从左往右递增,而Leading和Trailing是区分反向的,这一点在切换方向时尤其关键,上面有提到,阿拉伯的习惯是从右到左布局的。iOS中可以设置SuperView的semanticContentAttribute属性来切换语言方向。

Constraints

Auto Layout可以不直接通过Constraints,使用UIStackView控件。而其他方式,需要通过Constraints。

Equation

Equation介绍

Views的Layout是通过一组线性的Equation来定义的,每一个Constraint代表一个方程式。

AutoLayout-Constraints-Formula

大多数Constraint都是定义了两个Item之间的关系,但同时也能表示一个Item之间两个Attribute之间的关系,例如长是高的两倍。如果设置了Item的宽或高是常量,则此时Multiplier为0,Second Item为空,Second Attribute设置为NotAnAttribute

里面是一些Equation例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Setting a constant height
View.height = 0.0 * NotAnAttribute + 40.0

// Setting a fixed distance between two buttons
Button_2.leading = 1.0 * Button_1.trailing + 8.0

// Aligning the leading edge of two buttons
Button_1.leading = 1.0 * Button_2.leading + 0.0

// Give two buttons the same width
Button_1.width = 1.0 * Button_2.width + 0.0

// Center a view in its superview
View.centerX = 1.0 * Superview.centerX + 0.0
View.centerY = 1.0 * Superview.centerY + 0.0

// Give a view a constant aspect ratio
View.height = 2.0 * View.width + 0.0

需要注意的是,Auto Layout并不是简单地对其进行从右往左赋值,而是解所有方程式得出最终值,例如下例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Setting a fixed distance between two buttons
Button_1.trailing = 1.0 * Button_2.leading - 8.0

// Aligning the leading edge of two buttons
Button_2.leading = 1.0 * Button_1.leading + 0.0

// Give two buttons the same width
Button_2.width = 1.0 * Button.width + 0.0

// Center a view in its superview
Superview.centerX = 1.0 * View.centerX + 0.0
Superview.centerY = 1.0 * View.centerY + 0.0

// Give a view a constant aspect ratio
View.width = 0.5 * View.height + 0.0

创建无歧义,可解的Equations

Auto Layout要求系列Equations必须是有且只有唯一解,要求每个View都定义好了大小和位置。例如下图:

AutoLayout-Constraints-Equations

只看水平方向:

  • 图1定义了View的Leading与SuperView的Leading的关系,且给出了固定的长度,可以自动算出其大小和位置;
  • 图2定义了View的Leading与Trailing与SuperView的关系,可以自动算出其大小和位置;
  • 图3定义了View的Leading与SuperView的Leading的关系,且定义了其中心与SuperView中心对齐,可以自动算出其大小和位置;

但是,如果SuperView大小发生变化,此时,图1的长度不会自动变化,因此不符合要求,图2和图3都可以,但是图3可用性更强,特别是当需要操作多个Item时。

只要保证有解且唯一性,Equations的定义可以是多样性的,例如下图:

AutoLayout-Constraints-Equations-Vertical

AutoLayout-Constraints-Equations-Horizontal

在设备旋转是,两个View的位置保持布局不变,Equations可以这样写:

1
2
3
4
5
6
7
8
9
10
11
// Vertical Constraints
Red.top = 1.0 * Superview.top + 20.0
Superview.bottom = 1.0 * Red.bottom + 20.0
Blue.top = 1.0 * Superview.top + 20.0
Superview.bottom = 1.0 * Blue.bottom + 20.0

// Horizontal Constraints
Red.leading = 1.0 * Superview.leading + 20.0
Blue.leading = 1.0 * Red.trailing + 8.0
Superview.trailing = 1.0 * Blue.trailing + 20.0
Red.width = 1.0 * Blue.width + 0.0

也可以这样写:

1
2
3
4
5
6
7
8
9
10
11
// Vertical Constraints
Red.top = 1.0 * Superview.top + 20.0
Superview.bottom = 1.0 * Red.bottom + 20.0
Red.top = 1.0 * Blue.top + 0.0
Red.bottom = 1.0 * Blue.bottom + 0.0

//Horizontal Constraints
Red.leading = 1.0 * Superview.leading + 20.0
Blue.leading = 1.0 * Red.trailing + 8.0
Superview.trailing = 1.0 * Blue.trailing + 20.0
Red.width = 1.0 * Blue.width + 0.0

两种写法各有优劣,第一种写法即使Red View被移除了,Blue View依然布局不变,但是需要注意Red View和Blue View的top和bottom的常量保持一致;第二种写法,无需注意Red View的top的常量,但是一旦Red View被移除,Blue View将只有一个Constraint。

Inequalites

除了Equations,Auto Layout也支持Inequalites,例如:

1
2
3
4
5
// Setting the minimum width
View.width >= 0.0 * NotAnAttribute + 40.0

// Setting the maximum width
View.width <= 0.0 * NotAnAttribute + 280.0

这种情况下,需要其他的属性来决定其最终的大小。

Priorities

一般情况下,每一个Constraint都必须满足,如果没有解满足所有Constraint,则其中一条将被抛弃,Auto Layout会将不满足的Constraint打印到控制台。

每一个Constraint都有一个Priority,优先级为1000的必须满足,其他的属于可选的,可能被抛弃。Auto Layout会从优先级高到低进行计算,如果一个Constraint不满足,则跳过它,继续运算。但是即使被跳过,该Constraint也可以影响结果,Auto Layout会选择结果集合中靠近它的来作为解。

例如上面的不等式,第一条可以设置为1000,第二条为250,则View的长度一定大于40,且尽可能小于280。

Intrinsic Content Size

一些Views包含着一些Intrinsic Content(内部内容),例如Button有文字。

View Intrinsic Content Size
UIView NO
Sliders Width
Labels,Buttons,Switches,and Text Fields Width and Height
Text Views and Image Views Can Vary

Intrinsic Content Size取决于内容,Label和Button的内部内容大小取决于文字的长度和字体,ImageView取决于图片大小,TextView内部内容大小取决于文字、是否可以滑动以及与其他约束。

Auto Layout在水平和垂直维度上都通过一组Constraint来展示内部内容,其中,Content Hugging尝试压缩,而Compression Resistance尝试扩张。

AutoLayout-Constraints-IntrinsicContent

这些Constraints是通过Inequalites方式定义的:

1
2
3
4
5
6
7
// Compression Resistance
View.height >= 0.0 * NotAnAttribute + IntrinsicHeight
View.width >= 0.0 * NotAnAttribute + IntrinsicWidth

// Content Hugging
View.height <= 0.0 * NotAnAttribute + IntrinsicHeight
View.width <= 0.0 * NotAnAttribute + IntrinsicWidth

每一个Constraint都有Priority,一般为Content Hugging为250,Compression Resistance为750。因此,伸展比压缩更容易满足要求,例如,如果过度压缩,会裁剪按钮上的文字。

尽可能使用View的Intrinsic Content Size,可以不必自己维护Constraints,但是需要设置CHCR(Content-Hugging & Compression-Resistance)的Priority。

下面是一些需要注意的点:

  • 当需要一组View填充一个空间时,不要设置相同的Priority,不然Auto Layout不知道该拉伸哪个,最常见的例子是,一个Label和一个TextField并排时,需要尽可能拉伸TextField的长度,又不压缩到Label的文本,这种情况下,需要设置Label的Content-Hugging的Priority较高;
  • 一些奇怪的Layout经常发生于当View有隐藏的Background时,比如Button,经常被拉伸到比Intrinsic Content Size大,可以通过增大Content-Hugging Priority来阻止不想要的拉伸;
  • BaseLine Constraints只作用于高度,对于水平线的属性,没有影响;
  • 一些Views,像UISwitch,需要在Intrinsic Content Size范围内显示,可以修改CHCR Priority来控制其拉伸和压缩;
  • 避免给Views设置Required CHCR Priority(1000),因为1000是必须满足的,这样可能导致冲突,如果确实需要Views保持在Intrinsic Content Size,可以设置为999.

Intrinsic Content Size与Fitting Size是不同的,Intrinsic Content Size只存在于特定的Views中,而Fitting Size是通过Constraints计算出的最终布局结果。举例说明,StackView,并没有Intrisic Content Size,所以设置CHCR不起作用,而设置Constraints可以得到Fitting Size。

Intrinsic Size修改位置:

AutoLayout-IntrinsicSize