SnapKit 最佳實踐

四娘發表於2018-04-05

用了 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 可以挖掘的。

覺得文章還不錯的話可以關注一下我的部落格