(WWDC) Auto Layout 的秘密, Part 2



这一篇文章是 (WWDC) Mysteries of Auto Layout, Part 1 的续集。


内容概览:
  1. The Layout Cycle(布局循环)
  2. Legacy Layout(旧的布局方式)
  3. Constraint Creation(约束的创建)
  4. Constraining Negative Space • Unsatisfiable Constraints(约束负空间 & 不满足的约束)
  5. Resolving Ambiguity(处理不明确的约束)



The Layout Cycle(布局循环)


通过调用 setNeedsUpdateConstraints() 方法请求更新约束布局

在调用该方法一段时间后,view中被你重写的 updateConstraints() 方法就会被调用。


导致约束表达式变化的因素:
  • Activating 、deactivating
  • 设置 constant 或 priority
  • add 或 remove views


引擎重新计算布局
  • 引擎获取新的值来计算布局
  • views 调用 superview 的 setNeedsLayout() 方法
    (frame没有立刻更新,稍后superView会调用 layoutSubviews() 方法更新subView的布局)


贯穿整个View层级(从superView到subView)的更新
  • 先更新约束
  • 然后superView从布局引擎取出布局信息,并给view调整布局
    (macOS上会调用setFrame方法;iOS上会调用setBounds和setCenter方法)

如果你要重写layoutSubviews()方法来更新约束,这时候一定要小心!
首先,你需要调用 super.layoutSubviews() ;
然后,不要随意更改约束,只操作当前view和subView的约束。
更不要调用 setNeedsUpdateConstraints() 方法!

在layoutSubviews()方法被调用时,当前View以外的约束已经是最新的。
如果你所更改的约束不包含在当前view或subView中,你可能会遭遇布局死循环!!!


注意事项

更新约束时,frame不会立刻就更新!
重写layoutSubviews()方法来更新约束时,要谨慎!



Legacy Layout(旧的布局方式)

直接使用frame,而不是约束。

当view的translatesAutoresizingMaskIntoConstraints为true时,布局引擎会自动为你设置的frame添加对应的约束。这样就可以让其他view和你的view建立约束关系。

当你需要用代码来设置约束时,记得将view的translatesAutoresizingMaskIntoConstraints设置为false,否则你极有可能看到你不想要的布局效果。使用IB布局时,这个参数会被自动配置,因此不用担心这个问题。

如果你忘了设置为false,你就会看到控制台输出类似的错误。
其中包含了一个标志性的类名 NSAutoresizingMaskLayoutConstraint

2015-05-08 09:41:27.668 WWDC 2015[4107:226949] Unable to simultaneously
satisfy constraints:
(
    "",
    ""
)
Will attempt to recover by breaking constraint



Constraint Creation(约束的创建)

最基本的约束创建方式,很繁琐:

NSLayoutConstraint(item: button, attribute: .Top, relatedBy: .Equal, toItem: view,
       attribute: .Top, multiplier: 1, constant: 10).active = true
NSLayoutConstraint(item: button, attribute: .Leading, relatedBy: .Equal,
       toItem: view, attribute: .Leading, multiplier: 1, constant: 10).active = true

可以使用简化的创建方式:

button.topAnchor.constraintEqualToAnchor(view.topAnchor, constant:10)
button.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor, constant:10)

另外,要注意传入的参数:

// 不要把位置约束为常量
[v1.leadingAnchor constraintEqualToConstant:100];

// 也不要把位置约束为宽度
[v1.leadingAnchor constraintEqualToAnchor:v2.widthAnchor];



Constraining Negative Space • Unsatisfiable Constraints(约束负空间 & 不满足的约束)


约束负空间

如何对下面的view进行布局?


创建View来填充空间?




使用UILayoutGuide可以更好地解决这个问题!
因为View确实存在于视图层级中,而UILayoutGuide不需要。
你只需要创建UILayoutGuide并添加到View中,之后就可以根据这个UILayoutGuide来进行布局。



看一下UILayoutGuide的官方文档,你是否已经想到了如何使用它呢?

/* UILayoutGuides will not show up in the view hierarchy, but may be used as items in
 an NSLayoutConstraint and represent a rectangle in the layout engine.
 
 Create a UILayoutGuide with -init, and add to a view with -[UIView addLayoutGuide:]
 before using it in a constraint.
 */

@available(iOS 9.0, *)
open class UILayoutGuide : NSObject, NSCoding {

    /* The frame of the UILayoutGuide in its owningView's coordinate system.
     Valid by the time the owningView receives -layoutSubviews.
     */
    open var layoutFrame: CGRect { get }

    /* The guide must be added to a view with -[UIView addLayoutGuide:] before being used in a constraint.
     Do not use this property directly to change the owningView of a layout guide. Instead, use 
     -[UIView addLayoutGuide:] and -[UIView removeLayoutGuide:], which will use this property to 
     change the owningView.
     */
    weak open var owningView: UIView?

    /* For ease of debugging.
     'UI' prefix is reserved for UIKit-created layout guides.
     */
    open var identifier: String
    
    /* Constraint creation conveniences. See NSLayoutAnchor.h for details.
     */
    open var leadingAnchor: NSLayoutXAxisAnchor { get }
    open var trailingAnchor: NSLayoutXAxisAnchor { get }
    open var leftAnchor: NSLayoutXAxisAnchor { get }
    open var rightAnchor: NSLayoutXAxisAnchor { get }
    open var topAnchor: NSLayoutYAxisAnchor { get }
    open var bottomAnchor: NSLayoutYAxisAnchor { get }
    open var widthAnchor: NSLayoutDimension { get }
    open var heightAnchor: NSLayoutDimension { get }
    open var centerXAnchor: NSLayoutXAxisAnchor { get }
    open var centerYAnchor: NSLayoutYAxisAnchor { get }
}



不满足的约束

构建一个布局,然后运行。不过,并没有看到预期的结果。


控制台输出了很多错误信息!!?? 没错,这是约束冲突!

(
     "<_UILayoutSupportConstraint:0x7ffe9ad11a80 V:[_UILayoutGuide:
 0x7ffe9ad10650(20)]>",
     "<_UILayoutSupportConstraint:0x7ffe9ad10ba0 V:|-(0)-[_UILayoutGuide:
 0x7ffe9ad10650]   (Names: '|':UIView:0x7ffe9c81b720 )>",
     "",
     "",
     "”,   (Names: saturn:
 0x7ffe9acb8cb0 )>",
     "",
     "",
     "",
     ""
 )
 Will attempt to recover by breaking constraint
 



一起来解读一下这些错误信息吧!

原来是imageView的高度约束存在冲突!



你可以给约束设置标识符,这样就能更容易地阅读这些错误信息:

labelToTopConstraint.identifier = @"labelToTopConstraint";


输出水平或者竖直方向的所有约束
  • constraintsAffectingLayoutForAxis: (iOS)
  • constraintsAffectingLayoutForOrientation: (OS X)

在遇到这种约束冲突时,你还可以通过LLDB调用 constraintsAffectingLayoutForAxis: (iOS) 方法,然后输出水平或者竖直方向的所有约束。
参数为Int类型,0代表水平方向,1代表竖直方向。



Resolving Ambiguity(处理不明确的约束)


成因:
  • 约束不到位
  • 优先级冲突


解决方案
  1. 通过Xcode来查看问题详情:



  1. 通过LLDB来输出异常的约束:


  1. 查看 Alignment Rectangles,Debug - View Debugging - Show Alignment Rectangles:
  1. 查看 View Hierarchy:


  1. 使用LLDB执行 exerciseAmbiguityInLayout 方法来查看布局引擎提供的所有布局方案。




参考文章:
Mysteries of Auto Layout, Part 2




转载请注明出处,谢谢~

你可能感兴趣的:((WWDC) Auto Layout 的秘密, Part 2)