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


Debugging Auto Layout(Debug自动布局)

Debugging Tricks and Tips(Debug技巧和小贴士)

本节介绍收集和组织布局信息的技巧,及常见的反常情况。这些内容有助于我们解决问题,不论面对怎样复杂,极端的情况。

Understanding the Logs(控制台信息)

布局无法满足,或者视图方法constraintsAffectingLayoutForAxis:constraintsAffectingLayoutForOrientation:被调用时,约束信息将被输出至控制台。

这些信息相当有价值。举例来说,布局无法满足时,控制台输出如下信息:

  1. 2015-08-26 14:27:54.790 Auto Layout Cookbook[10040:1906606] Unable to simultaneously satisfy constraints.
  2. Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
  3. (
  4. "<NSLayoutConstraint:0x7a87b000 H:[UILabel:0x7a8724b0'Name'(>=400)]>",
  5. "<NSLayoutConstraint:0x7a895e30 UILabel:0x7a8724b0'Name'.leading == UIView:0x7a887ee0.leadingMargin>",
  6. "<NSLayoutConstraint:0x7a886d20 H:[UILabel:0x7a8724b0'Name']-(NSSpace(8))-[UITextField:0x7a88cff0]>",
  7. "<NSLayoutConstraint:0x7a87b2e0 UITextField:0x7a88cff0.trailing == UIView:0x7a887ee0.trailingMargin>",
  8. "<NSLayoutConstraint:0x7ac7c430 'UIView-Encapsulated-Layout-Width' H:[UIView:0x7a887ee0(320)]>"
  9. )
  10. Will attempt to recover by breaking constraint
  11. <NSLayoutConstraint:0x7a87b000 H:[UILabel:0x7a8724b0'Name'(>=400)]>
  12. Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
  13. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

从中我们可以看出五个约束互相冲突,无法同时满足。需要选定一个进行移除,或转换为可选约束。

还好,视图结构很简单:父视图包含一个标签和一个文本框。约束之间的关系如下:

  1. 标签宽度大于等于400pt;
  2. 标签前边与父视图留白对齐;
  3. 标签到文本框的间距为8pt;
  4. 文本框后边与父视图留白对齐;
  5. 父视图宽度为320pt。

为了修复布局,系统选择打破标签宽度约束(第一条约束)。

注意

上述信息使用视觉格式化语言(Visual Format Language,以下简称VFL)描述约束。即便不使用VFL创建约束,但为了理解Debug信息,必须掌握VFL。更多信息,详见Visual Format Language

上述约束中,最后一个由系统创建,无法修改。不难看出,其明显与第一个约束冲突:父视图宽度仅为320pt时,标签宽度不可能达到400pt。不过,无需移除第一个约束,只需将其优先级置为999即可。系统会在满足其他约束的前提下,尽量满足它。

根据自动缩放掩码生成的约束包含相应掩码信息(视图属性translatesAutoresizingMaskIntoConstraintsYES时)。从约束对象地址后面开始,h=v=分别表示水平和垂直方向,各对应三个字符。每个字符都代表一个值,可能为-&,前者代表距离固定,后者代表距离可变。水平方向上,这三个字符分别代表左边留白,宽度,右边留白;垂直方向上,分别代表上边留白,高度,下边留白。

例如,有约束信息如下:

  1. <NSAutoresizingMaskLayoutConstraint:0x7ff28252e480 h=--& v=--& H:[UIView:0x7ff282617cc0(50)]>"

将其分解:

  • NSAutoresizingMaskLayoutConstraint:0x7ff28252e480:约束类型和地址。这里,我们可以看出这个约束基于自动缩放掩码生成。

  • h=--& v=—&:自动缩放掩码,这里是默认值。水平方向上,前边留白固定,宽度固定,后边留白可变。垂直方向上,上边留白固定,高度固定,下边留白可变。换言之,不论父视图尺寸如何变化,视图左上角的位置和宽度不变。

  • H:[UIView:0x7ff282617cc0(50)]:约束的VFL描述。这里定义了一个宽度为50pt的视图,最后还显示了受此约束影响的视图对象的信息。

Adding Identifiers to the Logs(标注视图和约束)

上面的例子都很简单,但随着约束不断增加,信息越来越多,理解起来也越来越困难。不过,通过给视图和约束添加标签,有助于我们理解约束信息。

如果视图有文本信息,则Xcode将这一信息作为标签使用。如标签文本内容,按钮标题以及文本框占位文字。在IB中,如果身份检视面板中Label一栏有值,则优先使用它作为标签,展示在IB以及控制台信息中。

对于约束来说,通过属性面板或代码设置约束属性identifier后,约束信息将使用这个值表示相应约束。

下述信息中,视图和约束已经进行了标注:

  1. 2015-08-26 14:29:32.870 Auto Layout Cookbook[10208:1918826] Unable to simultaneously satisfy constraints.
  2. Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
  3. (
  4. "<NSLayoutConstraint:0x7b58bac0 'Label Leading' UILabel:0x7b58b040'Name'.leading == UIView:0x7b590790.leadingMargin>",
  5. "<NSLayoutConstraint:0x7b56d020 'Label Width' H:[UILabel:0x7b58b040'Name'(>=400)]>",
  6. "<NSLayoutConstraint:0x7b58baf0 'Space Between Controls' H:[UILabel:0x7b58b040'Name']-(NSSpace(8))-[UITextField:0x7b589490]>",
  7. "<NSLayoutConstraint:0x7b51cb10 'Text Field Trailing' UITextField:0x7b589490.trailing == UIView:0x7b590790.trailingMargin>",
  8. "<NSLayoutConstraint:0x7b0758c0 'UIView-Encapsulated-Layout-Width' H:[UIView:0x7b590790(320)]>"
  9. )
  10. Will attempt to recover by breaking constraint
  11. <NSLayoutConstraint:0x7b56d020 'Label Width' H:[UILabel:0x7b58b040'Name'(>=400)]>
  12. Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
  13. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

理解起来更容易了,有没有?

Visualizing Views and Constraints(可视化)

Xcode允许我们从视图结构的角度观察视图和约束。

利用模拟器进行如下操作(译者:真机上的操作类似):

  1. 在模拟器上运行App;
  2. 切换至Xcode;
  3. 选择Debug—>View Debugging—>Show Alignment Rectangles。我们将看到一个个矩形,每个矩形代表一个视图视图,称为边缘矩形(Alignment Rectangle)。

图68

自动布局利用这些矩形布局视图。这个选项有助于我们迅速发现尺寸异常的视图。

通过点击Debug工具栏上的View Hierarchy按钮,能够获取更多信息。视图结构将以3D形式展示在Xcode中,我们可以旋转角度,点击任意视图,探索上下级结构。在处理自动布局问题时,Show clipped contentShow Constraints工具格外有用。

图69

Show clipped content选项允许显示被剪裁掉的内容。Show Constraints显示所有影响当前选中视图的约束。它们有助于快速找出潜在问题视图。

更多详细,详见Debug Area Help(Debug区域帮助)

Understanding Edge Cases(边缘矩形特殊情况)

边缘矩形直接影响布局最终效果。下述特例会导致异常:

  • 自动布局系统根据视图的边缘矩形对其布局,而非frame。大多数情况下,二者相等。然而,某些视图会重新定义边缘矩形,避免将某些部分纳入布局计算(如角标)。

    更多信息,详见UIView Class ReferenceAligning Views with Auto Layout部分。

  • iOS视图属性transform能够缩放,旋转,甚至移动视图;然而,这些形变不会影响布局计算,始终以基于原始frame生成的边缘矩形为准;

  • 视图可以显示其bounds以外的内容。默认,视图不允许这种情况发生。然而,出于性能考虑,绘图引擎不做检查。这意味着视图尺寸(特别是自行绘制内容的视图)可能与其frame不符。

    通过设置视图属性clipsToBoundsYES,或比对视图frame的size,可以验证这个问题。

  • 视图保持固有高度,布局属性(Layout Attribute)NSLayoutAttributeBaselineNSLayoutAttributeFirstBaseline以及NSLayoutAttributeLastBaseline能够正确对齐文本。然而,一旦垂直方向上有缩放,文本会错位。

  • 对于视图结构来说,约束优先级是一种全局属性。封装视图(如利用堆叠视图,布局参照(Layout Guide)或容器视图)可以大大简化布局。然而,约束优先级无法被同时封装。模块内优先级仍然可以与其他模块优先级互相作用。
  • 比例约束将水平和垂直方向上的约束联系起来。通常,两个方向上的约束分开计算。然而,如果将视图高度依赖宽度,则二者互相影响。这些关系极大的增加了布局复杂度,可能影响布局其他部分,导致冲突。