Apple布局锚点Layout Anchors
Apple布局锚点Layout Anchors

Apple布局锚点Layout Anchors

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() 方法返回约束对象。

相关文章

1、Apple界面自动布局系统Auto Layout

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注