- 原文:Auto Layout Guide
- 作者:Apple
- 更新:[email protected]
尽量使用Interface Builder(界面编辑器)添加约束。可视化工具的存在便于我们编辑,管理和Debug约束。此外,IB能够实时侦测布局问题,在设计时之初将其暴露,便于定位和修复。
随着IB不断完善,其变得越来越强大。例如,几乎可以创建任意类型约束(详见Working with Constraints in Interface Builder(通过界面编辑器创建约束));针对尺寸类别创建约束(详见Debugging Auto Layout(Debug自动布局));使用堆叠视图;甚至动态添加和删除约束(详见Dynamic Stack View(动态堆叠视图))。然而,某些动态修改依然只能借助代码实现。
代码创建约束时有三种方式:布局锚点(Layou Anchor),NSLayoutConstraint以及Visual Format Lauguage(视觉格式化语言)。
NSLayoutAnchor是专门用于创建NSLayoutConstraint的工厂类,其API简明,流畅。要约束一个布局元素,访问其相应的锚点属性即可。例如,视图控制器的上下布局参照提供topAnchor
,bottomAnchor
以及heightAnchor
属性。视图则暴露了同四边,中心点,尺寸以及基准线对应的锚点属性。
注意
请注意iOS视图属性layoutMarginsGuide以及readableContentGuide。它们通过UILayoutGuide代表留白和可读内容区域。布局参照(Layout Guide)本身通过布局锚点(Layout Anchor)定义自身所代表的矩形,中心点和尺寸。
通过代码约束留白和可读内容时,使用这两个属性。
布局锚点创建约束的代码紧凑,易读。根据约束类型不同,有若干API可供选择,如表13-1所示。
表13-1 布局锚点的使用
// 获取代表父视图留白的布局参照
let margins = view.layoutMarginsGuide
// myView前边与留白前边对齐
myView.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
// myView后边与留白后边对齐
myView.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
// myView宽高比2:1
myView.heightAnchor.constraint(equalTo: myView.widthAnchor, multiplier: 2.0).isActive = true
Anatomy of a Constraint(约束详解)中提到,约束可以通过线性方程表示。
布局锚点有多个创建约束的方法。每个方法针对一种约束类型,需要相应参数。以下面代码为例:
myView.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
各参数作用如下:
方程 | 参数 |
---|---|
元素1 | myView |
属性1 | leadingAnchor |
关系 | constraintEqualToAnchor |
系数 | 无 (默认1.0) |
元素2 | margins |
属性2 | leadingAnchor |
常量 | 无 (默认1.0) |
利用继承,NSLayoutAnchor
提供类型检查:三个子类代表具体锚点类型;子类方法对参数有明确限制。这种手段彻底杜绝了非法约束。例如,水平锚点(leadAncor或trailingAnchor)只能相对于其他水平锚点约束;同理,约束尺寸时才能使用系数。
注意
NSLayoutConstraint不具备这种防御特性。非法约束会在运行时造成异常。而布局锚点可以将问题提前暴露。
更多信息,详见NSLayoutAnchor Class Reference。
NSLayoutConstraint的类方法constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:也可以用来创建约束。此方法是对约束方程式的直接反应:一个参数对应方程的一个项(详见The constraint equation(约束等式))。
很明显,上述方法罗列所有可能参数,即使其在当前约束中无效。这会产生大量重复代码,影响可读性。表13-2中代码的作用等同于表13-1。
表13-2 直接创建约束
NSLayoutConstraint(item: myView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leadingMargin, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: myView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailingMargin, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: myView, attribute: .height, relatedBy: .equal, toItem: myView, attribute:.width, multiplier: 2.0, constant:0.0).isActive = true
注意
iOS枚举NSLayoutAttribute中含有代表视图留白的Case。其作用等同于视图属性layoutMarginsGuide。然而,视图属性readableContentGuide代表可读内容区域,没有其他选择。
与布局锚点(Layout Anchor)相比,这种方式不能区分约束特点;可读性不高,容易遗漏重点。而且,不能利用静态检查发现非法约束。除非需要支持iOS8,OS X v10.10或更早版本,否则强烈推荐使用布局锚点。
更多信息,详见NSLayoutConstraint Class Reference。
视觉格式化语言(以下简称VFL) 允许我们使用字符串(ASCII编码)表示约束,是对约束的象形表意。VFL的优缺点如下:
- 控制台输出的Debug信息就是VFL格式,可以与约束创建代码互相呼应;
- 允许一次创建多条约束,代码紧凑;
- 仅能创建合法约束;
- 强调约束的清晰表达。如果表达起来有困难(如比例宽高),则无法创建;
- 编译器无法检查约束字符串,有错误只能在运行时抛出。
表13-3中代码的作用等同于表13-1。
表13-3 使用VFL创建约束
let views = ["myView" : myView]
let formatString = "|-[myView]-|"
let constraints = NSLayoutConstraint.constraints(withVisualFormat: formatString, options: .alignAllTop, metrics: nil, views: views)
NSLayoutConstraint.activate(constraints)
上述代码创建并激活一前一后两条约束。对于VFL来说,到父视图留白默认距离为0pt,所以本例的效果完全等同于前例。然而,无法在表13-1中创建比例约束。
假设需要横向排列多个视图,VFL必须定义垂直对齐方式和水平间距。上例中,"Align All Top"其实没有作用,因为整个布局只有一个视图(父视图除外)。
通过VFL创建约束的步骤如下:
-
创建
views
字典。字符串为key,视图对象为value(或其他可以参与约束的对象,如布局参照)。key代表视图对象出现在VFL字符串中;注意
使用OC时,通过宏NSDictionaryOfVariableBindings创建这个字典。使用Swift时,手动创建。
-
(可选)创建
metrics
字典。字符串为key,NSNumber为value。代表常量出现在VFL字符串中; -
编写VFL字符串,定义一行或一列视图;
-
调用NSLayoutConstraint的类方法constraintsWithVisualFormat:options:metrics:views:,创建一组约束。
-
调用约束方法activateConstraints:,激活约束。
更多信息,详见附录Visual Format Language。