自动布局指南-Part 1:入门

翻译自“Auto Layout Guide”。

1 入门

1.1 理解自动布局

自动布局根据视图层级结构中视图上的约束,动态计算所有视图的尺寸和位置。例如,可以约束一个按钮相对于一个图片视图水平居中,并让按钮的上边缘与图片的下边缘距离总是8个点。如果图片视图的尺寸或位置发生改变,按钮的位置自动调整匹配。

这种基于约束方法的设计允许你构建的用户界面可以动态的响应内部和外部变化。

1.1.1 外部变化

外部变化发生在父视图的尺寸或形状改变时。对于每一次变化,都必须更新视图层级结构的布局,充分利用可用空间。以下是一些引起外部变化的常见原因:

  • 用户重新调整窗口的尺寸(OS X)。
  • 用户在iPad上进入或离开分割视图(iOS)。
  • 设备旋转(iOS)。
  • 来电和音频录音栏出现或消失(iOS)。
  • 想要支持不同的尺寸类(size class)。
  • 想要支持不同的屏幕尺寸。

这些变化中的大部分可以在运行时发生,并且它们要求应用程序动态响应。其它的,比如支持不同屏幕尺寸,表示应用程序自适应不同的环境。即使屏幕尺寸不会在运行时改变,创建一个自适应界面让应用程序可以在iPhone 4S和iPhone 6 Plus,甚至iPad上运行良好。自动布局也是iPad上支持Slide Over和分割视图的关键组件。

1.1.2 内部变化

内部变化发生在用户界面上视图或控件的尺寸改变时。

以下是一些引起内部变化的常见原因:

  • 应用程序显示的内容发生改变。
  • 应用程序支持国际化。
  • 应用程序支持Dynamic Type(iOS)。

应用程序的内容发生改变时,新内容可能需要不一样的布局。这通常发生在显示文本或图片的应用程序中。例如,一个新闻应用程序需要根据单个新闻文章的内容调整它的布局。类似的,图片拼接必须处理各种各样的图片尺寸和长宽比。

国际化是让应用程序可以适应不同语言,地区和文化的过程。国际化应用程序的布局必须考虑这些区别,让应用程序在支持的所有语言和地区都能正确显示。

国际化在三个主要方面影响布局。首先,翻译用户界面到不同语言时,标签需要不同的空间。比如,通常德语比英语需要更多空间。通常日语需要的空间更少。

第二,即使语言一样,不同地区表示日期和数字的格式不同。尽管这些改变通常比语言改变更细微,但用户界面仍然需要细微的调整尺寸。

第三,改变语言不仅仅影响文本的尺寸,还影响布局的组织。不同的语言使用不同的布局方向。例如,英语使用从左到右的布局方向,阿拉伯语和希伯来语使用从右到左的布局方向。通常,用户界面元素的顺序应该匹配布局方向。如果英语中按钮在视图的右下角,那阿拉伯语中按钮应该在左下角。

最后,如果iOS应用程序支持dynamic type,用户就可以改变应用程序中的字体大小。这可以同时改变用户界面中文本元素的高度和宽度。如果用户在应用程序运行时改变了字体大小,字体和布局都必须适配。

1.1.3 自动布局VS基于框架(Frame-Based)布局

布局用户界面有三种主要的方法。通过代码布局用户界面,使用autoresizing masks自动响应一些外部变化,或者使用自动布局。

通常,应用程序通过代码设置视图层级结构中每一个视图的框架(frame)来布局用户界面。框架定义了视图在父视图坐标系中的原点,高度和宽度。

想要布局用户界面,需要计算视图层级结构中每一个视图的尺寸和位置。如果发生改变,需要重新计算所有受影响视图的frame。

在很多方面来说,通过代码定义视图的frame提供了最大的灵活性和最强大的功能。发生变化时,可以准确地做出任何想要的改变。但因为你还必须自己管理所有变化,所以布局一个简单的用户界面就需要大量的努力来设计,调试和维护。创建一个真正自适应的用户界面增加了一个数量级的难度。

可以使用autoresizing masks降低一些难度。当视图的父视图frame改变时,autoresizing mask定义了视图的frame如何改变。这简化了创建适应外部变化的布局。

然而autoresizing masks只支持一小部分合适的布局。对于复杂的用户界面,通常需要在代码中增加autoresizing masks。另外,autoresizing masks只适应外部变化,不支持内部变化。

Autoresizing masks只是通过代码迭代完善布局,而自动布局(Auto Layout)是一种全新的模式。它不考虑视图的frame,而是考虑它们的关系。

自动布局使用一系列约束定义用户界面。约束通常代表了两个视图之间的关系。然后自动布局根据这些约束计算每个视图的尺寸和位置。这样产生的布局可以同时响应内部和外部变化。

用于创建特定行为而设计的一组约束的逻辑,跟编写过程式或面向对象代码的逻辑不一样。幸运的是,精通自动布局和精通任何编程技术是一样的。有两个基本步骤:首先需要理解基于约束布局背后的逻辑,然后需要学习API。学习其它编程技术时,你已经成功的完成了这些步骤。自动布局也不例外。

该指南剩下的内容是让帮助你更容易的过渡到自动布局。“没有约束的自动布局”描述了一个高层次的抽象概念,它简化了用户界面背后自动布局的创建。“剖析约束”提供了你需要理解的背景原理,然后就可以成功的与自动布局交互。“在界面生成器中使用约束”描述了设计自动布局的工具,“通过代码创建约束”和“自动布局细则手册”详细讲述了API。最后,“自动布局细则手册”呈现了各种复杂度的布局实例,可以学习并在项目中使用,“调试自动布局”提供了自动布局出错时,修复错误的建议和工具。

1.2 没有约束的自动布局

堆栈视图(stack view)提供了一种利用自动布局能力的简单方式,而且没有引入复杂的约束。单个堆栈视图定义了用户界面元素的一行或一列。堆栈视图根据它的属性排列这些元素。

  • axis:(仅UIStackView)定义了堆栈视图的方向,垂直或水平。
  • orientation:(仅NSStackView)定义了堆栈视图的方向,垂直或水平。
  • distribution:定义了视图沿堆栈视图方向的布局。
  • alignment:定义了视图沿堆栈视图的正交方向上的布局。
  • spacing:定义了相邻视图的间隔。

可以在界面生成器中拖拽一个垂直或水平的堆栈视图到画布。然后把内容拖拽到堆栈中。

如果一个对象的内容尺寸是固定的,那么它在堆栈中的尺寸不变。如果没有固定尺寸,界面生成器会提供一个默认尺寸。可以重新调整对象的尺寸,并且界面生成器添加约束来维持它的大小。

可以使用属性检查器(Attributes inspector)修改堆栈视图的属性更一步调整布局。例如,下面的例子使用8个点的间隔和平均分配。

堆栈视图还根据被排列视图的content-hugging和compression-resistance优先级组织布局。可以使用尺寸检查器(Size inspector)修改这些属性。

提示
可以通过直接添加约束到被排列的视图进一步修改布局;但要避免任何可能的冲突:一个基本原则,在给定的维度(dimension)中,如果视图的尺寸默认回到它的固有内容尺寸(intrinsic content size),则可以安全的为该尺寸添加约束。更多关于约束冲突的信息,请参考“不可满足的约束”。

另外,可以在堆栈视图中嵌套其它堆栈视图,构建更复杂的布局。


一般来说,尽可能使用堆栈视图管理布局。只有在仅使用堆栈布局不能达到目的时,才创建约束。

更多关于使用堆栈视图的信息,请参考“UIStackView Class Reference”或者“NSStackView Class Reference”。

提示
尽管创造性使用嵌套堆栈视图可以生成复杂的用户界面,但你还是需要使用约束。至少,总是需要约束来定义最外层堆栈的位置(可能还有尺寸)。

1.3 剖析约束

视图层级结构的布局由一系列线性方程式定义。每个约束代表一个方程式。你的目标是声明有且只有一个可能答案的一系列方程式。

一个简单的方程式如下所示。


该约束表示红色视图的开头边界(leading edge)必须在蓝色视图结尾边界(trailing edge)之后8个点。该方程式由以下部分组成:

  • Item 1。方程式的第一项——这里是红色视图。该项必须是视图或布局向导(layout guide)。
  • Attribute 1。被约束的第一项的属性——这里是红色视图的开头边界。
  • Relationship。左右两边的关系。该关系可以是这三个值的其中一个:等于,大于等于或小于等于。在这里,左右两边是相等的关系。
  • Multiplier。Attribute 2的值乘以该浮点数。在这里是1.0。
  • Item 2。方程式的第二项——这里是蓝色视图。与第一项不同,该项可以为空。
  • Attribute 2。被约束的第二项的属性——这里是蓝色视图的结尾边界。如果第二项为空,该项必须是Not an Attribute
  • Constant。一个常量,浮点偏移量——这里是8.0。该值被加到attribute 2上。

大部分约束定义了用户界面中两个项之间的关系。这些项可以是视图或布局向导。约束还可以定义同一个项的两个不同属性的关系,例如,设置某一项的宽度和高度的长宽比。还可以给某一项的高度或宽度赋值为常量值。使用常量值时,如果第二项为空,则第二项的属性设置为Not an Attribute,乘数设为0.0。

1.3.1 自动布局属性

在自动布局中,属性定义了可以被约束的特征。通常包括四个边界(开头,结尾,顶部和底部),高度,宽度,以及纵向和横向中心。文本项还包括一个或多个基线(baseline)属性。

完整的属性列表请参考“NSLayoutAttribute”枚举。

提示
尽管OS X和iOS都使用了NSLayoutAttribute枚举,但它们定义的值略有不同。查看属性的完整列表时,确保查看的是正确的平台文档。

1.3.2 方程式示例

这些方程式使用的各种各样的参数和属性让你可以创建许多不同类型的约束。可以定义视图之间的间隔和视图边界的对齐,定义两个视图的相对尺寸,甚至定义视图的长宽比。但不是所有属性都是兼容的(compatible)。

有两种基本类型的属性。尺寸属性(比如高度和宽度)和位置(比如开头,左边和顶部)属性。尺寸属性指定项的大小,不指定任何位置。位置属性指定相对于其它项的位置,不指定项的尺寸。

认识到这些差异后,应用以下规则:

  • 不能使用尺寸属性约束位置属性。
  • 不能给位置属性赋值为常量值。
  • 不能给位置属性使用不同的(nonidentity)乘数(不是1.0的值)。
  • 对于位置属性,不能使用纵向属性约束横向属性。
  • 对于位置属性,不能使用Leading或Trailing属性约束Left或Right属性。

例如,如果没有额外的上下文,设置项的Top为常量值20.0没有意义。必须总是定义项相对于其它项的位置属性,例如在父视图的顶部下20.0个点。但设置项的高度为20.0完全有效。更多信息请参考”值的解释“。

列表3-1展示了各种通用约束的方程式例子。

提示

本章所有示例房程序都使用伪代码。要查看使用真实代码表示的约束,请参考”通过代码创建约束“或”自动布局细则手册“。

列表3-1 常见约束的示例方程式

// 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

1.3.3 相等,不是赋值

请注意,示例中的方程表示相等,而不是赋值。

自动布局求解方程式时,不仅仅是把右边的值赋给左边。而是计算attribute 1和attribute 2的值,让方程式的关系为真。这意味着可以自由的调整方程式项的顺序。例如,列表3-2中的方程式与列表3-1中的方程式完全一样。

列表3-2 反向的方程式

// 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

提示
重新调整项的顺序时,确保倒置了乘数和常量。例如,常量8.0变为-8.0,乘数2.0变为0.5。常量0.0和乘数1.0保持不变。

你会发现自动布局常常为同一个问题提供了多种解决方式。最好选择最清楚描述你意图的解决方案。但是不同的开发者肯定会争论哪个方案是最好的。此时,一致性比正确性更好。如果选择一种方法,并一直使用它,长期以往,会让你遇到更少的问题(跳过更多的坑)。比如,本指南遵循以下规则:

  1. 整数乘数比小数乘数好。
  2. 正常量比负常量好。
  3. 在布局顺序中,视图尽可能:从开头到结尾,从顶部到底部。

1.3.4 创建没有歧义,可满足的布局

使用自动布局时,目的是提供一系列方程式,这些方程式有且只有一个可能的解。有歧义的约束有多个可能的解。不满足的(Unsatisfiable)约束没有有效的解。

通常,约束必须同时定义每个视图的尺寸和位置。假设父视图的尺寸已确定(例如,iOS中屏幕的根视图),一个没有歧义的,可满足的布局中每个视图每个维度(dimension)需要两个约束(不包括父视图)。但是你有很多种选择,使用哪些约束。例如,下面的三个布局都是没有歧义的,可满足的布局(只显示水平方向的约束):


  • 第一个布局中,约束视图的开头边界相对于父视图的开头边界。同时设置视图为固定宽度。根据父视图的尺寸和其它约束可以计算出结尾边界的位置。
  • 第二个布局中,约束视图的开头边界相对于父视图的开头边界。同时约束视图的结尾边界相对于父视图的结尾边界。根据父视图的尺寸和其它约束可以计算出视图的宽度。
  • 第三个布局中,约束视图的开头边界相对于父视图的开头边界。同时居中对齐视图和父视图。然后,根据父视图的尺寸和其它约束可以计算出视图的宽度和结尾边界的位置。

请注意,每个布局有一个视图和两个水平方向的约束。在每种情况中,约束都同时完全的定义了视图的宽度和水平位置。这意味着所有布局在水平坐标轴上都是没用歧义的,可满足的布局。但是这些布局不是同样的有效。考虑父视图的宽度改变时会发生什么。

第一个布局中,视图的宽度不会改变。大多数时候这不是你想要的。实际上,避免为视图分配固定尺寸是一条通用规则。自动布局的目的是创建可以动态适应环境的布局。任何时候给视图一个固定尺寸,就削弱了它的能力。

或许不是很明显,第二个和第三个布局产生同样的行为:父视图宽度改变时,它们都让视图和父视图边距保持不变。但它们不一定相等。通常,第二个例子更容易理解,但第三个例子可能更有用,尤其是居中对齐多个项时。与往常一样,为具体布局选择最好的方法。

现在考虑更复杂的情况。想象你要在iPhone上并排显示两个视图。想要保证它们在所有边缘都有一定的边距,并且宽度相同。设备旋转时,也可以正确的重新调整大小。

如下图所示,纵向和横向显示视图:


这些约束应该如何设置?下图展示了一种直接的方法:


上面的方法使用以下约束:

// 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

参照之前的规则,这个布局有两个视图,四个水平约束和四个垂直约束。这不是一个行之有效的指南,只表明你的方向正确。更重要的是,这些约束唯一的指定了两个视图的尺寸和位置,产生了一个没有歧义的,可满足的布局。移除任何一个约束,布局变为有歧义的。添加额外的约束,会引入冲突。

但这不是唯一可能的方法。下面是一个同样有效的方法:


对齐蓝色盒子和红色盒子的顶部,而不是相对父视图指定蓝色盒子的顶部和底部。同样,对齐蓝色盒子和红色盒子的底部。这些约束如下所示:

// 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

这个例子中仍然由两个视图,四个水平约束和四个垂直约束。它仍然产生了一个没有歧义的,可满足的布局。

哪一个更好?
这些方案都产生了有效的布局。哪一个更好?
不幸的是,几乎不可能证明哪个方案明显比其它的好。每个方案都有自己的优点和缺点。
当移除一个视图时,第一个方案更健壮。从视图层级结构中移除一个视图,会同时移除引用该视图的所有约束。所以,如果移除了红色视图,蓝色视图还有三个约束。只需要添加一个约束就能再产生一个有效布局。在第二个方案中,移除红色视图后,蓝色视图只剩一个约束。
另一方面,在第一个方案中,如果想要视图的顶部和底部对齐,必须保证它们的顶部和底部约束使用相同的常量值。如果改变其中一个常量,必须同时修改其它几个。

1.3.5 不等式约束

到目前为止,实例中的约束都是等式,这只是其中一部分。约束也可以表示不等式。具体来说,约束的关系可以是等于,大于等于或者小于等于。

例如,可以使用约束定义视图的最小或最大尺寸(列表3-3)。

列表3-3 指定最小和最大尺寸

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

只要开始使用不等式,就打破了每个视图每个维度有两个约束的规则。总是可以使用两个不等式代替单个相等关系。列表3-4中,单个相等关系和一对不等式产生同样的行为。

列表3-4 使用两个不等式代替单个相等关系

// A single equal relationship
Blue.leading = 1.0 * Red.trailing + 8.0
 
// Can be replaced with two inequality relationships
Blue.leading >= 1.0 * Red.trailing + 8.0
Blue.leading <= 1.0 * Red.trailing + 8.0

反之不一定对,因为两个不等式不总是等价于单个相等关系。例如,列表3-3中的不等式显式视图宽度的可能值范围——但它们自己并没有定义宽度。还需要额外的水平约束在这个范围内定义视图的位置和尺寸。

1.3.6 约束优先级

默认情况下,所有约束都是必须的。自动布局必须计算一个解满足所有的约束。如果不能,则会发生一个错误。自动布局在控制台打印不满足约束的信息,并选择打破其中一个约束。然后重新计算不包括被打破约束的解。更多信息请参考“不满足的布局”。

也可以创建可选的约束。所有约束都有1-1000的优先级。优先级为1000的约束是必须的。其它约束都是可选的。

计算解时,自动布局尝试从高优先级到底优先级满足所有约束。如果不能满足可选的约束,会跳过该约束并继续计算下一个约束。

即使一个可选约束都不能满足,还是会影响布局。如果跳过约束后,布局存在不确定性,系统会选择最接近约束的解。用这种方法,不满足的可选约束会把视图拉向它们。

可选约束通常与不等式联合工作。例如,在列表3-4中,可以为两个不等式提供不同的优先级。大于等于关系是必须的(优先级为1000),小于等于关系的优先级更低(250)。这意味着蓝色视图与红色视图的距离不能小于8个点。但其它约束可以把它拖得更远。同时,可选约束把蓝色视图拉向红色视图,确保最近的距离是8个点,因为要考虑布局中的其它约束。

提示
不要强迫使用所有1000个优先级值。实际上,优先级通常聚集在系统定义的低(250),中(500),高(750)和必须(1000)周围。可能需要让约束的优先级比这些值高或低1-2点,来帮助阻止绑定(teis)。如果优先级远远超出这些值,可能需要重新检查布局逻辑。

iOS中预定义的约束常量列表请参考“UILayoutPriority”。OS X请参考“ Layout Priorities constants”。

1.3.7 固有内容尺寸(Intrinsic Content Size)

到目前为止,所有例子都使用约束同时定义视图的位置和尺寸。但是有些视图根据当前内容带有尺寸。这就是它们的固有内容尺寸。例如,按钮的固有内容尺寸是它的标题尺寸加上一个小页边留白(margin)。

不是所有视图都有固有内容尺寸。对于有固有内容尺寸的视图,它可以定义视图的高度,宽度或者两者都定义。表格3-1列出了一些例子。

表格3-1 普通控件的固有内容尺寸

视图 固有内容尺寸
UIView和NSView 没有固有内容尺寸
滑动条(Slider) 只定义了宽度(iOS)。
根据Slider的类型,定义了宽度,高度或两者都定义了(OS X)。
标签,按钮,开关和文本框 同时定义了宽度和高度。
文本视图和图片视图 固有内容尺寸可以改变。

固有内容尺寸基于视图的当前内容。标签或按钮的固有内容尺寸基于显示的文本总数和使用的字体。其它视图的固有内容尺寸更复杂。例如,一个空的图片视图没有固有内容尺寸。一旦添加了图片,固有内容尺寸设置为图片的尺寸。

文本视图的固有内容尺寸依赖于内容,是否可滚动和其它应用到视图的约束。例如,如果可滚动,视图没有固有内容尺寸。如果不能滚动,默认情况下,根据没有换行符的文本尺寸计算视图的固有内容尺寸。例如,如果文本中没有换行(returns),当成一行文本计算布局内容需要的宽度和高度。如果添加约束指定视图的宽度,固有内容尺寸定义显示需要的高度。

自动布局为每个维度使用一对约束来表示视图的固有尺寸内容。内容紧靠(content hugging)拉紧视图,使视图紧密的围绕在内容周围。压缩阻力(compression resistance)向外推送视图,使得视图不会裁剪内容。

这些约束使用列表3-5中的不等式定义。这里,IntrinsicHeight和IntrinsicWidth常量表示视图固有内容尺寸的高度和宽度值。

列表3-5 压缩阻力和内容紧靠方程式

// 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

每个约束都有自己的优先级。默认情况下,内容紧靠使用250优先级,压缩阻力750优先级。因此拉伸视图比压缩视图更容易。对于大多数控件,这都是希望的行为。例如,可以安全的把一个按钮拉伸得比固有尺寸更大;如果压缩按钮,它的内容可能被裁剪。请注意,界面生成器可以偶尔修改这些属性来阻止绑定(ties)。更多信息,请参考“设置Content-Hugging和Compression-Resistance优先级”。

只要有可能就在布局中使用固有内容尺寸。可以让布局动态的适应视图内容改变。同时还减少了创建没有歧义和冲突的布局的约束数量,但是仍需要管理content-hugging和compression-resistance(CHCR)的优先级。以下是处理固有内容尺寸的指南:

  • 当拉伸一系列视图来填满一个空间时,如果所有视图的content-hugging优先级相同,那么布局是有歧义的。自动布局不知道应该拉伸哪个视图。
    一个常见的示例是一对标签和文本框。通常,希望文本框拉伸填充额外的空间,而标签仍然保持固有内容尺寸。为了达到这种效果,确保文本框的水平content-hugging优先级比标签的小。
    实际上,这个示例太普遍了,界面生成器自动为你处理了,设置所有标签的content-hugging优先级为251。如果通过代码创建布局,需要自己修改content-hugging优先级。
  • 当带有不可见背景(比如按钮和标签)的视图不小心被拉伸,从而超过它们的固有内容尺寸时,通常会发生奇怪和意想不到的布局。真正的问题可能并不明显,因为文本简单的显示在错误的位置。通过增加content-hugging优先级来阻止不希望的拉伸。
  • 基线约束只在视图固有尺寸的高度下工作。如果视图被垂直拉伸或压缩,基线约束不能再正确的对齐。
  • 有些视图,比如开关,应该总是在它们的固有内容尺寸显示。根据需要增加它们的CHCR,阻止拉伸或压缩。
  • 避免给视图设置必须的CHCR优先级。通过,让一个视图的尺寸出错比不小心创建了一个冲突更好。如果视图需要总是保持固有内存尺寸,考虑使用非常高(999)的优先级代替。这种方法通常可以防止视图被拉伸或压缩,但仍然提供了一个紧急压力阀,用于视图在一个比你预期更大或更小的环境中显示时。

1.3.7.1 固有内容尺寸VS合适尺寸(Fitting Size)

固有内容尺寸作为自动布局的一个输入。当视图有固有内容尺寸时,系统产生约束来表示该尺寸,并用约束计算布局。

另一方面,合适尺寸是自动布局引擎的一个输出。它是根据视图的约束计算出来的视图尺寸。如果视图使用自动布局来布局子视图,那么系统就可以根据它的内容给视图计算出一个合适尺寸。

堆栈视图是一个很好的例子。排除其它任何约束,系统根据堆栈视图的内容和属性计算它的尺寸。从很多方面来说,堆栈视图好像有固有内容尺寸:仅仅使用一个垂直和一个水平约束定义位置,就能创建有效的布局。但它的尺寸是自动布局计算出来的——它不是自动布局的输入。设置堆栈视图的CHCR优先级没有效果,因为堆栈视图没有固有内容尺寸。

如果想要相对于堆栈视图外面的项来调整堆栈视图的合适尺寸,可以创建显式约束来捕获这些关系,或者相对于堆栈外面的项,来修改堆栈内容的CHCR优先级。

1.3.8 值的解释

自动布局的值总是使用点。但是,这些度量单位的确切意义会根据涉及的属性和视图的布局方向发生改变。

自动布局属性 注释
Height
Width
视图的尺寸 这些属性可以赋值为常量值或者组合其它高度和宽度属性。这些值不能为负数。
Top
Bottom
Baseline
向下移动屏幕时,这些值随着增加。 这些属性只可以与Center Y,Top,Bottom和Baseline组合。
Leading
Trailing
向结尾边界移动时,这些值会增加。对于从左到右的布局方向,向右移动时这些值会增加。对于从右到左的布局方向,向左移动时这些值会增加。 这些属性只可以与Leading,Trailing,或Center X属性组合。
Left
Right
向右移动时这些值会增加。 这些属性只可以与Left,Right,和Center X属性组合。
避免使用Left和Right属性。使用Leading和Trailing代替。这样可以让布局适应视图的阅读方向。
默认情况下,阅读方向由用户设置的当前语言决定。但是可以在需要时覆盖它。在iOS中切换从左到右和从右到左的语言时,设置持有约束的视图(约束影响的所有视图的最近的公有祖先)的semanticContentAttribute属性,来指定内容的布局是否翻转。
Center X
Center Y
根据方程式中其它属性来解释。 Center X可以由Center X,Leading,Trailing,Right和Left属性组合。
Center Y可以由Center Y,Top,Bottom和Baseline属性组合。

1.4 在界面生成器中使用约束

在界面生成器中设置自动布局约束有三种主要的选择:在视图之间control-drag,使用Pin和Align工具,以及让界面生成器设置约束,然后编辑或修改结果。每种方法都有优缺点。大部分开发者喜欢其中一种方式;但是熟悉所有三个方式让你可以根据手头的任务切换工具。

对于所有三个方式,都是从Object库中拖拽视图和控件到屏幕上开始。根据需要调整它们的尺寸和位置。在画布上放置视图时,界面生成器自动创建一组原型约束(prototyping constraints),它们定义了视图相对于左上角的当前尺寸和位置。

应用程序可以使用原型约束构建和运行。使用这些约束快速可视化和测试用户界面,之后需要使用自己的显式约束替换隐式约束。永远不要发布使用原型约束的应用程序。

一旦创建了第一个约束,系统会从该约束涉及的视图中移除所有原型约束。没有了原型约束,布局不再有足够的约束来唯一计算所有视图的尺寸和位置。它变为有歧义的布局。受影响的约束立即变为红色,并且Xcode会产生一些警告。

不要恐慌。继续添加约束,直到完成布局。一旦添加了一个约束,你需要负责添加所有需要的约束,用来创建没歧义的,可满足的布局。

关于修改布局警告和错误的更多信息,请参考“调试自动布局”。

1.4.1 Control-Dragging约束

要在两个视图之间创建约束,Control-click其中一个约束,并拖拽到另外一个。

释放鼠标时,界面生成器显示一个HUD菜单,列出所有可能的约束。

界面生成器根据正在添加约束的项和拖拽手势的方向,聪明的选择一组约束。如果差不多水平拖拽,获得设置视图之间水平间隔的选项,以及垂直对齐视图的选项。如果差不多垂直拖拽, 获得设置视图之间垂直间隔的选项,以及水平对齐视图的选项。两个手势都可能包括其它选项(例如设置视图的相对尺寸)。

提示
可以在画布和场景文档大纲(document outline)的图标中使用Control-drag。当想要拖拽约束到很难查找到的项,例如顶部或底部的布局向导时很有用。当拖拽到文档大纲,或者从文档大纲拖拽时,界面生成器不会根据手势方向过滤可能的约束列表。

界面生成器基于视图的当前frame创建约束。因此,拖拽约束时需要仔细的放置视图。如果基于界面生成器的指南排列视图,应该以一组合理的约束结束。如果需要,之后总是可以编辑约束。

Control-dragging提供了一种快速设置约束的方式;但是,因为约束的值是从场景的当前布局推断出来的,所以很容易偏差一个点。如果想要更好的控制,创建后检查和编辑约束,或者使用Pin和Align工具。

关于Control-dragging约束的更多信息,请参考“Auto Layout Help”中的“Adding Layout Constraints by Control-Dragging”。

1.4.2 使用Stack,Align,Pin和Resolve工具

界面生成器在编辑窗口的右下角提供了四个自动布局工具。分别是Stack工具,Align工具,Pin工具和Resolve Auto Layout Issues工具。

创建约束时想要更好的控制,或者想要一次设置多个约束时,使用Pin和Align工具。使用这些工具另一个优点是,创建约束之前,不需要精确的放置视图。相反,你可以大概设置视图的相对位置,添加约束,然后更新框架(frame)。此时自动布局为你计算正确的位置。

1.4.2.1 Stack工具

Stack工具可以快速的创建堆栈视图。在布局中选择一个或多个项,然后点击Stack工具。界面生成器堆栈视图中嵌入选中项,并根据堆栈的内容调整为当前合适尺寸。

提示
系统从视图的初始相对位置推断堆栈的方向。可以使用属性检查器修改方向和对齐(以及设置分布和间隔)。

1.4.2.2 Align工具

Align工具可以快速对齐布局中的项。选择想要对齐的项,然后单击Align工具。界面生成器显示一个弹出框视图,其中包括一些可能的对齐选项。

选择其中的选项来对齐选中的视图,然后点击Add Constraints按钮。界面生成器创建约束来实现这些对齐。默认情况下,约束没有任何的偏移量(edge或中心相互对齐),并且约束添加后,框架(frame)都不会更新。可以在创建约束之前修改这些设置。

使用Align工具之前,通常选择两个或多个视图。而Horizontally in Container或Vertically in Container约束可以添加到单个视图上。使用弹出框一次可以创建任意数量的约束——尽管一次创建多个约束没什么意义。

更多信息请参考“Auto Layout Help”中的“Adding Auto Layout Constraints with the Pin and Align Tools”。

1.4.2.3 Pin工具

Pin工具可以快速定义视图相对于它邻居的位置,或者快速定义视图的尺寸。选择想要固定(pin)位置或尺寸的项,然后点击Pin工具。界面生成器显示一个弹出框视图,其中包括一些选项。

弹出框顶部区域可以固定选中项的开头,顶部,结尾或底部边缘到它最近的邻居。关联的数字表示画布中两个项之间的当前间隔。可以输入自定义的间隔,或者点击三角形,设置它被约束到哪个视图,或者选择标准间隔。Constrain to margins复选框决定约束使用父视图的页边留白(margin)还是它的边缘(edge)。

弹出框的下面区域可以设置项的宽度或高度。Width和Height约束默认是当前画布尺寸,可以输入不同的值。Aspect Ratio约束使用项的当前长宽比;如果想要改变这个比例,需要创建之后检查和编辑约束。

通常,选择单个视图固定;但也可以选择两个或多个视图,并指定它们的宽度或高度相等。也可以一次创建多个约束,或者添加约束后更新框架。设置好选项后,点击Add Constraints按钮创建约束。

更多信息请参考“Auto Layout Help”中的“Adding Auto Layout Constraints with the Pin and Align Tools”。

1.4.2.4 Resolve Auto Layout Issues工具

Resolve Auto Layout Issues工具提供了一些选项用来修复常见的自动布局问题。菜单的上半部分只影响当前选中的视图。下半部分选项影响场景中所有视图。

可以使用这个工具更新视图的框架(frame),基于当前的约束,或者根据视图在画布中的当前位置更新约束。还可以添加缺失的约束,清理约束,或者重置视图为界面生成器推荐的约束。

添加或重置约束的命令在“让界面生成器创建约束”中详细讨论。

1.4.3 让界面生成器创建约束

界面生成器可以为你创建部分或全部约束。使用这个方法时,界面生成器根据视图在画布中的当前尺寸和位置,尝试推导出最佳的约束。务必小心的放置视图——空间中细微的差别会导致大不相同的布局。

点击Resolve Auto Layout Issues tool > Reset to Suggested Constraints,可以让界面生成器创建所有约束。界面生成器为选中视图(或者场景中的所有视图)创建必需的约束。

或者自己添加一些约束,然后点击Resolve Auto Layout Issues tool > Add Missing Constraints。该选项会添加约束,生成没有歧义的布局。同时,可以为选中视图或者场景中所有视图添加约束。

该方法可以快速的构建一个没有歧义的,可满足的布局。但是,除非用户界面很简单直接,否则布局的结构可能不是你期望的。总是测试用户界面,并修改约束,直到获得想要的结果。

1.4.4 查找和编辑约束

添加约束后,需要可以找到,查看和编辑。有多种选择可以访问约束。每中选择提供唯一的方法来组织和显示约束。

1.4.4.1 在画布中查看约束

编辑器用带颜色的线条在画布中显示影响当前选中视图的所有约束。形状,笔画类型和线条颜色可以告诉你很多关于当前约束的状态。

  • 工字型(I-bars,带结束端的T形线条)。工字型显示空间的尺寸。该空间可以是两个项的距离,或者项的高度或宽度。
  • 平线(Plain lines,不带结束端的直线)。平线显示边缘(edge)在哪对齐。例如,对齐两个或多个视图的开头边缘时,界面生成器使用简单的线条。这些线条也可以用于连接空间为0个点的项。
  • 实线。实线表示必需的约束(优先级 = 1000)。
  • 虚线。虚线表示可选的约束(优先级 < 1000)。
  • 红色的线。该约束影响的其中一个项有错误。可能项存在有歧义的布局,或者布局不满足。更多信息查看问题导航器或者界面生成器中大纲视图的箭头。
  • 橙色的线。橙色的线表示,根据当前的约束集,该约束影响的其中一个项的框架不在正确的位置。同时,界面生成器用虚线轮廓显示计算出来的框架位置。可以使用Resolve Auto Layout Issues tool > Update Frames指令移动项到计算出来的位置。
  • 蓝色的线。该约束影响的项存在一个没有歧义的,可满足的布局,并且项的框架在自动布局引擎计算出来的正确位置。
  • 相等标识(Equal Badges)。界面生成器用单独的栏(a separate bar)为每一项显示的约束,该约束指定了两个项有相同宽度或高度。两个栏都用蓝色标识,其中包括一个等于号。
  • 大于等于和小于等于标识。界面生成器用带有>=或<=符号的小蓝色标识标记所有标识大于等于和小于等于关系的约束。

1.4.4.2 在文档大纲中列出约束

界面生成器在文档大纲中列出所有约束,把它们在拥有它们的视图下面分组。约束被最近的,同时包括约束中两个项的视图拥有。这种情况下,每个视图包括自己和所有子视图,场景的根视图包括顶部和底部的布局向导。

尽管约束分散在大纲中,但大部分约束在场景的根视图结束。如果想要找出所有的约束,需要展开整个视图层级结构。

约束使用伪代码列出。通常这些列表都很长,并且以类似的视图集开头,所以必须增加大纲的宽度才能看见有意义的信息。在大纲中选中的约束会在画布中高亮显示。使用这个特征可以快速定位约束。

对于简单的场景,大纲是浏览场景中所有约束的好地方。布局变得更复杂后,就会很难找到特定的约束。最好一次只检查一个视图的约束——通过在画布中选中布局,或者在尺寸检查器中检查视图。

1.4.4.3 在属性检查器中查找约束

属性检查器列出了影响当前选中视图的所有约束。必需的约束以实现显示,可选的约束以虚线显示。描述列出了约束的重要信息。它总是包括受影响的属性和约束中的另一项。它还可能包括关系,常量值,以及乘数或比例。

上图中顶部图表显示了约束影响的属性。通过选择图表的一个或多个属性来过滤约束列表。然后列表只显示影响选中属性的约束列表。

更多信息请参考“Auto Layout Help”中的“Viewing the Complete List of Layout Constraints for an Item”。

1.4.4.4 检查和编辑约束

在画布或者文档大纲中选择约束时,属性检查器显示该约束的所有属性。其中包括约束方程式的所有值:第一项,关系,第二项,常量和乘数,还有约束的优先级和标识符。

提示:可以给约束的标识符属性提供一个描述性名称,这样可以在控制台日志和其它调试任务中更容易的确定约束。

还可以标记约束为一个占位符(placeholder)。这些约束只在设计时存在。应用程序运行时,布局不包括这些约束。当你计划运行时动态添加约束时,通常添加占位符约束。通过临时添加约束来创建没有歧义的,可满足的布局,可以清除界面生成器中的所有警告或错误。

可以自由的修改常量(Constant),优先级(Priority),乘数(Multiplier),关系(Relation),标识符(Identifier)和占位符(Placeholder)属性。第一项和第二个项的选项有更多的限制。可以交换第一项和第二项(根据需要反转乘数和常量)。还可以改变项的属性,但不能改变项本身。如果需要移动约束到另一项,需要删除约束,然后添加新约束。

有些编辑可以直接在尺寸检查器中修改。在约束的弹出框中点击Edit按钮,可以修改约束的关系,常量,优先级,或者乘数。想要额外的修改,需要双击约束,然后在属性检查器中打开。

更多信息请参考“Auto Layout Help”中的“Editing Auto Layout Constraints”。

1.4.5 设置Content-Hugging和Compression-Resistance优先级

在画布或者文档大纲中选择视图,来设置视图的content-hugging和compression-resistance优先级(CHCR优先级)。打开尺寸检查器,向下滚动,找到Content Hugging Priority和Compression Resistance Priority设置。

还可以在界面生成器中设置视图的固有内容尺寸。默认情况下,界面生成器使用视图的intrinsicContentSize方法返回的尺寸。设计时如果需要不同的尺寸,可以设置一个占位符固有内容尺寸。该占位符只在界面生成器中影响视图的尺寸,运行时没有任何影响。

更多信息请参考“Auto Layout Help”中的“Setting the Placeholder Intrinsic Size for a Custom View”。

1.4.6 只支持iOS的特征

iOS添加了一些独有的特征与自动布局交互。其中包括顶部和底部布局向导,视图的布局页边留白,视图的可读内容向导和视图的语义内容。

1.4.6.1 顶部和底部布局向导(Top and Bottom Layout Guides)

顶部和底部布局向导表示当前激活的视图控制器的可见内容区域的上下边缘(edge)。如果不想让你的内容扩展到透明或半透明的UIKit栏(例如,状态栏,导航栏,或者标签栏),使用自动布局来固定内容到各自的布局向导。

布局向导遵循UILayoutSupport协议,它给向导一个长度属性,用于测量向导和各自视图边缘的距离。具体来说:

  • 对于顶部布局向导,长度用点(point)表示视图控制器视图的顶部和视图上面最底部栏的底部之间的距离。
  • 对于底部布局向导:长度用点(point)表示视图控制器视图的底部和视图上面的顶部栏(里布标签栏)之间的距离。

这些向导可以作为约束中的项,支持顶部,底部和高度属性。通常,约束视图到顶部布局向导的底部属性,或者底部布局向导的顶部属性。这些向导还提供了topAnchor,bottomAnchor和heightAnchor属性,用于简化通过代码创建约束。

创建约束到根视图的顶部或底部边缘时,界面生成器根据情况自动提供顶部和底部布局向导,作为选项。如果布局向导时视图最近的邻居,界面生成器默认使用向导。使用Pin工具时,可以根据需要在布局向导和根视图边缘之间切换,通过点击三角形。

1.4.6.2 布局的页边留白(Margin)

自动布局为每个视图定义了页边留白。这些页边留白描述了视图边缘和它的子视图之间的推荐空间。可以使用layoutMargins或layoutMarginsGuide属性访问视图的页边留白。可以使用UIEdgeInsets结构体获得和设置layoutMargins属性。layoutMarginsGuide使用UILayoutGuide对象提供只读访问。另外,使用preservesSuperviewLayoutMargins属性决定视图的页面留白怎么与它父视图的页边留白交互。

默认的页边留白是每边8个点。可以根据应用程序的需要,修改这些值。

提示:系统设置和管理视图控制器根视图的页边留白。顶部和底部页边留白设置为0个点,这样可以更容易扩展内容到栏下(如果有的话)。侧边的页边留白根据控制器如何和在哪里显示而变化,可以是16或者20个点。不能修改这些页边留白。

当约束视图到它的父视图时,通常使用布局页边留白代替视图的边缘。在UIKit中,NSLayoutAttribute枚举定义了一些属性代表顶部,底部,开头,结尾,左边和右边页边留白,以及相对于页边留白的center X和center Y属性。

在界面生成器中,在视图和它的父视图之间使用Control-dragging添加约束时,默认使用页边留白属性。当使用Pin工具是,可以切换“Constrain to margins”复选框。如果选中它,约束使用父视图的页边留白属性。如果没有选中它,使用父视图的边缘。类似的,在属性检查器中编辑约束时,First Item和Second Item的下拉菜单包括一个“Relative to margin”选项。选中时使用页边留白属性,没选中时使用边缘。

最后,当通过代码创建到父视图的页边留白的约束时,使用layoutMarginsGuide属性,并直接创建约束到布局向导。这让你可以使用向导的布局锚点来创建约束,提供了更容易阅读的精简API。

1.4.6.3 可读内容向导(Readable Content Guides)

视图的readableContentGuide属性包括一个布局向导,其中定义了视图中文本对象最大的最近宽度。理想情况下,内容足够狭窄,用户不用移动头就能阅读。

该向导总是在视图的布局页边留白中居中,并且永远不会扩展这些页边留白。向导的尺寸总是根据系统动态类型(dynamic type)的尺寸变化。当用户选择更大字体时,系统创建更宽的向导,因为用户阅读时,通常会更远的拿着设备。

在界面生成器中,可以设置视图的页边留白代表布局的页边留白还是可读内容向导。选择视图(通常是视图控制器的根视图),然后打开尺寸检查器。如果选择Follow Readable Width复选框,添加到视图页边留白的任何约束都使用可读内容向导。

提示:对于大部分设备来说,可读内容向导和布局页边留白之间只有很细微的,或者没有区别。只有在横屏方向的iPad上时,区别才很明显。

1.4.6.4 语义内容

如果使用开头和结尾约束布局视图,当在从左到右的语言(比如英语)和从右到左的语言(比如阿拉伯语)之间切换时,视图自动翻转位置。但是有些界面元素不会根据阅读方向改变它们的位置。例如,基于物理方向(上,下,左,右)的按钮总是保持同样的相对方向。

视图的semanticContentAttribute属性决定了,当在从左到右的语言和从右到左的语言之间切换时,视图的内容是否应该翻转。

在界面生成器的属性检查器中设置Semantic选项。如果值为Unspecified,视图的内容跟随阅读方向翻转。如果设置为Spatial,Playback,或者Force Left-to-Right,内容总是使用开头边缘在左边,结尾边缘在右边的布局。强制Right-to-Left总是使用开头边缘在右边,结尾边缘在左边的布局。

1.4.7 经验法则

以下指南会帮助你成功的掌握布局。毫无疑问,这些规则的每一个都一些合理的例外。如果你决定改变它们,行动之前请仔细考虑你的方法。

  • 永远不要使用视图的frame,bounds或者center属性指定它的几何形状。
  • 尽可能使用堆栈视图。
    堆栈视图管理它们内容的布局,极大的简化了布局其余的约束逻辑。只有当堆栈视图没有提供需要的行为时,才依靠自定义约束。
  • 在视图和它最近的邻居之间创建约束。
    如果两个按钮紧挨着,约束第二个按钮的开头边缘到第一个按钮的结尾边缘。通常第二个按钮不应该有约束跨过第一个按钮到视图的边缘。
  • 避免给视图固定的高度或宽度。
    自动布局的重点是动态响应变化。设置固定尺寸消除了视图自适应的能力。但是你可能想要设置视图的最小或最大尺寸。
  • 如果设置约束时遇到困难,尝试使用Pin和Align工具。尽管这些工具比Control-dragging慢一些,但它们可以让你在创建约束之前,核实精确的值和项。这个额外的明智的检查很有用,尤其是你第一次使用时。
  • 自动更新项的框架(frame)时需要小心。如果项没有足够的约束来完全指定它的尺寸和位置,那么更新的行为是未定义的。视图常常会消失,因为它们的高度或宽度设为0,或者因为它们不小心放置到屏幕之外。
    可以总是尝试更新项的框架,如果需要的话,之后撤销这些改变。
  • 确保布局中所有视图都有有意义的名字。这样的话,使用工具时可以更容易确定视图。
    系统根据标签和按钮的文本或标题命名它们。对于其它视图,可以在身份检查器(Identity inspector)中设置Xcode Specific Label(或者在文档大纲中双击并编辑视图的名字)。
  • 总是使用开头和结尾约束代替右和左。
    可以使用视图的semanticContentAttribute属性(iOS)或userInterfaceLayoutDirection(OS X)调整视图如何解释开头和结尾边缘。
  • 在iOS中,当约束一个项到视图控制器根视图的边缘时,使用以下约束:
  • 水平约束。对于大多数控件,使用0个点的约束到布局页边留白。系统根据设备类型和应用程序如何显示视图控制器,自动提供正确的空间。
    对于从页边留白到页边留白填充根视图的文本对象,使用可读内容向导代替布局页边留白。
    对于需要从边缘到边缘填充根视图的项(例如背景图片),使用视图的开头和结尾边缘。
  • 垂直约束。如果视图延伸到栏之下,使用顶部和底部页边留白。具体来说,对滚动视图很常见,允许内容滚动到栏下面。注意,可能需要修改滚动视图的contentInset和scrollIndicatorInsets属性,正确的设置内容的初始位置。
    如果视图没有延伸到栏下面,约束视图到顶部和底部布局向导。
  • 使用代码实例化视图时,确保设置它们的translatesAutoresizingMaskIntoConstraints属性为NO。默认情况下,系统根据视图的框架(frame)和它的autoresizing mask,自动创建约束集。当添加自己的约束时,它们肯定与自动生成的约束冲突。这创建了不可满足的布局。
  • 了解OS X和iOS计算布局的差异。
    在OS X中,自动布局可以修改窗口的内容和尺寸。
    在iOS中,系统提供场景的尺寸和布局。自动布局只可以修改场景的内容。
    这些差异看起来不太重要,但它们对如何设计布局,尤其是如何使用优先级,有深远的影响。

推荐阅读更多精彩内容