AutoLayout 与布局
问题
AutoLayout 的原理是什么?约束冲突如何解决?Frame 布局和 AutoLayout 如何选择?
答案
AutoLayout 原理
AutoLayout 使用 Cassowary 线性约束求解算法将约束转换为一组线性方程,求解出每个视图的 frame:
view.leading = superview.leading + 16
view.trailing = superview.trailing - 16
view.top = titleLabel.bottom + 8
view.height = 44
每次布局循环会执行:更新约束 → 计算 frame → 布局子视图
约束优先级
约束优先级范围 1~1000:
| 优先级 | 值 | 说明 |
|---|---|---|
required | 1000 | 必须满足 |
defaultHigh | 750 | 高优先级 |
defaultLow | 250 | 低优先级 |
// Content Hugging:抗拉伸(值越大越不想被拉伸)
label.setContentHuggingPriority(.defaultHigh, for: .horizontal)
// Content Compression Resistance:抗压缩(值越大越不想被压缩)
label.setContentCompressionResistancePriority(.required, for: .horizontal)
实际应用
两个 Label 并排,希望左边的 Label 内容完整显示,右边的被截断:
- 左 Label:Compression Resistance = 751(更高)
- 右 Label:Compression Resistance = 750(默认)
代码布局
let redView = UIView()
redView.backgroundColor = .red
redView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(redView)
NSLayoutConstraint.activate([
redView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
redView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
redView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
redView.heightAnchor.constraint(equalToConstant: 100)
])
Intrinsic Content Size
某些控件有固有尺寸(不需要显式约束宽高):
| 控件 | 宽 | 高 |
|---|---|---|
| UILabel | 文本宽度 | 文本高度 |
| UIButton | title + padding | title + padding |
| UIImageView | 图片宽度 | 图片高度 |
| UIView | 无 | 无 |
Frame vs AutoLayout 选择
| Frame | AutoLayout | |
|---|---|---|
| 性能 | 更好 | 约束求解有开销 |
| 适配 | 需手动计算 | 自动适配 |
| 代码量 | 较多计算 | 简洁(配合 SnapKit) |
| 适用场景 | 高性能列表、动画 | 常规界面 |
常见面试问题
Q1: translatesAutoresizingMaskIntoConstraints 是什么?
答案:该属性决定是否将 AutoresizingMask 转为 AutoLayout 约束。代码创建的视图默认为 true,使用 AutoLayout 时必须设为 false,否则会产生约束冲突。Storyboard/Xib 中的视图自动设为 false。
Q2: 约束冲突如何排查?
答案:
- 控制台会打印不可满足的约束(
Unable to simultaneously satisfy constraints) - 设置
view.accessibilityIdentifier方便识别视图 - 降低非必要约束的优先级
- 使用 Xcode 的 Debug View Hierarchy 可视化检查
Q3: setNeedsLayout 和 layoutIfNeeded 的区别?
答案:
setNeedsLayout:标记需要重新布局,下一个 RunLoop 再执行layoutIfNeeded:如果有待执行的布局,立即执行
配合使用可以实现约束动画:
self.topConstraint.constant = 100
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded() // 立即执行布局变化
}