macOS约束对象NSLayoutConstraint
macOS约束对象NSLayoutConstraint

macOS约束对象NSLayoutConstraint

NSLayoutConstraint 是 Auto Layout 系统中的“约束对象”,用于描述:

一个视图的某个属性,如何与另一个视图(或自身、或常量)之间建立数学关系。

在早期,使用frame控制视图之间的几何关系。但是存在窗口大小变化错位、需要大量手动计算等问题。

Auto Layout只描述“关系”,不关系具体尺寸。

约束的数学本质

每个 NSLayoutConstraint 对象本质上是一个线性等式或不等式:

item1.attribute1 = multiplier × item2.attribute2 + constant

例如,约束红色方块的左边距离父视图左边20点。

公式表示:

redBox.leading = 1.0 × parentView.leading + 20 

代码表示:

NSLayoutConstraint(
    item: redBox,              // item1
    attribute: .leading,       // attribute1
    relatedBy: .equal,         // 关系:= / ≥ / ≤
    toItem: parentView,        // item2
    attribute: .leading,       // attribute2
    multiplier: 1.0,           // 乘数
    constant: 20               // 常量
)

核心属性

1、item (firstItem):第一个视图(等式左边的视图)。

let constraint = NSLayoutConstraint(...)
constraint.firstItem  // 返回第一个视图

2、attribute (firstAttribute):第一个视图的哪个属性。

可选值:

.left           // 左边
.right          // 右边
.top            // 顶部
.bottom         // 底部
.leading        // 左边(自动适配语言方向)
.trailing       // 右边(自动适配语言方向)
.width          // 宽度
.height         // 高度
.centerX        // 水平中心
.centerY        // 垂直中心
.lastBaseline   // 文本基线
.firstBaseline  // 文本第一行基线
.notAnAttribute // 无属性(用于固定尺寸)

3、relatedBy (relation):关系类型。

.equal                  // =  相等
.lessThanOrEqual        // ≤  小于等于
.greaterThanOrEqual     // ≥  大于等于

4、toItem (secondItem):第二个视图(等式右边的视图),可以是 nil(用于固定尺寸约束)

5、attribute (secondAttribute):第二个视图的哪个属性。

6、multiplier:乘数(比例系数),默认是 1.0,用于比例约束。

7、constant:常量(偏移量),默认是 0。

8、priority:约束优先级(UILayoutPriority / NSLayoutPriority)。

.required        // 1000 - 必须满足
.defaultHigh     // 750  - 高优先级
.defaultLow      // 250  - 低优先级
.fittingSizeLevel // 50  - 很低优先级

9、isActive:约束是否激活。

constraint.isActive = true   // 激活约束
constraint.isActive = false  // 取消激活

创建方式

方式 1:传统初始化方法

let constraint = NSLayoutConstraint(
    item: view1,
    attribute: .leading,
    relatedBy: .equal,
    toItem: view2,
    attribute: .trailing,
    multiplier: 1.0,
    constant: 20
)
constraint.isActive = true

方式 2:Layout Anchors(推荐)

let constraint = view1.leadingAnchor.constraint(
    equalTo: view2.trailingAnchor,
    constant: 20
)
constraint.isActive = true

约束方法

1、激活约束方法

方法 1:设置 isActive 属性

let constraint = view.widthAnchor.constraint(equalToConstant: 100)
constraint.isActive = true  // 激活

方法 2:批量激活(推荐)

NSLayoutConstraint.activate([
    view.widthAnchor.constraint(equalToConstant: 100),
    view.heightAnchor.constraint(equalToConstant: 100),
    view.centerXAnchor.constraint(equalTo: parentView.centerXAnchor)
])

方法 3:添加到视图(旧方式,不推荐)

// 已过时
view.addConstraint(constraint)

2、取消激活约束

// 单个取消
constraint.isActive = false

// 批量取消
NSLayoutConstraint.deactivate([constraint1, constraint2])

常见约束类型示例

1、固定尺寸约束

// 宽度 = 200
view.width = 200

// 用 NSLayoutConstraint
NSLayoutConstraint(
    item: view,
    attribute: .width,
    relatedBy: .equal,
    toItem: nil,              // 注意:nil
    attribute: .notAnAttribute, // 注意:.notAnAttribute
    multiplier: 1.0,
    constant: 200
)

// 用 Layout Anchors(更简洁)
view.widthAnchor.constraint(equalToConstant: 200)

2、相等约束

// view1 的左边 = view2 的左边
view1.leading = view2.leading

// 用 NSLayoutConstraint
NSLayoutConstraint(
    item: view1,
    attribute: .leading,
    relatedBy: .equal,
    toItem: view2,
    attribute: .leading,
    multiplier: 1.0,
    constant: 0
)

// 用 Layout Anchors
view1.leadingAnchor.constraint(equalTo: view2.leadingAnchor)

3、偏移量约束

// view1 的顶部 = view2 的底部 + 20
view1.top = view2.bottom + 20

// 用 NSLayoutConstraint
NSLayoutConstraint(
    item: view1,
    attribute: .top,
    relatedBy: .equal,
    toItem: view2,
    attribute: .bottom,
    multiplier: 1.0,
    constant: 20
)

// 用 Layout Anchors
view1.topAnchor.constraint(equalTo: view2.bottomAnchor, constant: 20)

4、比例约束

// view1 的宽度 = view2 的宽度 × 0.5
view1.width = view2.width × 0.5

// 用 NSLayoutConstraint
NSLayoutConstraint(
    item: view1,
    attribute: .width,
    relatedBy: .equal,
    toItem: view2,
    attribute: .width,
    multiplier: 0.5,
    constant: 0
)

// 用 Layout Anchors
view1.widthAnchor.constraint(equalTo: view2.widthAnchor, multiplier: 0.5)

5、不等式约束

// view 的宽度 >= 100
view.width ≥ 100

// 用 NSLayoutConstraint
NSLayoutConstraint(
    item: view,
    attribute: .width,
    relatedBy: .greaterThanOrEqual,
    toItem: nil,
    attribute: .notAnAttribute,
    multiplier: 1.0,
    constant: 100
)

// 用 Layout Anchors
view.widthAnchor.constraint(greaterThanOrEqualToConstant: 100)

优先级约束

当约束冲突时,系统会根据优先级决定满足哪个:

let constraint1 = view.widthAnchor.constraint(equalToConstant: 200)
constraint1.priority = .required  // 1000 - 必须满足

let constraint2 = view.widthAnchor.constraint(equalToConstant: 300)
constraint2.priority = .defaultHigh  // 750 - 高优先级

NSLayoutConstraint.activate([constraint1, constraint2])

// 结果:width = 200(因为 constraint1 优先级更高)

常用约束等级:

UILayoutPriority.required           // 1000
UILayoutPriority.defaultHigh        // 750
UILayoutPriority.defaultLow         // 250
UILayoutPriority.fittingSizeLevel   // 50

// 自定义优先级
let customPriority = UILayoutPriority(rawValue: 800)

调试约束

1、添加标识符

let constraint = view.widthAnchor.constraint(equalToConstant: 100)
constraint.identifier = "widthConstraint"

当约束冲突时,控制台会显示:

Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want.
    (
        "<NSLayoutConstraint:0x... 'widthConstraint' UIView.width == 100>",
        "<NSLayoutConstraint:0x... UIView.width == 200>",
        ...
)

2、打印约束信息

// 打印视图的所有约束
print(view.constraints)

// 打印单个约束
print(constraint)
// 输出: <NSLayoutConstraint:0x... UIView.width == 100>

注意事项

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、推荐使用 Layout Anchors 创建(更安全、简洁)。

3、用 NSLayoutConstraint.activate([…]) 批量激活。

4、修改约束后调用 layoutIfNeeded() 触发重新布局。

topConstraint.constant = 200

// 此时 box.frame 还是旧值
print(box.frame)  // 输出旧的 frame

// 强制立即更新布局
view.layoutIfNeeded()

调用layoutIfNeeded() 可以立即计算新的布局,得到新的frame值,立即执行布局,会递归调用所有子视图的布局。

总结

NSLayoutConstraint 是 Auto Layout 的核心类,本质是一个线性方程。

可以通过传统初始化或Layout Anchors创建,使用isActive 或 activate() 激活。

相关文章

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

2、Apple布局锚点Layout Anchors

   

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

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

发表回复

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