这一篇文章是 (WWDC) Mysteries of Auto Layout, Part 1 的续集。
内容概览:
- The Layout Cycle(布局循环)
- Legacy Layout(旧的布局方式)
- Constraint Creation(约束的创建)
- Constraining Negative Space • Unsatisfiable Constraints(约束负空间 & 不满足的约束)
- 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:
(
"<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进行布局?
创建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 )>",
"<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 )>
一起来解读一下这些错误信息吧!
原来是imageView的高度约束存在冲突!
你可以给约束设置标识符,这样就能更容易地阅读这些错误信息:
labelToTopConstraint.identifier = @"labelToTopConstraint";
输出水平或者竖直方向的所有约束
- constraintsAffectingLayoutForAxis: (iOS)
- constraintsAffectingLayoutForOrientation: (OS X)
在遇到这种约束冲突时,你还可以通过LLDB调用 constraintsAffectingLayoutForAxis: (iOS) 方法,然后输出水平或者竖直方向的所有约束。
参数为Int类型,0代表水平方向,1代表竖直方向。
Resolving Ambiguity(处理不明确的约束)
成因:
- 约束不到位
- 优先级冲突
解决方案
-
通过Xcode来查看问题详情:
-
通过LLDB来输出异常的约束:
- 查看
Alignment Rectangles
,Debug - View Debugging - Show Alignment Rectangles:
-
查看 View Hierarchy:
- 使用LLDB执行
exerciseAmbiguityInLayout
方法来查看布局引擎提供的所有布局方案。
参考文章:
Mysteries of Auto Layout, Part 2
转载请注明出处,谢谢~