SnapKit 最佳实践

用了 SnapKit 很久,一开始觉得这就是个很简单的语法糖,后面用着用着还是觉得有点磕磕绊绊,所以又回去看过了一遍官方文档,发现了几个 best practice 是我之前一直没留意到的,就写出来分享一下。

inset 是个高级抽象

刚开始使用 SnapKit 时,我都是直接使用 offset 来控制边距的:

view.snp.makeConstraints {
$0.top.left.equalToSuperview().offset(10)
$0.right.bottom.equalToSuperview().offset(-10)
}

offset 使用的是绝对值,例如说 superviewbottom 是 300 时,那 viewbottom 就会是 300 + (-10)

为了简化在这种情况下的语法,SnapKit 封装了一个高级抽象 inset,帮我们自动转换:

switch layoutAttribute {
case .left : return value.left
case .top : return value.top
case .right : return -value.right
case .bottom : return -value.bottom
...
}

使用 inset,之前的代码就可以简化成这样:

view.snp.makeConstraints {
$0.top.left.bottom.right.equalToSuperview().inset(10)
// 或者直接使用 edges
$0.edges.equalToSuperview().inset(10)
}

总结一句就是,在描述 view 与 superview 关系时,应该使用 inset,而描述 view 与同一层级的其它 view 时,应该使用 offset

不可忽视的 ConstraintConstantTarget

在一个 view 里,一般来说设计师都会给 content 一个统一的边距,类似于 h5 里 padding 的概念,在构建约束时我们经常会把这个 padding 分散到各处:

container.addSubview(a)
container.addSubview(b)

a.snp.makeConstraints {
$0.top.equalToSuperview().inset(5)
$0.left.right.equalToSuperview().inset(15)
}

b.snp.makeConstraints {
$0.top.equalTo(a.snp.bottom).offset(5)
$0.left.right.equalToSuperview().inset(15)
$0.bottom.equalToSuperview().inset(5)
}

同是 padding 但却分散去处理是一件很糟糕的事情,更好的方式是使用已有的抽象 UIEdgeInsets

在调用 equalTo, offset 或者 inset 传入数值时,我们会发现传入的参数类型实际上只有 ConstraintConstantTarget,这是一个协议,SnapKit 把它作为一个类簇在使用,通过一个方法将它转化为 CGFloat 来作为 constraint 的 constant

UIEdgeInsets 也遵循了这个协议,所以我们可以更加优雅地去处理边距:

let containerInsets = UIEdgeInsets(top: 5, left: 15, bottom: 5, right:15)

container.addSubview(a)
container.addSubview(b)

a.snp.makeConstraints {
$0.top.left.right.equalToSuperview().inset(containerInsets)
}

b.snp.makeConstraints {
$0.top.equalTo(a.snp.bottom).offset(5)
$0.left.bottom.right.equalToSuperview().inset(containerInsets)
}

通过这样的代码,绝大部分时候我们都可以只用一行代码去描述 view 跟 superview 之间的边距,而且修改起来也很方便。另外 CGPointCGSize 也遵循了这个协议,大家可以去挖掘更多有趣的用法,例如 size.equalTo(20)

修改约束时尽量使用 updateConstraints

原生的 NSLayoutConstraint 在使用时,如果我们需要修改 constant 的值,一般会使用一个变量去引用,有需要时再去通过这个引用修改它的 constant

同样的方式也适用于 SnapKit,我们可以通过 constraint 方法去获取到这个约束,然后强引用它:

var someConstraint: Constriant?

a.snp.makeConstriants {
someConstraint = $0.top.equalToSuperview().constraint
$0.left.equalToSuperview().inset(15)
$0.bottom.equalToSuperview()
}

但这种方式会让代码看起来很混乱,并且 topbottom 的约束必须拆成两行,一次性只能引用一个约束。更好的方式是使用 updateConstraints 方法:

a.snp.makeConstriants {
$0.top.bottom.equalToSuperview()
$0.left.equalToSuperview().inset(15)
}

...

a.snp.updateConstraints {
$0.top.equalToSuperview().inset(10)
}

这个方法会遍历现有的所有约束,然后找到你在 updateConstraints 里更新的约束,更新它们的 constant

这么做的好处就是语法更简洁一致,让约束表现得更像是 view 的属性。但缺点也很明显,只能更新 constant

小结

我个人感觉写好业务逻辑也不是一件容易的事情,但难度不是在于实现,而是可维护性跟实现速度,这里面还是有很多 best pratice 可以挖掘的。