翻译@Auto Layout Guide(自动布局指南)


Advanced Auto Layout(自动布局进阶使用)

Programmatically Creating Constraints(代码布局)

尽量使用Interface Builder(界面编辑器)添加约束。可视化工具的存在便于我们编辑,管理和Debug约束。此外,IB能够实时侦测布局问题,在设计时之初将其暴露,便于定位和修复。

随着IB不断完善,其变得越来越强大。例如,几乎可以创建任意类型约束(详见Working with Constraints in Interface Builder(通过界面编辑器创建约束));针对尺寸类别创建约束(详见Debugging Auto Layout(Debug自动布局));使用堆叠视图;甚至动态添加和删除约束(详见Dynamic Stack View(动态堆叠视图))。然而,某些动态修改依然只能借助代码实现。

代码创建约束时有三种方式:布局锚点(Layou Anchor),NSLayoutConstraint以及Visual Format Lauguage(视觉格式化语言)。

Layout Anchors(布局锚点)

NSLayoutAnchor是专门用于创建NSLayoutConstraint的工厂类,其API简明,流畅。要约束一个布局元素,访问其相应的锚点属性即可。例如,视图控制器的上下布局参照提供topAnchorbottomAnchor以及heightAnchor属性。视图则暴露了同四边,中心点,尺寸以及基准线对应的锚点属性。

注意

请注意iOS视图属性layoutMarginsGuide以及readableContentGuide。它们通过UILayoutGuide代表留白和可读内容区域。布局参照(Layout Guide)本身通过布局锚点(Layout Anchor)定义自身所代表的矩形,中心点和尺寸。

通过代码约束留白和可读内容时,使用这两个属性。

布局锚点创建约束的代码紧凑,易读。根据约束类型不同,有若干API可供选择,如表13-1所示。

表13-1 布局锚点的使用

  1. // 获取代表父视图留白的布局参照
  2. let margins = view.layoutMarginsGuide
  3. // myView前边与留白前边对齐
  4. myView.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
  5. // myView后边与留白后边对齐
  6. myView.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
  7. // myView宽高比2:1
  8. myView.heightAnchor.constraint(equalTo: myView.widthAnchor, multiplier: 2.0).isActive = true

Anatomy of a Constraint(约束详解)中提到,约束可以通过线性方程表示。

图59

布局锚点有多个创建约束的方法。每个方法针对一种约束类型,需要相应参数。以下面代码为例:

  1. myView.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true

各参数作用如下:

方程 参数
元素1 myView
属性1 leadingAnchor
关系 constraintEqualToAnchor
系数 无 (默认1.0)
元素2 margins
属性2 leadingAnchor
常量 无 (默认1.0)

利用继承,NSLayoutAnchor提供类型检查:三个子类代表具体锚点类型;子类方法对参数有明确限制。这种手段彻底杜绝了非法约束。例如,水平锚点(leadAncortrailingAnchor)只能相对于其他水平锚点约束;同理,约束尺寸时才能使用系数。

注意

NSLayoutConstraint不具备这种防御特性。非法约束会在运行时造成异常。而布局锚点可以将问题提前暴露。

更多信息,详见NSLayoutAnchor Class Reference

NSLayoutConstraint Class(NSLayoutConstraint对象)

NSLayoutConstraint的类方法constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:也可以用来创建约束。此方法是对约束方程式的直接反应:一个参数对应方程的一个项(详见The constraint equation(约束等式))。

很明显,上述方法罗列所有可能参数,即使其在当前约束中无效。这会产生大量重复代码,影响可读性。表13-2中代码的作用等同于表13-1

表13-2 直接创建约束

  1. NSLayoutConstraint(item: myView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leadingMargin, multiplier: 1.0, constant: 0.0).isActive = true
  2. NSLayoutConstraint(item: myView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailingMargin, multiplier: 1.0, constant: 0.0).isActive = true
  3. 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

Visual Format Language(视觉格式化语言)

视觉格式化语言(以下简称VFL) 允许我们使用字符串(ASCII编码)表示约束,是对约束的象形表意。VFL的优缺点如下:

  • 控制台输出的Debug信息就是VFL格式,可以与约束创建代码互相呼应;
  • 允许一次创建多条约束,代码紧凑;
  • 仅能创建合法约束;
  • 强调约束的清晰表达。如果表达起来有困难(如比例宽高),则无法创建;
  • 编译器无法检查约束字符串,有错误只能在运行时抛出。

表13-3中代码的作用等同于表13-1

表13-3 使用VFL创建约束

  1. let views = ["myView" : myView]
  2. let formatString = "|-[myView]-|"
  3. let constraints = NSLayoutConstraint.constraints(withVisualFormat: formatString, options: .alignAllTop, metrics: nil, views: views)
  4. NSLayoutConstraint.activate(constraints)

上述代码创建并激活一前一后两条约束。对于VFL来说,到父视图留白默认距离为0pt,所以本例的效果完全等同于前例。然而,无法在表13-1中创建比例约束。

假设需要横向排列多个视图,VFL必须定义垂直对齐方式和水平间距。上例中,”Align All Top”其实没有作用,因为整个布局只有一个视图(父视图除外)。

通过VFL创建约束的步骤如下:

  1. 创建views字典。字符串为key,视图对象为value(或其他可以参与约束的对象,如布局参照)。key代表视图对象出现在VFL字符串中;

    注意

    使用OC时,通过宏NSDictionaryOfVariableBindings创建这个字典。使用Swift时,手动创建。

  2. (可选)创建metrics字典。字符串为key,NSNumber为value。代表常量出现在VFL字符串中;

  3. 编写VFL字符串,定义一行或一列视图;
  4. 调用NSLayoutConstraint的类方法constraintsWithVisualFormat:options:metrics:views:,创建一组约束。
  5. 调用约束方法activateConstraints:,激活约束。

更多信息,详见附录Visual Format Language