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() 激活。
