Swift-snapKit使用
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// 这里隐式捕获了 selfmake.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 inguard let self = self else { return }make.center.equalTo(self.view)make.width.equalTo(self.view).multipliedBy(0.5)}}
}2. 视图层级顺序
❌ 错误用法:
swift
// 先设置约束,后添加到父视图
view.snp.makeConstraints { make inmake.edges.equalToSuperview()
}
parentView.addSubview(view) // 太晚了!✅ 正确用法:
swift
// 先添加到父视图,再设置约束
parentView.addSubview(view)
view.snp.makeConstraints { make inmake.edges.equalToSuperview()
}3. 约束更新和重制
更新特定约束:
swift
class CustomView: UIView {var topConstraint: Constraint?func setupConstraints() {self.snp.makeConstraints { make inself.topConstraint = make.top.equalToSuperview().constraintmake.left.right.equalToSuperview()make.height.equalTo(100)}}func updateTopConstraint() {// 更新单个约束self.topConstraint?.update(offset: 20)}
}重制所有约束:
swift
class CustomView: UIView {func remakeConstraintsForNewLayout() {// 移除所有旧约束,创建新约束self.snp.remakeConstraints { make inmake.edges.equalToSuperview()}}func updateConstraintsForAnimation() {// 更新现有约束self.snp.updateConstraints { make inmake.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 inmake.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 inmake.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 inself.topOffset = make.top.equalToSuperview().offset(0).constraintmake.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 inmake.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 inmake.left.top.bottom.equalToSuperview()
}view2.snp.makeConstraints { make inmake.left.equalTo(view1.snp.right)make.top.bottom.equalToSuperview()make.width.equalTo(view1)
}view3.snp.makeConstraints { make inmake.left.equalTo(view2.snp.right)make.right.top.bottom.equalToSuperview()make.width.equalTo(view1)
}9. 调试技巧
添加标识符(iOS 11+):
swift
view.snp.makeConstraints { make inlet constraint = make.width.equalTo(100).constraintconstraint.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 inmake.width.equalTo(200).priority(.high)make.width.greaterThanOrEqualTo(100).priority(.medium)make.width.lessThanOrEqualTo(300).priority(.low)
}// 或者使用具体数值
view.snp.makeConstraints { make inmake.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)// 低优先级:如果内容太多可以超过150make.width.greaterThanOrEqualTo(150).priority(.low)
}3. 动态调整优先级
保存约束引用并修改优先级:
swift
class CustomView: UIView {var widthConstraint: Constraint?var heightConstraint: Constraint?func setupConstraints() {self.snp.makeConstraints { make inself.widthConstraint = make.width.equalTo(100).priority(.high).constraintself.heightConstraint = make.height.equalTo(100).priority(.high).constraintmake.center.equalToSuperview()}}func makeSizeFlexible() {// 降低优先级,允许压缩self.widthConstraint?.deactivate()self.snp.makeConstraints { make inself.widthConstraint = make.width.equalTo(100).priority(.low).constraint}}
}4. 条件优先级
根据条件设置不同优先级:
swift
class AdaptiveView: UIView {var isCompact: Bool = falsefunc updateConstraints() {self.snp.remakeConstraints { make inif 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 inmake.left.equalToSuperview()make.top.bottom.equalToSuperview()// 首选宽度,但不是强制的make.width.equalTo(100).priority(.high)}button2.snp.makeConstraints { make inmake.left.equalTo(button1.snp.right)make.top.bottom.equalToSuperview()// 与button1等宽,高优先级make.width.equalTo(button1).priority(.high)}button3.snp.makeConstraints { make inmake.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 inportraitConstraints = [make.width.equalToSuperview().priority(.high).constraint,make.height.equalTo(200).priority(.high).constraint]}// 横屏约束(初始不激活)landscapeConstraints = self.snp.prepareConstraints { make inmake.width.equalTo(300).priority(.high).constraintmake.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 inmake.left.top.bottom.equalToSuperview()make.width.equalTo(100).priority(.required)}// 右栏:固定宽度,高优先级rightView.snp.makeConstraints { make inmake.right.top.bottom.equalToSuperview()make.width.equalTo(100).priority(.required)}// 中间栏:自适应,但有限制centerView.snp.makeConstraints { make inmake.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 inlet widthConstraint = make.width.equalTo(100).priority(.high).constraintwidthConstraint.identifier = "HighPriorityWidth"let heightConstraint = make.height.equalTo(100).priority(.medium).constraintheightConstraint.identifier = "MediumPriorityHeight"
}检查激活的约束:
swift
func debugConstraints() {DispatchQueue.main.async {let constraints = self.constraintsfor 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))测试不同屏幕尺寸下的优先级表现
优先保证内容可读性,其次考虑设计精确度
正确使用优先级可以让布局更加灵活和健壮,特别是在处理动态内容和多设备适配时。
