Layout Anchors(布局锚点) 是苹果在 iOS 9 引入的一套 API,用于以类型安全和更直观的方式创建 Auto Layout 约束。
可以把 Anchor 理解为视图上的参考点或参考线,你可以用它们来描述视图之间的关系。
基本概念
每个 UIView/NSView 都有一系列的 anchor 属性,代表视图的不同位置或尺寸:
let view = NSView()
// 这些都是 Layout Anchors:
view.leadingAnchor // NSLayoutXAxisAnchor - 左边缘锚点
view.trailingAnchor // NSLayoutXAxisAnchor - 右边缘锚点
view.topAnchor // NSLayoutYAxisAnchor - 顶部锚点
view.bottomAnchor // NSLayoutYAxisAnchor - 底部锚点
view.widthAnchor // NSLayoutDimension - 宽度锚点
view.heightAnchor // NSLayoutDimension - 高度锚点
view.centerXAnchor // NSLayoutXAxisAnchor - 水平中心锚点
view.centerYAnchor // NSLayoutYAxisAnchor - 垂直中心锚点
Anchor 的类型
Layout Anchors 有三种主要类型:
1、NS/UILayoutXAxisAnchor – X 轴相关
用于水平方向的约束:
view.leadingAnchor // 左边缘(自动适配语言方向)
view.trailingAnchor // 右边缘(自动适配语言方向)
view.leftAnchor // 绝对左边
view.rightAnchor // 绝对右边
view.centerXAnchor // 水平中心
2、NS/UILayoutYAxisAnchor – Y 轴相关
用于垂直方向的约束:
view.topAnchor // 顶部
view.bottomAnchor // 底部
view.centerYAnchor // 垂直中心
view.firstBaselineAnchor // 第一行文本基线
view.lastBaselineAnchor // 最后一行文本基线
3、NS/UILayoutDimension – 尺寸相关
用于宽度和高度:
view.widthAnchor // 宽度
view.heightAnchor // 高度
使用方法
每个 anchor 都有 constraint() 方法来创建约束:
// 1. 两个 anchor 相等
view1.leadingAnchor.constraint(equalTo: view2.leadingAnchor)
// 2. 两个 anchor 相等,带偏移量
view1.topAnchor.constraint(equalTo: view2.bottomAnchor, constant: 20)
// view1 的顶部 = view2 的底部 + 20
// 3. 大于等于
view1.widthAnchor.constraint(greaterThanOrEqualToConstant: 100)
// view1 的宽度 >= 100
// 4. 小于等于
view1.heightAnchor.constraint(lessThanOrEqualTo: view2.heightAnchor)
// view1 的高度 <= view2 的高度
// 5. 固定尺寸(NSLayoutDimension 专用)
view1.widthAnchor.constraint(equalToConstant: 200)
// view1 的宽度 = 200
// 6. 倍数关系(NSLayoutDimension 专用)
view1.widthAnchor.constraint(equalTo: view2.widthAnchor, multiplier: 0.5)
// view1 的宽度 = view2 的宽度 × 0.5
类型安全的优势
Layout Anchors 的最大优势是编译时类型检查:
// 正确:X 轴 anchor 只能和 X 轴 anchor 比较
view1.leadingAnchor.constraint(equalTo: view2.trailingAnchor)
// 正确:Y 轴 anchor 只能和 Y 轴 anchor 比较
view1.topAnchor.constraint(equalTo: view2.bottomAnchor)
// 编译错误:不能把 X 轴和 Y 轴混用
view1.leadingAnchor.constraint(equalTo: view2.topAnchor) // 编译器报错!
// 编译错误:不能把位置 anchor 和尺寸 anchor 混用
view1.leadingAnchor.constraint(equalTo: view2.widthAnchor) // 编译器报错!
使用示例
1、简单布局
让一个红色方块居中,宽高 100:
let redBox = UIView()
redBox.backgroundColor = .red
redBox.translatesAutoresizingMaskIntoConstraints = false // 必须配置
view.addSubview(redBox)
NSLayoutConstraint.activate([
// 水平居中:红盒子的 centerX = 父视图的 centerX
redBox.centerXAnchor.constraint(equalTo: view.centerXAnchor),
// 垂直居中:红盒子的 centerY = 父视图的 centerY
redBox.centerYAnchor.constraint(equalTo: view.centerYAnchor),
// 固定宽度 100
redBox.widthAnchor.constraint(equalToConstant: 100),
// 固定高度 100
redBox.heightAnchor.constraint(equalToConstant: 100)
])
NSLayoutConstraint.activate 是激活方式,也可以单个激活:
let constraint = view.widthAnchor.constraint(equalToConstant: 100)
constraint.isActive = true // 激活
2、填充父视图(含边距)
let contentView = UIView()
contentView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(contentView)
NSLayoutConstraint.activate([
// 左边距 20
contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
// 右边距 20(注意是负值)
contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
// 上边距 50
contentView.topAnchor.constraint(equalTo: view.topAnchor, constant: 50),
// 下边距 50(注意是负值)
contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -50)
])
为什么 trailing 和 bottom 用负值?
因为约束的公式是:
item1.attribute = item2.attribute + constant
3、比例约束
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(imageView)
NSLayoutConstraint.activate([
// 宽度是父视图的 80%
imageView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8),
// 高宽比 16:9
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 9.0/16.0),
// 水平居中
imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
// 距离顶部 100
imageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100)
])
4、多个视图排序
水平排列两个按钮,间距 20。
let button1 = UIButton()
let button2 = UIButton()
button1.translatesAutoresizingMaskIntoConstraints = false
button2.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button1)
view.addSubview(button2)
NSLayoutConstraint.activate([
// button1 在左边
button1.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
button1.centerYAnchor.constraint(equalTo: view.centerYAnchor),
button1.widthAnchor.constraint(equalToConstant: 100),
button1.heightAnchor.constraint(equalToConstant: 44),
// button2 在 button1 右边,间距 20
button2.leadingAnchor.constraint(equalTo: button1.trailingAnchor, constant: 20),
button2.centerYAnchor.constraint(equalTo: button1.centerYAnchor), // 垂直对齐
button2.widthAnchor.constraint(equalTo: button1.widthAnchor), // 同宽
button2.heightAnchor.constraint(equalTo: button1.heightAnchor) // 同高
])
5、填满窗口
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
常用Anchor方法
1、NS/UILayoutXAxisAnchor(X轴锚点)、NS/UILayoutYAxisAnchor r(Y轴锚点)
// 相等
.constraint(equalTo: anchor)
.constraint(equalTo: anchor, constant: offset)
// 大于等于
.constraint(greaterThanOrEqualTo: anchor)
.constraint(greaterThanOrEqualTo: anchor, constant: offset)
// 小于等于
.constraint(lessThanOrEqualTo: anchor)
.constraint(lessThanOrEqualTo: anchor, constant: offset)
2、NS/UILayoutDimension(尺寸锚点)
// 固定尺寸
.constraint(equalToConstant: value)
.constraint(greaterThanOrEqualToConstant: value)
.constraint(lessThanOrEqualToConstant: value)
// 倍数关系
.constraint(equalTo: dimension, multiplier: ratio)
.constraint(equalTo: dimension, multiplier: ratio, constant: offset)
.constraint(greaterThanOrEqualTo: dimension, multiplier: ratio)
.constraint(lessThanOrEqualTo: dimension, multiplier: ratio)
3、Safe Area Layout Guide
iOS 11+ 引入了 Safe Area,也提供了 anchors:
// 避开刘海、底部横条等
view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
view.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
view.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor)
view.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor)
Layout Anchors和NS/UILayoutConstraint的关系
Layout Anchors等价于NS/UILayoutConstraint,类似于语法糖。
例如:
imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10)
内部等价于:
NSLayoutConstraint(
item: imageView,
attribute: .leading,
relatedBy: .equal,
toItem: contentView,
attribute: .leading,
multiplier: 1,
constant: 10
)
Layout Anchors 可以忽略底层细节。
注意事项
1、使用前必须设置 translatesAutoresizingMaskIntoConstraints = false
let redBox = UIView(frame: CGRect(x: 100, y: 100, width: 200, height: 100))
redBox.translatesAutoresizingMaskIntoConstraints = false // 设置为 false
view.addSubview(redBox)
如果不设置 translatesAutoresizingMaskIntoConstraints ,就会自动生成基于 frame 的约束,并弹出约束冲突的报错,手动添加的约束无法生效。
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
(
"<NSLayoutConstraint:0x... UIView.width == 200>", // 自动生成的
"<NSLayoutConstraint:0x... UIView.width == 150>", // 手动添加的
...
)
Will attempt to recover by breaking constraint...
2、用 NSLayoutConstraint.activate([…]) 或者 UILayoutConstraint.activate([…]) 批量激活。
3、修改约束后调用 layoutIfNeeded() 触发重新布局。
topConstraint.constant = 200
// 此时 box.frame 还是旧值
print(box.frame) // 输出旧的 frame
// 强制立即更新布局
view.layoutIfNeeded()
调用layoutIfNeeded() 可以立即计算新的布局,得到新的frame值,立即执行布局,会递归调用所有子视图的布局。
4、X 轴 anchor 只能和 X 轴 anchor 比较。
5、Y 轴 anchor 只能和 Y 轴 anchor 比较。
6、尺寸 anchor(width/height)可以和尺寸 anchor 比较,也可以设置固定值。
7、trailing/bottom 的 constant 通常是负值。
总结
Layout Anchors是视图上的参考点,用于创建NS/UILayoutConstraint 的类型安全 API,通过 .constraint() 方法返回约束对象。
