iOS-Auto Layout[9]-Debugging

AutoLayout中可能出现三类异常,Unsatisfiable LayoutsAmbiguous LayoutsLogical Errors

Unsatisfiable Layouts

设计时,当系统无法满足所有Constraints时,一般是因为出冲突,这是可以看到在冲突的View Controller上面看到红色的按钮,点进去可以看到冲突的Constraints。

AutoLayout-Debugging

有些冲突是在运行时才导致的,例如横竖屏切换,此时,AutoLayout开始逐一破坏这些冲突的Constraints,直到计算成功为止,这些冲突以及被破坏的Constraints会被打印到控制台。

常见的导致Unsatisfiable Layouts的场景:

  • 使用代码直接添加View到View结构树种,在IB中,IB会自动将View的translatesAutoresizingMaskIntoConstraints属性置为NO,而在代码中新建的View默认为YES,需要在添加约束前手动改为NO;
  • 当空间特别小时,也容易导致Unsatisfiable Layouts,为了尽可能在出现这种情况时,尽可能的保护布局,可以考虑将一些Constraints的优先级从Required(1000)降到High(750),这样,当出现冲突时,这些优先级较低的Constraints会更先被破坏,尽可能保证原有的布局。

Ambiguous Layouts

出现Ambiguous Layout一般是两种情况:

  • 需要更多的Constraints来计算布局;
  • 当有多个同优先级的备选Constraints冲突时,系统不知道应该破坏哪一个。

出现Ambiguous Layout时,如果在设计阶段,是比较容易解决的,如果在运行时,Auto Layout会选择其中一种计算结果作为布局,此时布局很可能是不可控的,并且,被破坏的Constraints并不会打印到控制台。

通过下述的一些方法和属性,可以辅助调试Ambiguous Layouts,首先,设置一个断点:

  • hasAmbiguousLayout属性:该属性标示对应的View是否有Ambiguous Layout;
  • exerciseAmbiguityInLayout方法:当确定一个View有Ambiguous Layout时,在该View上调用该方法,可以让系统在多个可能的方案中切换;
  • constraintsAffectingLayoutForAxis方法:在View上调用该方法,返回一组在对应的轴上的所有影响的Constraints;
  • constraintsAffectingLayoutForOrientation方法:在View上调用该方法,返回一组在对应的横竖屏朝向上的所有影响的Constraints;
  • _autolayoutTrace方法:私有方法,在View上调用该方法,可以打印出包含该View的View结构树的信息,Ambiguous Layout的Views以及translatesAutoresizingMaskIntoConstraints为YES的Views都会被打印出来。

Logical Errors

Logical Errors指的是布局有时候虽然没有明显的错误,但是得到的结果与预想的不符,这个没有定位方法,只能通过检查和调试来解决。

Debugging Tricks and Tips

一些Debug技巧:

Logs

控制台打印出来的Log,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.

可以明显看出,第一个和第五个Constraints冲突,系统破坏了第一个。

如果View的translatesAutoresizingMaskIntoConstraints为YES时,会有一些额外的信息,例如:

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

其中,h=后面带着三个参数,代表左边界,宽度和右边界,v=后面带着三个参数,上边界,高度和下边界,“-”代表固定值,“&”代表可变值。

Adding Identifiers

为了更好在Log中识别信息,可以给View设置identifier属性,如图:

AutoLayout-Debug

当没有设置时,XCode也会用一些属性进行标示,例如Label的Text,Button的Title,Text Filed的Placeholder。

Visualizing Views and Constraints

在虚拟器上运行App,然后在XCode中选择:

1
Debug > View Debugging > Show Alignment Rectangles

可以看到控件的矩形,如下图:

AutoLayout-Debug

选择:

1
Debug > View Debugging > Capture View Hierarchy

可以看到整个View的结构,如下图:
AutoLayout-Debug

Edge Case

下面是一些可能出现的Edge Case:

  • Auto Layout是根据View的Alignment矩形来布局位置的,而不是Frame,绝大多数情况下,这两个值都是相同的,但是有些情况下,经过计算后,View的Alignment矩形可能会没有覆盖View的全部;
  • 利用View的Transform属性来旋转,缩放和移动时,AutoLayout不会重新进行计算;
  • 有些View的内容可能超过其bounds;
  • NSLayoutAttributeBaseline, NSLayoutAttributeFirstBaseline,和NSLayoutAttributeLastBaseline三个参数只有当其内部高度足够时,才会显示正常,否则可能会异常;
  • Constraints的优先级是全局的,这意味着即使是在StackView中View的Constraints,也可能会与StackView外View的Constraints根据优先级进行计算;
  • Aspect ratio允许垂直和水平的Constraint进行交互,这将导致两个方向的Constraints互相产生影响,可能导致冲突或者异常布局。