注:本文节选并翻译自Debugging Tricks and Tips
以下话题描述了在布局中总结提炼出的信息,同时描述了一些你可能遇到的意料之外的情况。你也许不会再每次布局中都会用到这些技术,但是它们可以在一些难题上给你提供帮助。
理解日志
视图的信息可以在控制台中输出,也许是因为存在了不满足条件的布局,或是因为你明确使用了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.
这个错误消息展示了五个冲突约束。这些约束无法同时满足。你应该要么移除一个,要么把一个约束变成可选约束。
幸运的是,这个视图层级相对简单。你有一个父视图,它包含着一个label和一个text field。冲突的约束被设置成了如下关系:
- label的宽度大于等于400点。
- label的起始边等于父视图的起始空白区域。
- 在label和text field之间包含8个点的空间。
- text field的尾边等于父视图的尾部空白区域。
- 父视图的宽度为320点。
系统试图通过移除label的宽度来恢复正常。
说明
控制台中写入的约束使用的是可视化语言(Visual Format Language,以后写做VFL)。即使你从不会使用VFL来创建约束,你也必须可以阅读并理解它以便有效调试Auto Layout的问题。要获取更多信息,请看可视化语言。
这些约束当中,最后一个是系统创建的。你不能改变它。并且,它和第一个约束一起产生了一个明显的冲突。如果你的父视图只有320点宽度,你不可能会有一个400点宽度的label。幸运的是,你不必移除首个约束。如果你把它的优先级降到999,系统还是会试图提供一个可选宽度----尽可能地接近它(400宽度),并同时满足其他约束。
基于视图的autoresizing mask的约束(例如,当translatesAutoresizingMaskIntoConstraints为YES时,约束被创建)在mask上存在额外信息。在约束被定位后,日志字符串会显示"h=",且其后跟随着三个字符,“v=”后同样跟随三个字符。一个“-”(连字符)字符代表一个确定值,同时一个“&”(与符号)字符代表一个可变值。对于水平方向(h=),三个字符分别代表了左间距、
宽度和右间距。在垂直方向上(v=),他们分别代表了顶部间距、高度和底部间距。
例如,考虑如下日志:
<NSAutoresizingMaskLayoutConstraint:0x7ff28252e480 h=--& v=--& H:[UIView:0x7ff282617cc0(50)]>"
此信息由如下部分组成:
- NSAutoresizingMaskLayoutConstraint:0x7ff28252e480:约束的类和地址。本例中,此类说明它基于视图的autoresizing mask。
- h=--& v=--&:视图的autoresizing mask。这是默认的mask。在水平方向上它包含一个确定的左间距、一个确定的宽度和一个可变的右间距。在竖直方向上它存在一个确定的顶部间距、一个确定的高度和一个可变的底部间距。换句话说,在父视图尺寸改变时,这个视图的左上角和尺寸保持不变。
- H:[UIView:0x7ff282617cc0(50)]:使用VFL描述的约束。在本例中,它描述了一个单独的50点宽度的视图。此描述还包含了约束涉及到的所有视图的类和地址。
给日志添加识别符号
虽然前一个例子相对容易理解,越来越长的约束列表很快就变得难以理解。你可以通过给每个视图和约束设置一个有意义的识别符来让日志容易阅读。
如果视图有一个明显的文字组件,Xcode就直接使用它来做识别符。例如,Xcode使用一个label的文字,一个button的标题,或者一个text field的占位字来识别这些视图。其他情况下,需要在识别检查器(Identity inspector)中设置指定的标签。Interface Builder在交互设置中使用这些识别符。大多数的这些识别符都会在控制台日志中显示。
对于约束来说,通过代码或者属性检查器(Attribute inspector)来设置identifier属性。当在控制台打印信息时,Auto Layout就会使用这些识别符。
举例来说,这是一个带有识别符版本的相同的错误约束:
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.
正如所见,这些识别符可以让你在日志中快速且容易地识别出约束。
理解边界案例
这里有一些可能导致Auto Layout出现不可预料情况的边界案例:
- Auto Layout是基于视图的对齐矩形来定位视图的,而不是frame。大多时候,二者是相等的。可是,在布局计算时,一些视图可能会设置了自定义的对齐矩形进而超出了视图本身(如badges)。
了解更多信息,在UIView类参考中查看使用Auto Layout对齐视图的相关内容。 - 在iOS中,你可以使用视图的transform属性来设置尺寸、旋转或移动视图;可是,这些变换根本不会影响Auto Layout的计算。Auto Layout是通过视图未变换时的frame来计算其对齐矩形的。
- 一个视图可以在其边界范围之外显示内容。大多时候,视图会正常显示并将其内容限制在边界之内。可是,有时出于性能考虑,这不会被图像引擎强制实现。这意味着该视图(特别是那些自定义绘制的视图)可能会以一个不同于本身frame的尺寸进行绘制。
你可以将视图的clipsToBounds属性置为YES来测试这个问题,或者通过视图的frame进行验证。 - 只有当所有视图以它们的真实内容尺寸高度显示时,NSLayoutAttributeBaseline、NSLayoutAttributeFirstBaseline和NSLayoutAttributeLastBaseline属性才会正确对齐文字。如果其中一个视图在竖直方向上被拉伸或压缩,其文字就可能出现在错误的位置上。
- 约束优先级会在整个视图层级中作为全局属性出现。你可能会经常使用stack view、layout guide或者虚拟视图将视图进行分组;可是,这种做法并不能将所需的视图优先级封装到内部。Auto Layout会继续将组内的优先级同外面的进行比较(甚至是其他组中的优先级)。
- 宽高比约束允许水平和竖直约束进行交互。一般来说,水平和竖直布局是单独计算的。可是,如果你使用一个视图的宽度来约束其高度时,你便创建了一个水平和竖直约束间的连接关系:他们现在会对其他约束产生影响甚至发生冲突。这种交互会明显增加布局的复杂度,并且可能会与一些不相干的布局产生某些不可预期的冲突。
网友评论