snapKit的使用注意
SnapKit 是一个优秀的 Swift Auto Layout 库,但在使用中需要注意一些重要事项。以下是详细的使用注意事项:
1. 内存管理 - 避免循环引用
❌ 错误用法:
swift
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton()
view.addSubview(button)
button.snp.makeConstraints { make in
// 这里隐式捕获了 self
make.center.equalTo(self.view)
make.width.equalTo(self.view).multipliedBy(0.5)
}
}
}
✅ 正确用法:
swift
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton()
view.addSubview(button)
button.snp.makeConstraints { [weak self] make in
guard let self = self else { return }
make.center.equalTo(self.view)
make.width.equalTo(self.view).multipliedBy(0.5)
}
}
}
2. 视图层级顺序
❌ 错误用法:
swift
// 先设置约束,后添加到父视图
view.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
parentView.addSubview(view) // 太晚了!
✅ 正确用法:
swift
// 先添加到父视图,再设置约束
parentView.addSubview(view)
view.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
3. 约束更新和重制
更新特定约束:
swift
class CustomView: UIView {
var topConstraint: Constraint?
func setupConstraints() {
self.snp.makeConstraints { make in
self.topConstraint = make.top.equalToSuperview().constraint
make.left.right.equalToSuperview()
make.height.equalTo(100)
}
}
func updateTopConstraint() {
// 更新单个约束
self.topConstraint?.update(offset: 20)
}
}
重制所有约束:
swift
class CustomView: UIView {
func remakeConstraintsForNewLayout() {
// 移除所有旧约束,创建新约束
self.snp.remakeConstraints { make in
make.edges.equalToSuperview()
}
}
func updateConstraintsForAnimation() {
// 更新现有约束
self.snp.updateConstraints { make in
make.top.equalToSuperview().offset(50)
}
}
}
4. 优先级使用
swift
view.snp.makeConstraints { make in
// 设置优先级
make.width.equalTo(200).priority(.high)
make.width.lessThanOrEqualTo(300).priority(.required)
make.width.greaterThanOrEqualTo(100).priority(.medium)
// 或者使用具体数值
make.height.equalTo(100).priority(999)
}
5. 安全区域和边距
❌ 过时用法:
swift
view.snp.makeConstraints { make in
make.top.equalTo(topLayoutGuide.snp.bottom) // iOS 11 之前
}
✅ 现代用法:
swift
view.snp.makeConstraints { make in
// 适配安全区域
make.top.equalTo(view.safeAreaLayoutGuide.snp.top)
make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom)
// 或者使用 edges 快捷方式
make.edges.equalTo(view.safeAreaLayoutGuide)
}
考虑可读边距:
swift
view.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(
top: 0,
left: 16,
bottom: 0,
right: 16
))
}
6. 性能优化
批量设置约束:
swift
// ❌ 低效:多次布局计算
view1.snp.makeConstraints { $0.top.equalToSuperview() }
view2.snp.makeConstraints { $0.top.equalTo(view1.snp.bottom) }
view3.snp.makeConstraints { $0.top.equalTo(view2.snp.bottom) }
// ✅ 高效:一次布局计算
snp.prepareConstraints {
view1.snp.makeConstraints { $0.top.equalToSuperview() }
view2.snp.makeConstraints { $0.top.equalTo(view1.snp.bottom) }
view3.snp.makeConstraints { $0.top.equalTo(view2.snp.bottom) }
}
// 然后调用 layoutIfNeeded()
7. 动画处理
swift
class AnimatedView: UIView {
var topOffset: Constraint?
func setup() {
self.snp.makeConstraints { make in
self.topOffset = make.top.equalToSuperview().offset(0).constraint
make.centerX.equalToSuperview()
make.width.height.equalTo(100)
}
}
func animate() {
// 更新约束
self.topOffset?.update(offset: 200)
// 执行动画
UIView.animate(withDuration: 0.3) {
self.superview?.layoutIfNeeded()
}
}
}
8. 常见约束模式
居中布局:
swift
view.snp.makeConstraints { make in
make.center.equalToSuperview()
make.size.equalTo(CGSize(width: 100, height: 100))
}
等分布局:
swift
let views = [view1, view2, view3]
views.forEach { parentView.addSubview($0) }
view1.snp.makeConstraints { make in
make.left.top.bottom.equalToSuperview()
}
view2.snp.makeConstraints { make in
make.left.equalTo(view1.snp.right)
make.top.bottom.equalToSuperview()
make.width.equalTo(view1)
}
view3.snp.makeConstraints { make in
make.left.equalTo(view2.snp.right)
make.right.top.bottom.equalToSuperview()
make.width.equalTo(view1)
}
9. 调试技巧
添加标识符(iOS 11+):
swift
view.snp.makeConstraints { make in
let constraint = make.width.equalTo(100).constraint
constraint.identifier = "CustomViewWidthConstraint"
}
检查约束冲突:
swift
override func updateConstraints() {
super.updateConstraints()
// 检查是否有无法满足的约束
if hasAmbiguousLayout {
exerciseAmbiguityInLayout()
}
}
10. 最佳实践总结
-
总是先
addSubview再设置约束 -
使用
[weak self]避免循环引用 -
合理使用
makeConstraints、updateConstraints、remakeConstraints -
考虑安全区域,特别是 iPhone X 及以上机型
-
为复杂动画预先保存约束引用
-
使用优先级解决约束冲突
-
批量设置约束优化性能
-
为重要约束添加标识符便于调试
遵循这些注意事项可以避免常见的 SnapKit 使用陷阱,写出更健壮、易维护的布局代码。
SnapKit 的布局优先级是非常重要的功能,用于处理约束冲突和动态布局。以下是详细的使用指南:
1. 优先级基础用法
内置优先级级别:
swift
view.snp.makeConstraints { make in
make.width.equalTo(200).priority(.high)
make.width.greaterThanOrEqualTo(100).priority(.medium)
make.width.lessThanOrEqualTo(300).priority(.low)
}
// 或者使用具体数值
view.snp.makeConstraints { make in
make.width.equalTo(200).priority(999)
make.height.equalTo(100).priority(500)
}
SnapKit 内置优先级常量:
swift
.priority(.required) // 1000
.priority(.high) // 750
.priority(.medium) // 500
.priority(.low) // 250
.priority(.fittingSizeLevel) // 50
2. 优先级解决约束冲突
场景:宽度自适应但有最大最小限制
swift
label.snp.makeConstraints { make in
// 必需约束:最大宽度不超过屏幕的80%
make.width.lessThanOrEqualToSuperview().multipliedBy(0.8).priority(.required)
// 高优先级:首选宽度
make.width.equalTo(150).priority(.high)
// 低优先级:如果内容太多可以超过150
make.width.greaterThanOrEqualTo(150).priority(.low)
}
3. 动态调整优先级
保存约束引用并修改优先级:
swift
class CustomView: UIView {
var widthConstraint: Constraint?
var heightConstraint: Constraint?
func setupConstraints() {
self.snp.makeConstraints { make in
self.widthConstraint = make.width.equalTo(100).priority(.high).constraint
self.heightConstraint = make.height.equalTo(100).priority(.high).constraint
make.center.equalToSuperview()
}
}
func makeSizeFlexible() {
// 降低优先级,允许压缩
self.widthConstraint?.deactivate()
self.snp.makeConstraints { make in
self.widthConstraint = make.width.equalTo(100).priority(.low).constraint
}
}
}
4. 条件优先级
根据条件设置不同优先级:
swift
class AdaptiveView: UIView {
var isCompact: Bool = false
func updateConstraints() {
self.snp.remakeConstraints { make in
if isCompact {
make.width.equalTo(80).priority(.required)
make.height.equalTo(40).priority(.required)
} else {
make.width.equalTo(120).priority(.required)
make.height.equalTo(60).priority(.required)
}
make.center.equalToSuperview()
}
}
}
5. 多约束竞争场景
按钮组布局示例:
swift
class ButtonGroup: UIView {
let button1 = UIButton()
let button2 = UIButton()
let button3 = UIButton()
func setupConstraints() {
[button1, button2, button3].forEach { addSubview($0) }
button1.snp.makeConstraints { make in
make.left.equalToSuperview()
make.top.bottom.equalToSuperview()
// 首选宽度,但不是强制的
make.width.equalTo(100).priority(.high)
}
button2.snp.makeConstraints { make in
make.left.equalTo(button1.snp.right)
make.top.bottom.equalToSuperview()
// 与button1等宽,高优先级
make.width.equalTo(button1).priority(.high)
}
button3.snp.makeConstraints { make in
make.left.equalTo(button2.snp.right)
make.right.equalToSuperview()
make.top.bottom.equalToSuperview()
// 与button1等宽,但优先级较低,可以压缩
make.width.equalTo(button1).priority(.medium)
}
}
}
6. UILabel 多行文本自适应
swift
label.snp.makeConstraints { make in
// 必需:最大宽度限制
make.width.lessThanOrEqualTo(300).priority(.required)
// 高优先级:首选宽度
make.width.equalTo(200).priority(.high)
// 低优先级:最小宽度保证可读性
make.width.greaterThanOrEqualTo(80).priority(.low)
// 高度自适应
make.height.greaterThanOrEqualTo(40).priority(.required)
}
7. 响应式布局优先级
根据屏幕方向调整:
swift
class ResponsiveView: UIView {
var portraitConstraints: [Constraint] = []
var landscapeConstraints: [Constraint] = []
func setupConstraints() {
// 竖屏约束
self.snp.makeConstraints { make in
portraitConstraints = [
make.width.equalToSuperview().priority(.high).constraint,
make.height.equalTo(200).priority(.high).constraint
]
}
// 横屏约束(初始不激活)
landscapeConstraints = self.snp.prepareConstraints { make in
make.width.equalTo(300).priority(.high).constraint
make.height.equalToSuperview().priority(.high).constraint
}
}
func updateForOrientation(_ isLandscape: Bool) {
if isLandscape {
portraitConstraints.forEach { $0.deactivate() }
landscapeConstraints.forEach { $0.activate() }
} else {
landscapeConstraints.forEach { $0.deactivate() }
portraitConstraints.forEach { $0.activate() }
}
}
}
8. 复杂布局优先级策略
三栏布局示例:
swift
class ThreeColumnLayout: UIView {
let leftView = UIView()
let centerView = UIView()
let rightView = UIView()
func setupConstraints() {
[leftView, centerView, rightView].forEach { addSubview($0) }
// 左栏:固定宽度,高优先级
leftView.snp.makeConstraints { make in
make.left.top.bottom.equalToSuperview()
make.width.equalTo(100).priority(.required)
}
// 右栏:固定宽度,高优先级
rightView.snp.makeConstraints { make in
make.right.top.bottom.equalToSuperview()
make.width.equalTo(100).priority(.required)
}
// 中间栏:自适应,但有限制
centerView.snp.makeConstraints { make in
make.left.equalTo(leftView.snp.right)
make.right.equalTo(rightView.snp.left)
make.top.bottom.equalToSuperview()
// 最小宽度保证,但优先级较低
make.width.greaterThanOrEqualTo(200).priority(.medium)
// 首选宽度,但不是强制的
make.width.equalTo(300).priority(.low)
}
}
}
9. 调试优先级问题
添加约束标识符:
swift
view.snp.makeConstraints { make in
let widthConstraint = make.width.equalTo(100).priority(.high).constraint
widthConstraint.identifier = "HighPriorityWidth"
let heightConstraint = make.height.equalTo(100).priority(.medium).constraint
heightConstraint.identifier = "MediumPriorityHeight"
}
检查激活的约束:
swift
func debugConstraints() {
DispatchQueue.main.async {
let constraints = self.constraints
for constraint in constraints {
if let identifier = constraint.identifier {
print("约束: \(identifier), 优先级: \(constraint.priority.rawValue), 激活: \(constraint.isActive)")
}
}
}
}
10. 最佳实践总结
-
合理使用优先级层次 :
.required→.high→.medium→.low -
避免过多
.required优先级,容易导致约束冲突 -
为自适应布局使用
.medium和.low优先级 -
保存约束引用用于动态调整
-
使用约束标识符便于调试
-
考虑使用
UILayoutPriority自定义数值 :.priority(UILayoutPriority(800)) -
测试不同屏幕尺寸下的优先级表现
-
优先保证内容可读性,其次考虑设计精确度
正确使用优先级可以让布局更加灵活和健壮,特别是在处理动态内容和多设备适配时。