用了 SnapKit 很久,一開始覺得這就是個很簡單的語法糖,後面用著用著還是覺得有點磕磕絆絆,所以又回去看過了一遍官方文件,發現了幾個 best practice 是我之前一直沒留意到的,就寫出來分享一下。
inset 是個高階抽象
剛開始使用 SnapKit 時,我都是直接使用 offset
來控制邊距的:
view.snp.makeConstraints {
$0.top.left.equalToSuperview().offset(10)
$0.right.bottom.equalToSuperview().offset(-10)
}
複製程式碼
offset
使用的是絕對值,例如說 superview
的 bottom
是 300 時,那 view
的 bottom
就會是 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 之間的邊距,而且修改起來也很方便。另外 CGPoint
和 CGSize
也遵循了這個協議,大家可以去挖掘更多有趣的用法,例如 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()
}
複製程式碼
但這種方式會讓程式碼看起來很混亂,並且 top
跟 bottom
的約束必須拆成兩行,一次性只能引用一個約束。更好的方式是使用 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 可以挖掘的。
覺得文章還不錯的話可以關注一下我的部落格