(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(布局循环)

1306450-fc5b18b586d8bf95.png


通过调用 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:
(
    "<NSAutoresizingMaskLayoutConstraint:0x6100000810e0 h=--& v=--& H:|-(0)-
[NSButton:0x618000140160'Button']   (Names: '|':NSView:0x618000120460 )>",
    "<NSLayoutConstraint:0x6180000828a0 H:|-(10)-[NSButton:
0x618000140160'Button'](LTR)   (Names: '|':NSView:0x618000120460 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x6000000825d0 H:|-(10)-[NSButton:0x600000140c60'Button']
(LTR)   (Names: '|':NSView:0x6000001203c0 )>



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进行布局?


1306450-1e1225122e3c195d.png

创建View来填充空间?


1306450-8a637c261ea4e66a.png



使用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 }
}



不满足的约束

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


1306450-9cfa8085f43c5b81.png

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

(
     "<_UILayoutSupportConstraint:0x7ffe9ad11a80 V:[_UILayoutGuide:
 0x7ffe9ad10650(20)]>",
     "<_UILayoutSupportConstraint:0x7ffe9ad10ba0 V:|-(0)-[_UILayoutGuide:
 0x7ffe9ad10650]   (Names: '|':UIView:0x7ffe9c81b720 )>",
     "<NSLayoutConstraint:0x7ffe9acbef60 'saturnWidth ' saturn.width ==
 1.5*saturn.height   (Names: saturn:0x7ffe9acb8cb0 )>",
     "<NSLayoutConstraint:0x7ffe9c905460 'imageHorizontal' saturn.leading
 == UIView:0x7ffe9c81b720.leadingMargin   (Names: saturn:
 0x7ffe9acb8cb0 )>",
     "<NSLayoutConstraint:0x7fedd3423ae0 'imageHorizontal' UIView:
 0x7fedd3607b90.trailingMargin == saturn.trailing>”,   (Names: saturn:
 0x7ffe9acb8cb0 )>",
     "<NSLayoutConstraint:0x7ffe9c905aa0 'verticalLayout' V:
 [_UILayoutGuide:0x7ffe9ad10650]-(NSSpace(8))-[saturn]   (Names: saturn:
 0x7ffe9acb8cb0 )>",
     "<NSLayoutConstraint:0x7ffe9c905b40 'verticalLayout' V:[saturn]-
 (NSSpace(8))-[UILabel:0x7ffe9c903d10'Which planet is this?']   (Names:
 saturn:0x7ffe9acb8cb0 )>",
     "<NSLayoutConstraint:0x7ffe9c906050 'labelToTop' V:|-(100)-[UILabel:
 0x7ffe9c903d10'Which planet is this?']   (Names: '|':UIView:0x7ffe9c81b720
 )>",
     "<NSLayoutConstraint:0x7ffe9aca0130 'UIView-Encapsulated-Layout-Width'
 H:[UIView:0x7ffe9c81b720(375)]>"
 )
 Will attempt to recover by breaking constraint
 <NSLayoutConstraint:0x7ffe9acbef60 'saturnWidth ' saturn.width ==
 1.5*saturn.height   (Names: saturn:0x7ffe9acb8cb0 )>



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

1306450-91853292ee00b736.png

1306450-c03413bd767b140b.png
1306450-f75c4022e7258c51.png
1306450-f2a1b156d7c31eaf.png
1306450-4a8f6a84df42ba2f.png
1306450-f186d56d76946831.png

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



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

labelToTopConstraint.identifier = @"labelToTopConstraint";
1306450-19adc4f2a5063a14.png


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

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



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


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


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


    1306450-ebcc1fcdbdbf425d.png
1306450-18c0cd3cb85d9af8.png


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


    1306450-04e7fd0a0473e684.png
  1. 查看 Alignment Rectangles,Debug - View Debugging - Show Alignment Rectangles:
    1306450-f4e7c273865a2423.png
  1. 查看 View Hierarchy:


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




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




转载请注明出处,谢谢~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值