跳到主要内容

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:

优先级说明
required1000必须满足
defaultHigh750高优先级
defaultLow250低优先级
// 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文本宽度文本高度
UIButtontitle + paddingtitle + padding
UIImageView图片宽度图片高度
UIView

Frame vs AutoLayout 选择

FrameAutoLayout
性能更好约束求解有开销
适配需手动计算自动适配
代码量较多计算简洁(配合 SnapKit)
适用场景高性能列表、动画常规界面

常见面试问题

Q1: translatesAutoresizingMaskIntoConstraints 是什么?

答案:该属性决定是否将 AutoresizingMask 转为 AutoLayout 约束。代码创建的视图默认为 true,使用 AutoLayout 时必须设为 false,否则会产生约束冲突。Storyboard/Xib 中的视图自动设为 false

Q2: 约束冲突如何排查?

答案

  1. 控制台会打印不可满足的约束(Unable to simultaneously satisfy constraints
  2. 设置 view.accessibilityIdentifier 方便识别视图
  3. 降低非必要约束的优先级
  4. 使用 Xcode 的 Debug View Hierarchy 可视化检查

Q3: setNeedsLayout 和 layoutIfNeeded 的区别?

答案

  • setNeedsLayout:标记需要重新布局,下一个 RunLoop 再执行
  • layoutIfNeeded:如果有待执行的布局,立即执行

配合使用可以实现约束动画:

self.topConstraint.constant = 100
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded() // 立即执行布局变化
}

相关链接