翻译@Auto Layout Guide(自动布局指南)
Debugging Auto Layout(Debug自动布局)
Debugging Tricks and Tips(Debug技巧和小贴士)
本节介绍收集和组织布局信息的技巧,及常见的反常情况。这些内容有助于我们解决问题,不论面对怎样复杂,极端的情况。
Understanding the Logs(控制台信息)
布局无法满足,或者视图方法constraintsAffectingLayoutForAxis:或constraintsAffectingLayoutForOrientation:被调用时,约束信息将被输出至控制台。
这些信息相当有价值。举例来说,布局无法满足时,控制台输出如下信息:
2015-08-26 14:27:54.790 Auto Layout Cookbook[10040:1906606] Unable to simultaneously satisfy constraints.
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)
(
"<NSLayoutConstraint:0x7a87b000 H:[UILabel:0x7a8724b0'Name'(>=400)]>",
"<NSLayoutConstraint:0x7a895e30 UILabel:0x7a8724b0'Name'.leading == UIView:0x7a887ee0.leadingMargin>",
"<NSLayoutConstraint:0x7a886d20 H:[UILabel:0x7a8724b0'Name']-(NSSpace(8))-[UITextField:0x7a88cff0]>",
"<NSLayoutConstraint:0x7a87b2e0 UITextField:0x7a88cff0.trailing == UIView:0x7a887ee0.trailingMargin>",
"<NSLayoutConstraint:0x7ac7c430 'UIView-Encapsulated-Layout-Width' H:[UIView:0x7a887ee0(320)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7a87b000 H:[UILabel:0x7a8724b0'Name'(>=400)]>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
从中我们可以看出五个约束互相冲突,无法同时满足。需要选定一个进行移除,或转换为可选约束。
还好,视图结构很简单:父视图包含一个标签和一个文本框。约束之间的关系如下:
- 标签宽度大于等于400pt;
- 标签前边与父视图留白对齐;
- 标签到文本框的间距为8pt;
- 文本框后边与父视图留白对齐;
- 父视图宽度为320pt。
为了修复布局,系统选择打破标签宽度约束(第一条约束)。
注意
上述信息使用视觉格式化语言(Visual Format Language,以下简称VFL)描述约束。即便不使用VFL创建约束,但为了理解Debug信息,必须掌握VFL。更多信息,详见Visual Format Language。
上述约束中,最后一个由系统创建,无法修改。不难看出,其明显与第一个约束冲突:父视图宽度仅为320pt时,标签宽度不可能达到400pt。不过,无需移除第一个约束,只需将其优先级置为999即可。系统会在满足其他约束的前提下,尽量满足它。
根据自动缩放掩码生成的约束包含相应掩码信息(视图属性translatesAutoresizingMaskIntoConstraints为YES
时)。从约束对象地址后面开始,h=
和v=
分别表示水平和垂直方向,各对应三个字符。每个字符都代表一个值,可能为-
或&
,前者代表距离固定,后者代表距离可变。水平方向上,这三个字符分别代表左边留白,宽度,右边留白;垂直方向上,分别代表上边留白,高度,下边留白。
例如,有约束信息如下:
<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
后,约束信息将使用这个值表示相应约束。
下述信息中,视图和约束已经进行了标注:
2015-08-26 14:29:32.870 Auto Layout Cookbook[10208:1918826] Unable to simultaneously satisfy constraints.
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)
(
"<NSLayoutConstraint:0x7b58bac0 'Label Leading' UILabel:0x7b58b040'Name'.leading == UIView:0x7b590790.leadingMargin>",
"<NSLayoutConstraint:0x7b56d020 'Label Width' H:[UILabel:0x7b58b040'Name'(>=400)]>",
"<NSLayoutConstraint:0x7b58baf0 'Space Between Controls' H:[UILabel:0x7b58b040'Name']-(NSSpace(8))-[UITextField:0x7b589490]>",
"<NSLayoutConstraint:0x7b51cb10 'Text Field Trailing' UITextField:0x7b589490.trailing == UIView:0x7b590790.trailingMargin>",
"<NSLayoutConstraint:0x7b0758c0 'UIView-Encapsulated-Layout-Width' H:[UIView:0x7b590790(320)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7b56d020 'Label Width' H:[UILabel:0x7b58b040'Name'(>=400)]>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
理解起来更容易了,有没有?
Visualizing Views and Constraints(可视化)
Xcode允许我们从视图结构的角度观察视图和约束。
利用模拟器进行如下操作(译者:真机上的操作类似):
- 在模拟器上运行App;
- 切换至Xcode;
- 选择Debug—>View Debugging—>Show Alignment Rectangles。我们将看到一个个矩形,每个矩形代表一个视图视图,称为边缘矩形(Alignment Rectangle)。
自动布局利用这些矩形布局视图。这个选项有助于我们迅速发现尺寸异常的视图。
通过点击Debug工具栏上的View Hierarchy
按钮,能够获取更多信息。视图结构将以3D形式展示在Xcode中,我们可以旋转角度,点击任意视图,探索上下级结构。在处理自动布局问题时,Show clipped content
和Show Constraints
工具格外有用。
Show clipped content
选项允许显示被剪裁掉的内容。Show Constraints
显示所有影响当前选中视图的约束。它们有助于快速找出潜在问题视图。
更多详细,详见Debug Area Help(Debug区域帮助)。
Understanding Edge Cases(边缘矩形特殊情况)
边缘矩形直接影响布局最终效果。下述特例会导致异常:
自动布局系统根据视图的边缘矩形对其布局,而非frame。大多数情况下,二者相等。然而,某些视图会重新定义边缘矩形,避免将某些部分纳入布局计算(如角标)。
更多信息,详见UIView Class Reference中
Aligning Views with Auto Layout
部分。iOS视图属性transform能够缩放,旋转,甚至移动视图;然而,这些形变不会影响布局计算,始终以基于原始frame生成的边缘矩形为准;
视图可以显示其bounds以外的内容。默认,视图不允许这种情况发生。然而,出于性能考虑,绘图引擎不做检查。这意味着视图尺寸(特别是自行绘制内容的视图)可能与其frame不符。
通过设置视图属性clipsToBounds为
YES
,或比对视图frame的size,可以验证这个问题。视图保持固有高度,布局属性(Layout Attribute)NSLayoutAttributeBaseline,NSLayoutAttributeFirstBaseline以及NSLayoutAttributeLastBaseline能够正确对齐文本。然而,一旦垂直方向上有缩放,文本会错位。
- 对于视图结构来说,约束优先级是一种全局属性。封装视图(如利用堆叠视图,布局参照(Layout Guide)或容器视图)可以大大简化布局。然而,约束优先级无法被同时封装。模块内优先级仍然可以与其他模块优先级互相作用。
- 比例约束将水平和垂直方向上的约束联系起来。通常,两个方向上的约束分开计算。然而,如果将视图高度依赖宽度,则二者互相影响。这些关系极大的增加了布局复杂度,可能影响布局其他部分,导致冲突。