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. 优先保证内容可读性,其次考虑设计精确度

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

相关推荐
q***18842 小时前
搭建Golang gRPC环境:protoc、protoc-gen-go 和 protoc-gen-go-grpc 工具安装教程
开发语言·后端·golang
多多*2 小时前
一个有 IP 的服务端监听了某个端口,那么他的 TCP 最大链接数是多少
java·开发语言·网络·网络协议·tcp/ip·缓存·mybatis
Kay_Liang2 小时前
Spring IOC核心原理与实战技巧
java·开发语言·spring boot·spring·ioc·依赖注入·控制反转
xcLeigh2 小时前
Rust入门:基础语法应用
开发语言·rust·编程·教程·基础语法
Mr.wangh2 小时前
单例模式&阻塞队列详解
java·开发语言·单例模式·多线程·阻塞队列
nvd113 小时前
Lit.js 入门介绍:与 React 的对比
开发语言·javascript·react.js
张较瘦_3 小时前
[论文阅读] 软件工程 | 解决Java项目痛点:DepUpdater如何平衡依赖升级的“快”与“稳”
java·开发语言·论文阅读
HalvmånEver3 小时前
Linux:基础开发工具(一)
linux·运维·服务器·开发语言·学习·进阶学习
杜子不疼.3 小时前
【C++】深入拆解二叉搜索树:从递归与非递归双视角,彻底掌握STL容器的基石
开发语言·c++