知行

自动布局指南

Note:到目前(2016-07-13)官方文档已经更改三次,下文可看可不看,到官方文档的传送门



本文翻译自官方文档

自动布局(Auto Layout)介绍

对于一个应用,它的界面元素之间有一定的关系,而自动布局是对这些关系的数学上的描述。通过使用自动布局可以达到布局的目的。

自动布局从Xcode5开始内建在IB(Interface Builder)。在iOSOS X上同样适用。

Auto layout

自动布局概念

约束(constraint是自动布局的基本建筑块。约束是用来表达你的界面元素之间的规则;例如,你可以创建一个约束用来指定一个元素的宽,或者它与另一个元素之间的水平距离。通过创建、移除或者改变约束来影响界面的布局。

当计算界面元素的运行时位置,自动布局系统同时考虑所有的约束,通过这样的方式设置元素位置以最佳满足所有的约束。

约束基础

你可以认为一个约束是一个人们描述语句的数学上的表现。例如,定义一个button的位置,你可能这样描述button的左边距离包含该按钮视图的左边20点。更正式地讲,这句话转换为:button.left = (container.left + 20),这就对应表达式 y = m * x + b

  • yx 是这些视图的属性
  • mb 是浮点类型的值

一个 属性 可以是 leftrighttopbottomleadingtrailingwidthheightcenterXcenterYbaseline 中的一个。

属性 leadingtrailng 在像中文这样从左到右书写的语言中,它们跟 leftright 等同,但在从右到左书写的语言跟 rightleft 等同。当你创建约束时,默认使用 leadingtrailng。如果约束跟语言有关,则使用 leadingtrailng;若否,则使用 leftright

约束有其它属性可以设置:

  • 常量(Constant value)。约束的物理上的尺寸或偏移,以为单位。
  • 关系(Relation)。自动布局不仅仅只支持视图属性的固定值,你还可以使用关系和不等式,例如,一个视图 width >= 20,或者 textView.leading >= superView.leading + 20
  • 优先级(Priority level)。约束有优先级。优先满足一个有高优先级的约束。默认的优先级是必须(NSLayoutPriorityRequired),它的意思就是必须正确地满足这个约束。布局系统尽可能满足一个可选约束,甚至不能完成。

    有了优先级,你就可能表达有用的条件控制行为。

约束是累计的,并不会相互覆盖。如果已经存在一个约束,你设置另一个相同类型的约束,并不会覆盖之前的那个。例如,设置第二个跟视图的宽有关的约束不会移除或改变第一个跟视图的宽有关的约束–你需要手动移除第一个约束。

约束能跨越视图层次,不过有些限制条件。

如果视图层次包含一个自定义实现了 UIViewlayoutSubview 方法的视图,那么你就不能设置一个跨越视图层次的约束。也不能跨越那些有 bounds 变换的视图(像 scrollview )。你可以认为这些视图是障碍–不能通过。

有着内在的内容尺寸

buttons 这样的叶级视图通常比定位它们的代码更了解它们应该是什么样的大小。一个视图包含一些内容,而布局系统不能自然地了解,但是通过内在的内容尺寸交流,它告诉布局系统那些内容多大。

对于元素,例如 text 标签,你通常应当设置该元素变成它的内在的尺寸(选择 Editor > Size To Fit Content )。这意味着该元素会随着不同语言的不同内容适当地放大或缩放。

应用架构

Auto Layout 架构分发控制器和视图之间的布局责任。视图变得更自我组织,而不是写一个全知的控制器对于一个给定的几何结构来计算视图需要到哪儿。这种方式减少了控制器的逻辑上的复杂性,并且可以更容易地重新设计视图,而不需要改变相应的布局代码。

你可能仍然需要一个控制器在运行时添加、移除或者调整约束。

控制器的角色

虽然一个视图指定它的内在内容尺寸,视图的用户说,这是多么的重要。例如,默认,一个 button:

  • 强烈希望在竖直方向上紧挨它的内容(按钮真滴应该是它们的天然高度)。
  • 不强求在水平方向上紧挨它的内容(在标题和边缘之间有额外的填充是可以接受的)。
  • 强烈抵抗在两个方向上的压缩或裁剪。

例如,在含有彼此相邻的两个按钮的用户界面,如果有额外的空间,它依赖于控制器来决定这些按钮怎样增长。应当只是其中一个按钮增长?应当两个相同的增长?或者应该按比例地?如果没有足够的空间而不压缩或裁剪的内容,以适应这两个按钮,应当其中一个按钮首先被裁断?或者两者一样?等等。

你使用 setContentHuggingPriority:forAxis:setContentCompressionResistancePriority:forAxis: 设置一个 UIView 的实例。默认地,视图的有一个值,要么 NSLayoutPriorityDefaultHigh,要么 NSLayoutPriorityDefaultLow


IB 中使用约束

最简单的添加、编辑或者移除约束的方式就是在 IB 中使用可视化布局工具。在两个视图之间 Control-dragging 会弹出各种窗口,使用这些窗口,你就可以简单的一次创建一个或多个约束。

添加约束

当你从 Object Library 中拖出一个元素,并把它拖到 IB 中的画布,该元素开始的时候是无约束的,这是为了通过拖动附件的元素容易地制作界面原型。如果在没有添加任何约束到一个元素上时,你创建并运行,你将发现 IB 修理该元素的宽和高,并且相对于父视图的左上角固定位置;这意味着改变窗口的大小不会移动或改变该元素的位置。为了使你的界面正确的响应大小或方向上的改变,你需要开始添加约束。

Important: 尽管当你创建用户界面时没有合适的约束,Xcode 并不会产生警告或错误,你不应当在这样的状态运行你的应用。

依赖于你想要的精确度的级别和你想要一次添加约束的数量,有几种方式来添加约束。

通过 Control-Drag 添加约束

像创建连接到 IBOutletIBAction,在画布上按住 Ctrl 键盘,从一个视图上拖拽,是创建一个约束最快的方式。
针对单个约束,当你明确知道你想要的约束的类型和你想把它放到何处,那么 Control-Drag 是一种快速、简洁的方式。

你可以从一个元素 Control-Drag 到它自己,到它的包含者,或者到另一个元素。取决于你拖拽向的什么以及拖拽的方向,Auto Layout
适当地限制约束的可能性。例如,如果你从一个元素水平向右拖拽向它的包含者,你有两个选项:固定它到包含者的尾空间;垂直居中。

Tip: Control-Drag 弹出的菜单中多选,按着 CommandShift 键。

通过 AlignPin 菜单添加约束

你也可以使用在 IB 画布中的 Auto Layout menu 来添加约束。

除了为了对齐或者调节间隔而添加约束,你还可以使用这个菜单来解决布局问题,并且决定约束改变行为。

  • 对齐(Align)。创建对齐约束,像把一个视图置于它的容器中心,或者两个视图的左边对齐。
  • 固定(Pin)。创建间距约束,像定义一个视图的高,或者指定它距离另一个视图的水平距离。
  • 问题(Issues)。通过添加或重新设计基于建议的约束来解决布局上的问题。
  • 调整(Resizing)。指定调整如何影响约束。

如果你只选择了一个约束,那么需要多个元素的约束选项是被禁止使用的。

通过对齐或固定菜单添加约束

  1. 选中恰当的约束旁边的复选框。
    为了选择“到最近的邻居的约束”,选中元素的适当的边所对应的红色的约束。

    如果你需要创建的一个跟其它视图(不是最近的邻居)相关的约束,点击文本框中的黑色下三角显示附件其它视图的下来菜单。
  2. 输入相应的常量。
  3. 点击按钮来创建约束。
    • 添加约束按钮添加新的约束到选中的元素。
    • 添加和更新框架按添加新的约束到选中的元素,并且在你的界面上移动元素位置以尽可能的满足每个约束。

    Note: 每次点击这个两个按钮中一个或两个都是添加新的约束,并不是编辑已存在的约束。

添加缺失的或建议的约束

如果你需要一个布局上的起点或者需要快速地做许多改变,那么可以使用“问题菜单”来添加约束。

如果你需要添加大量的约束来描述你的界面布局,并且不想每次只添加一个约束,可以选择 Issues -> Add Missing Constraints 来添加清楚的约束集合。这个命令基于事物的分布位置进行推断约束。

如果你需要没有错误滴恢复一系列的约束,或者你只是想重新开始,可以选择 Issues -> Reset to Suggested Constraints 来移除错误的约束并添加清楚的约束集合。这个命令等同于 Clear Constraints 然后 Add Missing Constraints

编辑约束

你可以改变约束的常量、关系和优先级。你可以在画布上双击约束并且编辑值,或者选中约束并使用属性检查器。然而,你不能改变约束的类型。

删除约束

选中删除。

以编程方式使用自动布局

尽管界面生成器为使用自动布局提供一个方便的可视化界面,但是你也可以通过代码创建、添加、移除和调整约束。例如,如果你在
运行时添加或移除视图,你将需要以编程的方式添加约束来保证你的界面正确地响应方向或大小的改变。

编程创建约束

使用NSLayoutConstraint的实例表示约束。通常使用constraintsWithVisualFormat:options:metrics:views:来创建约束。

这个方法的第一个问题,“可视化格式化字符串(visual format string)”,为你想要描述的布局提供一个可视化陈述。可视化格式化字符串被设计的尽可能地可读;
一个视图在中括号中表示,视图之间的一个连接使用连接符表示(或者使用被视图分隔的距离的数值分开的两个连接符)。

例如,你可能想表示两个按钮之间的约束:

使用下面的可视化格式化字符串:

1
[button1]-12-[button2]

单个连接符表示标准的间隔,所以可以表示成这样:

1
[button1]-[button2]

视图的名字来源于views字典–这些键是你在视觉形式字符串中使用的名字,这些值对应视图对象。与人方便,NSDictionaryOfVariableBindings 创建一个字典,它的键对应视图变量的名字。用代码创建约束变成:

1
2
3
4
5
NSDictionary *viewsDictionary =
NSDictionaryOfVariableBindings(self.button1, self.button2);
NSArray *constraints =
[NSLayoutConstraint constraintsWithVisualFormat:@"[button1]-[button2]"
options:0 metrics:nil views:viewsDictionary];

视觉形式语言更喜欢良好的可视化表达性上的完整性。尽管在实际用户界面上的大多数约束可以使用视觉形式语言,但是有一些是不可以的。长宽比是一个有用的但是不能表达的约束(例如imageView.width = 2 * imageView.height)。为了创建这样的约束,可以使用constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:

你也可以使用这个方法创建之前的[button1]-[button2]约束:

1
2
3
4
5
6
7
[NSLayoutConstraint constraintWithItem:self.button1
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self.button2
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:-12.0];