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
            // 这里隐式捕获了 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. 最佳实践总结

  1. 总是先 addSubview 再设置约束

  2. 使用 [weak self] 避免循环引用

  3. 合理使用 makeConstraintsupdateConstraintsremakeConstraints

  4. 考虑安全区域,特别是 iPhone X 及以上机型

  5. 为复杂动画预先保存约束引用

  6. 使用优先级解决约束冲突

  7. 批量设置约束优化性能

  8. 为重要约束添加标识符便于调试

遵循这些注意事项可以避免常见的 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. 最佳实践总结

  1. 合理使用优先级层次.required.high.medium.low

  2. 避免过多 .required 优先级,容易导致约束冲突

  3. 为自适应布局使用 .medium.low 优先级

  4. 保存约束引用用于动态调整

  5. 使用约束标识符便于调试

  6. 考虑使用 UILayoutPriority 自定义数值.priority(UILayoutPriority(800))

  7. 测试不同屏幕尺寸下的优先级表现

  8. 优先保证内容可读性,其次考虑设计精确度

正确使用优先级可以让布局更加灵活和健壮,特别是在处理动态内容和多设备适配时。

相关推荐
雨落秋垣22 分钟前
手搓 Java 的用户行为跟踪系统
java·开发语言·linq
爱丽_1 小时前
深入理解 Java Socket 编程与线程池:从阻塞 I/O 到高并发处理
java·开发语言
serendipity_hky2 小时前
互联网大厂Java面试故事:核心技术栈与场景化业务问题实战解析
java·spring boot·redis·elasticsearch·微服务·消息队列·内容社区
多敲代码防脱发2 小时前
为何引入Spring-cloud以及远程调用(RestTemplate)
java·开发语言
plmm烟酒僧2 小时前
TensorRT 推理 YOLO Demo 分享 (Python)
开发语言·python·yolo·tensorrt·runtime·推理
sailing-data2 小时前
【SE】接口标准化
java·开发语言
无名3873 小时前
RTPEngine 官方自带的 perl 测试程序
开发语言·perl·通信
fruge3 小时前
接口 Mock 工具对比:Mock.js、Easy Mock、Apifox 的使用场景与配置
开发语言·javascript·ecmascript
AI小云3 小时前
【数据操作与可视化】Matplotlib绘图-生成其他图表类型
开发语言·python·matplotlib
Elastic 中国社区官方博客3 小时前
ES|QL 在 9.2:智能查找连接和时间序列支持
大数据·数据库·人工智能·sql·elasticsearch·搜索引擎·全文检索