再看SnapKit

weixin_34138377發表於2018-06-01

連續兩個多月高強度的工作,每天幾乎加班到8點才下班,專案在今天上午終於是提交到 App Store 了。整個人瞬時如釋重負,但卻有種空虛感,不知道做什麼了。再看下SnapKit吧。發現之前一直認為就那麼用來設定約束即可,沒留意到的地方還挺多。下面分別概述下。

一、inset 和 offset

相信大部分小夥伴們都是使用 offset 來控制邊距的吧。

cciView.snp.makeConstraints { (make) in
    make.top.equalTo(researchView.snp.bottom).offset(kHeight.d10)
    make.left.equalTo(10)
    make.right.equalTo(-10)
    make.bottom.equalTo(bottomView.snp.bottom)
}

其實 offset 使用的是絕對值,尤其是設定右、下約束的時候,子控制元件相對於父控制元件都需要加-號。這一點使用久了,和蘋果的 xib 比起來,或多或少覺得有點不太合理。其實該框架是有 inset 來抽象控制的。下面貼下框架的原始碼:

public class ConstraintMakerEditable: ConstraintMakerPriortizable {
    
    @discardableResult
    public func multipliedBy(_ amount: ConstraintMultiplierTarget) -> ConstraintMakerEditable {
        self.description.multiplier = amount
        return self
    }
    
    @discardableResult
    public func dividedBy(_ amount: ConstraintMultiplierTarget) -> ConstraintMakerEditable {
        return self.multipliedBy(1.0 / amount.constraintMultiplierTargetValue)
    }
    
    @discardableResult
    public func offset(_ amount: ConstraintOffsetTarget) -> ConstraintMakerEditable {
        self.description.constant = amount.constraintOffsetTargetValue
        return self
    }
    
    @discardableResult
    public func inset(_ amount: ConstraintInsetTarget) -> ConstraintMakerEditable {
        self.description.constant = amount.constraintInsetTargetValue
        return self
    }
    
}

使用 inset,之前的程式碼就可以簡化成這樣(只是舉例):

   make.top.equalTo(researchView.snp.bottom).offset(kHeight.d10)
        make.left.equalTo(10)
        make.right.equalToSuperview().inset(10)
        make.bottom.equalTo(bottomView.snp.bottom)
    }

總結下來就是:在描述 view 與 superview 關係時,應該使用 inset,而描述 view 與同一層級的其它 view 時,應該使用 offset。

二、ConstraintConstantTarget

在佈局 view 的時候,一般來說設計師都會給 content 一個統一的邊距,類似於 html 中的 padding,在構建約束時我們經常會把這個 padding 分散到各處。但事實上,將 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)
}

這樣,後期修改起來會很容易,程式碼也簡潔了很多。另外 CGPoint 和 CGSize 也遵循了這個協議,大家可以去研究下更多有趣的用法,例如 size.equalTo(20)。

三、修改約束儘量用 updateConstraints

如果要更新佈局約束,小弟之前的做法是仿照蘋果,用區域性變數來引用,然後更新。如下:

// 區域性變數儲存約束屬性
private var bottomViewConsT: Constraint?

// 展示 view 時更新底部約束
bottomViewConsT?.update(offset: -tmpBottomConsH)
UIView.animate(withDuration: kTime.duration, animations: {[weak self] in
        self?.layoutIfNeeded()
}) {  (_) in
}

// 設定約束
bottomView.snp.makeConstraints { (make) in
    make.left.right.equalTo(self).offset(0)
    make.height.equalTo(frame.height * 0.35)
    bottomViewConsT = make.top.equalTo(self.snp.bottom).offset(0).constraint
    }

// 隱藏 view 時更新底部約束
bottomViewConsT?.update(offset: 0)
  UIView.animate(withDuration: 0.1, animations: { [weak self] in
    self?.layoutIfNeeded()
}) { [weak self] (_) in
    self?.removeFromSuperview()
}

其實可以改為:

// 展示 view 時更新底部約束
bottomView.snp.updateConstraints { (make) in
    make.top.equalTo(self.snp.bottom).inset(0)
}

// 設定約束
bottomView.snp.makeConstraints { (make) in
    make.left.right.equalTo(self).offset(0)
    make.height.equalTo(frame.height * 0.35)
    bottomViewConsT = make.top.equalTo(self.snp.bottom).offset(0).constraint
}
// ……

這麼做的好處就是語法更簡潔一致,讓約束表現得更像是 view 的屬性。但缺點也很明顯,只能更新 constant。

暫時就這些吧(^_^)~~~

相關文章