从 iOS 8 开始,Apple 就一直改进 AutoLayout 的体验,iOS 9 UIKit 团队推出了 UIStackView,让自动化布局变得更加简单
Stack View 提供了一些垂直和水平方向上的布局方式,通过设置这些属性如 alignment,distribution,spacing 你可以定义这些 contained views 之间的距离
stack view 可看作是一个容器,包含着需要布局的 views
现在来玩第一个 Stack View
选中这三个 view,然后将他们加入 Stack View
将 view 加入 stackView 会将原有的约束全部清除掉,因此我们需要重新添加约束,不过这一次是在 stackView 上添加
Top: 20, Leading: 0, Trailing: 0, Bottom: 0
解决了外部,再回到 stack view 内部,内部 view 之前的间距太近了,我们来添加一些 Spacing,方法如下:
照例,运行看下效果:
如果你有一堆 views,相互之间都设好约束。如果其中一个 view 被隐藏掉,为了保证显示效果,你需要对约束做一些调整。而现在有了 stack view,你只需要将 view 的 hidden
设为 true
,隐藏掉就好了,其余的事情交给 stack view 来处理。
除了在 Auto Layout 上的改进,iOS 9 还放出了两个新玩意: layout anchors 和 layout guides ,分别来自 NSLayoutAnchor
类和 UILayoutGuide
类
Layout anchors提供了一种直观的方式来创建约束,想象下你有两个 labels, bottomLabel
和 topLabel
。你想让 bottomLabel 位于 topLabel 的下方,且二者直接的距离为 8 points。在 iOS 9 之前,你可能要这么写:
let constraint = NSLayoutConstraint( item: topLabel, attribute: .Bottom, relatedBy: .Equal, toItem: bottomLabel, attribute: .Top, multiplier: 1, constant: 8 )
而有了 Layout anchors 允许我们这么做
let constraint = topLabel.bottomAnchor.constraintEqualToAnchor( bottomLabel.topAnchor, constant: 8)
constraintEqualToAnchor:constant:
方法属于类 NSLayoutAnchor
的实例方法,这个类还有几个类似的方法:
- constraintEqualToAnchor: - constraintGreaterThanOrEqualToAnchor: - constraintGreaterThanOrEqualToAnchor:constant: - constraintLessThanOrEqualToAnchor: - constraintLessThanOrEqualToAnchor:constant:
NSLayoutAnchor
主要优势在于你不用直接创建 NSLayoutConstraint
了,而是先选择你要添加约束的对象包括(UIView、NSView、UILayoutGuide),然后用这些对象的 anchor
属性通过上面的方法来构建你的约束
再回到上面的例子中,现在 view 有 layout anchor
对象( bottomAnchor
)来表示 .Bottom attribute
了,而 bottomAnchor
属于 NSLayoutYAxisAnchor
,继承自 NSLayoutAnchor
NSLayoutAnchor
有四个子类以及分别对应了如下 anchor :
如果你查看文档,会发现 NSLayoutDimension
多了一些方法,增加了 multiplier
和 Constant
func constraintEqualToConstant(_:) func constraintEqualToAnchor(_:multiplier:) func constraintEqualToAnchor(_:multiplier:constant:) func constraint[Less|Greater]ThanOrEqualToConstant(_:) func constraint[Less|Greater]ThanOrEqualToAnchor(_:multiplier:) func constraint[Less|Greater]ThanOrEqualToAnchor( _:multiplier:constant:)
heightAnchor 和 widthAnchor 都是属于 NSLayoutDimension
,而文档的意思只有涉及这两种才会用到 multiplier
还有注意的一点就是使用这些 NSLayoutAnchor
构建约束时,类型必须一致。即 constraint[Equal|LessThanOrEqual|GreaterThanOrEqual]ToAnchor
这些方法的两个对象的 anchor 类型必须一致。
不一致 OC 会警告
而 Swift 会直接报错
layout guide 解决了你之前用 dummy view 才能解决的布局问题,将 layout guide 想象成一个隐形的矩形或 view 层级上的框架,你可以利用矩形的边缘来布局,就和你之前加个 dummy view 用法完全一样,你可以在上面添加约束什么的。
这样做的好处就是轻量,没有副作用,之前 dummy view 虽然也是隐形的,但毕竟在 view 层级结构中,还是要参与消息传递过程。
UILayoutGuide 定义的这个矩形区域也能很好的与 Auto Layout 交互
下面来解决一个布局问题
红框圈出的 cell 文字没有居中,原因也很简单,设置了 label 的 top constant 为固定值(15),这个 value 在单行文字正常,多行就有问题了。
在 iOS 9 之前,你可能会创建一个 dummy container view 来居中,现在则完全可以用 layout guide 来做
目前 layout guide 还只能通过代码添加,在 awakeFromNib():
中添加如下代码:
// 1 为 cell 的 contentView 添加 layoutGuide let layoutGuide = UILayoutGuide() contentView.addLayoutGuide(layoutGuide) // 2 let topConstraint = layoutGuide.topAnchor .constraintEqualToAnchor(nameLabel.topAnchor) // 3 let bottomConstraint = layoutGuide.bottomAnchor .constraintEqualToAnchor(locationNameLabel.bottomAnchor) // 4 let centeringConstraint = layoutGuide.centerYAnchor .constraintEqualToAnchor(contentView.centerYAnchor) // 5 NSLayoutConstraint.activateConstraints( [topConstraint, bottomConstraint, centeringConstraint])
首先为 cell 的 contentView 添加 layoutGuide,其实就相当于给 cell 的 contentView 加了一个隐形的矩形框(尺寸等于 contentView 的 frame),接着利用 layoutGuide 的 Anchor 对象创建需要的约束,最后一步激活这些约束。
activateConstraints(_:)
是从 iOS 8 开始,苹果推荐使用的
再次运行,居中问题似乎解决了
不过仔细一看,之前 cell 的内容确实居中了,不过 下面的 locationNameLabel 被压扁了。还记得之前提到过『label 的 top constant 被设为固定值(15)』吗?我们现在已经通过 layout guide 设置了居中,不再需要这个 top 约束
了。
直接在 Xcode 中删除的话,会报错缺失约束,这是因为我们通过 layout guide 添加的那些约束只有在运行时才会生效,所以暂时还不能删掉这个 top 约束
,还好,Xcode 已经提供了解决方案,在 top 约束
的 Placeholder 上打上勾即可。
这样, top 约束
会在运行时移除,Xcode 也不会报错了
最后运行,一切 OK