- 翻译@Auto Layout Guide(自动布局指南)
- Auto Layout Cookbook(自动布局使用手册)
- Views with Intrinsic Content Size(利用固有尺寸布局)
- Auto Layout Cookbook(自动布局使用手册)
翻译@Auto Layout Guide(自动布局指南)
Auto Layout Cookbook(自动布局使用手册)
Views with Intrinsic Content Size(利用固有尺寸布局)
本节通过实际例子演示如何利用固有尺寸消减约束,简化布局;通常需要配合外扩和内缩优先级同时使用。
源码详见Auto Layout Cookbook。
Simple Label and Text Field(标签和文本框的简单组合)
本例布局一个标签和一个文本框组:前者宽度由文字决定;后者占据剩余空间。
借助固有属性,只需五条约束,即可搞定布局。当然别忘了正确设置外扩和内缩优先级。
更多关于固有尺寸,外扩和内缩优先级的信息,详见章节Intrinsic Content Size(固有尺寸)。
Views and Constraints(搭建布局)
拖拽控件到画布,填写标签内容和文本框占位文字,然后按照下图添加约束:
Name Label.Leading = Superview.LeadingMargin
Name Text Field.Trailing = Superview.TrailingMargin
Name Text Field.Leading = Name Label.Trailing + Standard
Name Text Field.Top = Top Layout Guide.Bottom + 20.0
Name label.Baseline = Name Text Field.Baseline
Attributes(设置属性)
文本框内缩优先级必须小于标签,才会被优先拉伸;IB会自动将前者设置为250,后者设置为251。可以在尺寸面板验证:
Name | Horizontal hugging | Vertical hugging | Horizontal resistance | Vertical resistance |
---|---|---|---|---|
Name Label | 251 | 251 | 750 | 750 |
Name Text Field | 250 | 250 | 750 | 750 |
Discussion(分析&讨论)
本布局垂直方向上只有两条约束(4和5),水平方向上只有三条约束(1,2和3)。回忆一下章节Creating Nonambiguous, Satisfiable Layouts(创建明确可满足的布局)提到的规则:对于一个视图来说,要创建明确可满足的布局,水平和垂直方向上各需要两条约束。这是因为固有尺寸定义了标签宽高,文本框高度,从而替代了缺少的三条约束。
为简单起见,我们假设文本框高度一定大于标签;并且,上方间距以文本框到控制器上方布局参照的距离为准;最后,由于二者都展示文字,所以通过基准线对齐。
外扩和内缩优先级会影响水平方向上哪个视图被优先拉伸。IB已经帮我们设置好了:标签内缩优先级为251;文本框为250。因此由后者填充剩余空间。
注意
如果空间太小,无法显示所有视图,还需要修改外扩优先级:其决定空间不足时,优先剪裁哪个视图。
修改本例的外扩优先级作为课后练习留给读者。如果标签文字过多,或字体过大,现有空间不足以完全显示时,布局会产生歧义。系统会随机打破一个约束,其中一个视图被裁剪。
理想情况下,布局不应超出当前空间——如果空间紧张,最好使用自适应布局。但如果需要支持多种语言,以及动态字体,那么视图尺寸就很难控制。修改外扩优先级是万全之策。
Dynamic Height Label and Text Field(高度动态变化的标签和文本框)
通过假设文本框高度总是大于标签,Simple Label and Text Field(标签和文本框的简单组合)的布局逻辑得以简化。然而这种假设并非永远正确:增大标签字号,其高度有可能大于文本框。
所以,最好以高度较大的视图的上方间距为准。使用常规字号时,效果与上例没有区别;然而,标签字号调大于等于36.0pt时,上方间距将基于标签计算。
本例多少有些牵强:实际使用时,文本框字号往往会随着标签一起增大。然而,鉴于用户可以设置动态字体大小,那么,当动态字体控件和固定尺寸控件(如图片)一同展示时,本技巧就有了用武之地。
Views and Constraints(搭建布局)
视图结构同Simple Label and Text Field(标签和文本框的简单组合) 一致,但约束更复杂:
Name Label.Leading = Superview.LeadingMargin
Name Text Field.Trailing = Superview.TrailingMargin
Name Text Field.Leading = Name Label.Trailing + Standard
Name Label.Top >= Top Layout Guide.Bottom + 20.0
Name Label.Top = Top Layout Guide.Bottom + 20.0 (Priority 249)
Name Text Field.Top >= Top Layout Guide.Bottom + 20.0
Name Text Field.Top = Top Layout Guide.Bottom + 20.0 (Priority 249)
Name label.Baseline = Name Text Field.Baseline
Attributes(设置属性)
文本框内缩优先级必须小于标签,才会被优先拉伸;IB会自动将前者设置为250,后者设置为251。可以在尺寸面板验证:
Name | Horizontal hugging | Vertical hugging | Horizontal resistance | Vertical resistance |
---|---|---|---|---|
Name Label | 251 | 251 | 750 | 750 |
Name Text Field | 250 | 250 | 750 | 750 |
Discussion(分析&讨论)
每个视图的上方间距各通过一对约束定义。必要约束规定到控制器上方布局参照的距离大于等于20.0pt;可选约束试图将这个距离固定在20.0pt。
对于较高的视图,两个约束都能满足,上方间距20pt。而对于较矮的视图,只满足必要约束,忽略可选约束。因此,视图高度变化,上方间距重新计算。
注意
可选约束优先级务必小于默认内缩优先级(250)。否则,内缩约束失效,视图拉伸,而非改变位置。
特别注意以基准线对齐的情况:基准线对齐仅在文本视图保持固有尺寸时有效,尺寸变化,约束失效(包括必要约束)。
Fixed Height Columns(定高组合)
接下来我们以Simple Label and Text Field(标签和文本框的简单组合)为基础,纵向排列一组标签和文本框。所有标签后边对齐;所有文本框前后对齐;水平位置以最长的标签为准。同Simple Label and Text Field(标签和文本框的简单组合)一样,我们假设文本框高度一定大于标签。
Views and Constraints(搭建布局)
排布视图,随后按照下图添加约束:
First Name Label.Leading = Superview.LeadingMargin
Middle Name Label.Leading = Superview.LeadingMargin
Last Name Label.Leading = Superview.LeadingMargin
First Name Text Field.Leading = First Name Label.Trailing + Standard
Middle Name Text Field.Leading = Middle Name Label.Trailing + Standard
Last Name Text Field.Leading = Last Name Label.Trailing + Standard
First Name Text Field.Trailing = Superview.TrailingMargin
Middle Name Text Field.Trailing = Superview.TrailingMargin
Last Name Text Field.Trailing = Superview.TrailingMargin
First Name Label.Baseline = First Name Text Field.Baseline
Middle Name Label.Baseline = Middle Name Text Field.Baseline
Last Name Label.Baseline = Last Name Text Field.Baseline
First Name Text Field.Width = Middle Name Text Field.Width
First Name Text Field.Width = Last Name Text Field.Width
First Name Text Field.Top = Top Layout Guide.Bottom + 20.0
Middle Name Text Field.Top = First Name Text Field.Bottom + Standard
Last Name Text Field.Top = Middle Name Text Field.Bottom + Standard
Attributes(设置属性)
打开属性面板,设置如下。 注意,右对齐标签文本的好处是:无论标签长度如何变化,文本永远紧贴文本框显示。
View | Attribute | Value |
---|---|---|
First Name Label | Text | First Name |
First Name Label | Alignment | Right |
First Name Text Field | Placeholder | Enter first name |
Middle Name Label | Text | Middle Name |
Middle Name Label | Alignment | Right |
Middle Name Text Placeholder | Text | Enter middle name |
Last Name Label | Text | Last Name |
Last Name Label | Alignment | Right |
Last Name Text Field | Placeholder | Enter last name |
每一组标签和文本框,前者内缩优先级应大于后者。IB已经自动设置,可以在尺寸面板验证:
Name | Horizontal hugging | Vertical hugging | Horizontal resistance | Vertical resistance |
---|---|---|---|---|
First Name Label | 251 | 251 | 750 | 750 |
First Name Text Field | 250 | 250 | 750 | 750 |
Middle Name Label | 251 | 251 | 750 | 750 |
Middle Name Text Field | 250 | 250 | 750 | 750 |
Last Name Label | 251 | 251 | 750 | 750 |
Last Name Text Field | 250 | 250 | 750 | 750 |
Discussion(分析&讨论)
简单来说,这个布局就是将Simple Label and Text Field复制三份,从上至下排列在一起。重点是如何对齐。
首先,右对齐标签文本在很大程度上简化了布局:接下来只需让齐标签后边对齐,使它们等宽,而无须顾及文本长度。因为外扩优先级大于内缩优先级,所以标签总是被拉伸;又因为所有标签等宽,所以最终宽度等由文本最长的标签固有尺寸决定。
标签前边已经相对于父视图留白约束,文本框后边也对应父视图留白。同时,行与行之间已经等宽,所以只需对齐标签后边或文本框前边即可。
做法有很多,这里我们选择让所有文本框等宽。
Dynamic Height Columns(动高组合)
本布局将Dynamic Height Label and Text Field(高度动态变化的标签和文本框)和Fixed Height Columns(纵向排列一组标签和文本框)相结合,要点包括:
- 标签后边对齐,长度以最长的标签为准;
- 文本框等宽,前后对齐;
- 文本框被拉伸,填充剩余空间,而非标签;
- 行高以较高的视图为准;
- 字体或文本改变,布局可以动态适应;
Views and Constraints(搭建布局)
依照上例排布视图,随后按照下图添加约束:
First Name Label.Leading = Superview.LeadingMargin
Middle Name Label.Leading = Superview.LeadingMargin
Last Name Label.Leading = Superview.LeadingMargin
First Name Text Field.Leading = First Name Label.Trailing + Standard
Middle Name Text Field.Leading = Middle Name Label.Trailing + Standard
Last Name Text Field.Leading = Last Name Label.Trailing + Standard
First Name Text Field.Trailing = Superview.TrailingMargin
Middle Name Text Field.Trailing = Superview.TrailingMargin
Last Name Text Field.Trailing = Superview.TrailingMargin
First Name Label.Baseline = First Name Text Field.Baseline
Middle Name Label.Baseline = Middle Name Text Field.Baseline
Last Name Label.Baseline = Last Name Text Field.Baseline
First Name Text Field.Width = Middle Name Text Field.Width
First Name Text Field.Width = Last Name Text Field.Width
First Name Label.Top >= Top Layout Guide.Bottom + 20.0
First Name Label.Top = Top Layout Guide.Bottom + 20.0 (Priority 249)
First Name Text Field.Top >= Top Layout Guide.Bottom + 20.0
First Name Text Field.Top = Top Layout Guide.Bottom + 20.0 (Priority 249)
Middle Name Label.Top >= First Name Label.Bottom + Standard
Middle Name Label.Top = First Name Label.Bottom + Standard (Priority 249)
Middle Name Text Field.Top >= First Name Text Field.Bottom + Standard
Middle Name Text Field.Top = First Name Text Field.Bottom + Standard (Priority 249)
Last Name Label.Top >= Middle Name Label.Bottom + Standard
Last Name Label.Top = Middle Name Label.Bottom + Standard (Priority 249)
Last Name Text Field.Top >= Middle Name Text Field.Bottom + Standard
Last Name Text Field.Top = Middle Name Text Field.Bottom + Standard (Priority 249)
Attributes(设置属性)
打开属性面板,设置如下。 注意,右对齐标签文本的好处是:无论标签长度如何变化,文本永远紧贴文本框显示。
View | Attribute | Value |
---|---|---|
First Name Label | Text | First Name |
First Name Label | Alignment | Right |
First Name Text Field | Placeholder | Enter first name |
Middle Name Label | Text | Middle Name |
Middle Name Label | Alignment | Right |
Middle Name Text Placeholder | Text | Enter middle name |
Last Name Label | Text | Last Name |
Last Name Label | Alignment | Right |
Last Name Text Field | Placeholder | Enter last name |
每一组标签和文本框,前者内缩优先级应大于后者。IB已经自动设置,可以在尺寸面板验证:
Name | Horizontal hugging | Vertical hugging | Horizontal resistance | Vertical resistance |
---|---|---|---|---|
First Name Label | 251 | 251 | 750 | 750 |
First Name Text Field | 250 | 250 | 750 | 750 |
Middle Name Label | 251 | 251 | 750 | 750 |
Middle Name Text Field | 250 | 250 | 750 | 750 |
Last Name Label | 251 | 251 | 750 | 750 |
Last Name Text Field | 250 | 250 | 750 | 750 |
Discussion(分析&讨论)
我们将Dynamic Height Label and Text Field(高度动态变化的标签和文本框)和Fixed Height Columns(纵向排列一组标签和文本框)中的技巧结合起来:正如前者一样,成对使用必要和可选约束,动态调整行间距;又像后者一样,右对齐标签文本,通过文本框等宽约束对齐各行。
注意
视图到控制器上方布局参照的距离为20.0pt,行间距为8.0pt。如果要求上方间距根据上方栏位存在与否动态调整——则必须添加额外约束。思路与Adaptive Single View(单个自适应视图)一致。具体实现作为课后作业留给读者。
不难看出,布局已经开始变得复杂。不过,有几种思路可以简化:首先,之前已经说过,尽量使用堆叠视图;此外,整合控件,分组布局。但不管哪种思路,本质都是分解复杂布局,降低管理难度。
Two Equal-Width Buttons(等宽按钮)
接下来我们布局两个等宽按钮:与屏幕底部对齐;平分横向空间。
Views and Constraints(搭建布局)
对齐边缘时,以IB提示的蓝色虚线为准(这是系统给出的对齐建议)。 拖拽两个按钮到画布,以IB提示的蓝色虚线为准,沿着屏幕底部对齐它们。拉伸其中一个,填充横向空间,无需担心宽度不等,稍后更新frame即可。最后按照下图添加约束:
Short Button.Leading = Superview.LeadingMargin
Long Button.Leading = Short Button.Trailing + Standard
Long Button.Trailing = Superview.TrailingMargin
Bottom Layout Guide.Top = Short Button.Bottom + 20.0
Bottom Layout Guide.Top = Long Button.Botton + 20.0
Short Button.Width = Long Button.Width
Attributes(设置属性)
为了观察frame变化,设置按钮背景颜色;为了证明文本不影响宽度,设置长短不一的按钮标题。打开属性面板,设置如下:
View | Attribute | Value |
---|---|---|
Short Button | Background | Light Gray Color |
Short Button | Title | short |
Long Button | Background | Light Gray Color |
Long Button | Title | Much Longer Button Title |
Discussion(分析&讨论)
我们只用了按钮固有高度,忽略其固有宽度。水平方向上,按钮等宽,填充横向空间;垂直方向上的思路和Two Equal-Width Views(两个等宽视图)一致:只有两条约束,而非四条。
长短不一的标题可以证明文本对布局的影响(这里没有任何影响)。
注意
本例中我们将按钮背景颜色设置为浅灰色,便于观察frame。默认,按钮和标签没有背景颜色(透明),所以很难察觉frame的变化。
Three Equal-Width Buttons(等宽按钮x3)
本例是Two Equal-Width Buttons(等宽按钮)的延伸,布局三个等宽按钮。
Views and Constraints(搭建布局)
排布视图,随后按照下图添加约束:
Short Button.Leading = Superview.LeadingMargin
Medium Button.Leading = Short Button.Trailing + Standard
Long Button.Leading = Medium Button.Trailing + Standard
Long Button.Trailing = Superview.TrailingMargin
Bottom Layout Guide.Top = Short Button.Bottom + 20.0
Bottom Layout Guide.Top = Medium Button.Bottom + 20.0
Bottom Layout Guide.Top = Long Button.Bottom + 20.0
Short Button.Width = Medium Button.Width
Short Button.Width = Long Button.Width
Attributes(设置属性)
为了观察frame变化,设置按钮背景颜色;为了证明文本不影响宽度,设置长短不一的按钮标题。打开属性面板,设置如下:
View | Attribute | Value |
---|---|---|
Short Button | Background | Light Gray Color |
Short Button | Title | short |
Medium Button | Background | Light Gray Color |
Medium Button | Title | Medium |
Long Button | Background | Light Gray Color |
Long Button | Title | Long Button Title |
Discussion(分析&讨论)
多增加一个按钮,意味着增加三条约束(水平两条,垂直一条)。水平方向上的两条约束分别负责定义按钮水平位置和宽度,因为我们忽略其固有宽度。与此同时,固有高度负责定义按钮高度,所以仍需一条约束确定按钮垂直位置。
注意
通过IB能够快速创建等宽约束:同时选中三个按钮,打开固定工具(Pin Tool),选择Equal Widths,即可生成两条等宽约束。
Two Buttons with Equal Spacing(等间距按钮)
表面看来,本布局同Two Equal-Width Buttons(等宽按钮)相似。但是,这里的按钮宽度以较长的标题为准。如果空间足够,则两个按钮宽度都以标题较长的按钮的固有宽度为准;剩余空间分为三等份,填充间隙。
iPhone上,竖屏时两种布局(等宽和等间距)看起来没区别;而横屏时(或屏幕更大的设备上,如iPad),二者区别就很明显了。
Views and Constraints(搭建布局)
拖拽两个按钮以及三个视图到画布上,穿插摆放。按照下图添加约束:
Leading Dummy View.Leading = Superview.LeadingMargin
Short Button.Leading = Leading Dummy View.Trailing
Center Dummy View.Leading = Short Button.Trailing
Long Button.Leading = Center Dummy View.Trailing
Trailing Dummy View.Leading = Long Button.Trailing
Trailing Dummy View.Trailing = Superview.TrailingMargin
Bottom Layout Guide.Top = Leading Dummy View.Bottom + 20.0
Bottom Layout Guide.Top = Short Button.Bottom + 20.0
Bottom Layout Guide.Top = Center Dummy View.Bottom + 20.0
Bottom Layout Guide.Top = Long Button.Bottom + 20.0
Bottom Layout Guide.Top = Trailing Dummy View.Bottom + 20.0
Short Button.Leading >= Superview.LeadingMargin
Long Button.Leading >= Short Button.Trailing + Standard
Superview.TrailingMargin >= Long Button.Trailing
Leading Dummy View.Width = Center Dummy View.Width
Leading Dummy View.Width = Trailing Dummy View.Width
Short Button.Width = Long Button.Width
Leading Dummy View.Height = 0.0
Center Dummy View.Height = 0.0
Trailing Dummy View.Height = 0.0
Attributes(设置属性)
为了观察frame变化,设置按钮背景颜色;为了证明宽度以文本长度为准,设置长短不一的按钮标题。打开属性面板,设置如下:
View | Attribute | Value |
---|---|---|
Short Button | Background | Light Gray Color |
Short Button | Title | short |
Long Button | Background | Light Gray Color |
Long Button | Title | Much Longer Button Title |
Discussion(分析&讨论)
不难看出,布局已经开始变得复杂。必须说明,本例旨在展示特定技巧,实际开发中最好使用堆叠视图。
我们需要间距根据父视图尺寸变化,这意味着需要一组等宽约束控。但空间是没法约束的,所以只能使用视图占位。
我们利用三个视图代表三个间距,它们是空的UIView对象,高度为0pt,以减少对于视图结构的影响。
注意
请注意:占位视图相当消耗性能,大量使用,意味着需要大量内存去绘制它们,尽管没有任何内容。
此外,占位视图会参与视图结构响应链的组成。这意味着它们会响应消息:例如执行点击测试时(Hit Testing)。消息不慎被占位视图拦截,会造成许多难以发现的bug。
也可以使用UILayoutGuide表示间距。在自动布局中,这种轻量级的对象代表一个矩形区域。布局参照(Layout Guide)不具有任何显示属性,不参与视图结构,是封装控件和表示间距的理想选择。
不幸的是,布局参照无法通过IB添加;而混合布局(IB + 代码)又十分麻烦。就难易程度而言,占位视图要比布局参照简单(IB布局)。
本例中,大于等于约束限制按钮最小间距;相等约束保证按钮等宽,间距等宽。接下来的布局交由按钮的外扩和内缩优先级完成。若空间不足,则占位视图宽度为0pt,按钮均分空间(减去标准间距);空间增加,按钮宽度增加,直至达到较大按钮固有宽度,随后占位视图宽度开始增加,充剩余空间。
Two Buttons with Size Class-Based Layouts(根据尺寸类别布局)
接下来我们将根据尺寸类别创建两套布局:一套适用于Any-Any,按钮等宽,横向排列,同Two Equal-Width Buttons(两个等宽按钮)一致。
另一套适用于Compact-Regular,按钮纵向排列,如图所示:
iPhone竖屏时按钮纵向排列;其他时候横向排列。
Constraints(搭建布局)
视图排布同“两个等宽按钮”。在Any-Any下,添加1到6号约束。
接着切换至Compact-Regular。
卸载约束2和5,按照下图所示,添加约束7,8,9。
Short Button.Leading = Superview.LeadingMargin
Long Button.Leading = Short Button.Trailing + Standard
Long Button.Trailing = Superview.TrailingMargin
Bottom Layout Guide.Top = Short Button.Bottom + 20.0
Bottom Layout Guide.Top = Long Button.Botton + 20.0
Short Button.Width = Long Button.Width
Long Button.Leading = Superview.LeadingMargin
Short Button.Trailing = Superview.TrailingMargin
Long Button.Top = Short Button.Bottom + Standard
Attributes(设置属性)
为了观察frame变化,设置按钮背景颜色;为了证明文本不影响宽度,设置长短不一的按钮标题。打开属性面板,设置如下:
View | Attribute | Value |
---|---|---|
Short Button | Background | Light Gray Color |
Short Button | Title | short |
Long Button | Background | Light Gray Color |
Long Button | Title | Much Longer Button Title |
Discussion(分析&讨论)
借助IB,我们可以基于尺寸类别配置视图,视图属性及约束。水平和垂直方向上各有三种类型(Compact,Any或Regular)尺寸类别,共计九种:四种对应具体设备类型(Compact-Compact,Compact-Regular,Regulard-Compact以及Regular-Regular);其余为基础类型,对应两种或多种尺寸类别(Compact-Any,Regular-Any,Any-Compact,Any-Regular以及Any-Any)。
系统根据尺寸类别加载最合适的布局:Any-Any适用于所有尺寸;Compact-Any对应宽度紧凑,高度任意(的尺寸);Compact-Regular宽度紧凑,高度正常(的尺寸)。尺寸类别变化——如iPhone从竖屏到横屏——布局自动切换,伴有动画。
借助这一特性,我们可以为横竖屏iPhone创建不同布局;甚至区分iPhone和iPad布局。许多细节都可以针对尺寸类别区分配置,当然,区分越细,布局越复杂,设计和维护的难度越大。
务必谨记,每个可能出现的尺寸类别都必须对应一个合法布局。除了四个具体尺寸类别外,针对基础尺寸类别的布局可以应对两个或全部(四个)具体尺寸类别(译者:例如,任意设备竖屏时的尺寸类可能为Compact-Regular和Regular-Regular,那么只需针对Any-Regular制作一套布局即可)。总之,一个简单的原则:以Any-Any为基础创建默认布局;根据需要,切换至其他具体尺寸类别,分别调整。
如果布局复杂,动手之前可以使用尺寸类别九宫格进行分析:使用布局填充四角。从图中可以看出哪些约束在多个尺寸类别下都有效,进而找到尺寸类别和布局的最佳组合。(译者:Xcode8中貌似已经移除了这一特性,所以…。好吧, 其实我从来都没用过😂)
更多关于尺寸类别的信息,详见章节Debugging Auto Layout(Debug自动布局)。