分拆 View Controller 中的自動(dòng)布局代碼

當(dāng)頁(yè)面復(fù)雜起來(lái)的時(shí)候,或者說(shuō),當(dāng)頁(yè)面控件數(shù)量太多的時(shí)候,自動(dòng)布局的代碼也會(huì)逐漸繁瑣起來(lái),造成了 View Controller 中的代碼繁重起來(lái)

因此,這里提供一種方法,將 UI 的布局代碼移動(dòng)到其他文件中,這個(gè)方法受到了 ?? 這篇文章 的啟發(fā),大部分的代碼也是從這里出來(lái)

布局的方法使用自動(dòng)布局,而且還是用 SnapKit 的那種,語(yǔ)言使用的是 Swift 3

創(chuàng)建一個(gè)關(guān)于自動(dòng)布局的協(xié)議

先創(chuàng)建一個(gè)文件,普通的 Swift 文件的那種,并在里面 import SnapKit

定義一個(gè) protocol

protocol Layoutable {
    func layoutMaker() -> (ConstraintMaker) -> Void
    func layoutUpdater() -> (ConstraintMaker) -> Void
}

其中 layoutMaker() 使用來(lái)創(chuàng)建約束,layoutUpdater() 是用來(lái)更新約束


這里的 (ConstraintMaker) -> Void 是什么?

看下面一段代碼,這是我們布局自定義控件的一般寫(xiě)法,至少是我吧

ivCalendar.snp.makeConstraints { (make) in
  make.left.equalTo(contentView).offset(PreviewCellConstants.marginToFrame)
  make.centerY.equalTo(lbDate)
  make.size.equalTo(PreviewCellConstants.LabelCalendar.size)
}

再看 makeConstraints 方法的定義

public func makeConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
   ConstraintMaker.makeConstraints(item: self.view, closure: closure)
}

可以看出,我們寫(xiě)的一大串的布局代碼,其實(shí)是一個(gè) Closure,類型是 (ConstraintMaker) -> Void,而這里,就是我們用來(lái)分拆代碼的關(guān)鍵了


對(duì)于上面的 func layoutUpdater() -> (ConstraintMaker) -> Void 協(xié)議方法,很多時(shí)候,我們只需要 make,而不太需要 update,這里可以選擇將它刪除。但是,也可以為它實(shí)現(xiàn)一個(gè)默認(rèn)的實(shí)現(xiàn),也就是 protocol 的 extension

如果沒(méi)用到 func layoutUpdater() -> (ConstraintMaker) -> Void 這個(gè)方法,而且不作處理(刪除它,或者提供默認(rèn)實(shí)現(xiàn)),那么,在 implement 協(xié)議的時(shí)候,編譯器將就會(huì)報(bào)錯(cuò):沒(méi)有 conforms to protocol

Layoutabel 的默認(rèn)實(shí)現(xiàn),只是簡(jiǎn)單地返回一個(gè)空的 Closure,當(dāng)我們有實(shí)現(xiàn)對(duì)應(yīng)的方法時(shí),調(diào)用的將會(huì)是后者

extension Layoutable {
    func layoutMaker() -> (ConstraintMaker) -> Void {
        return { make in
            
        }
    }
    
    func layoutUpdater() -> (ConstraintMaker) -> Void {
        return { make in
            
        }
    }
}

為 UIView 添加布局相關(guān)的方法

在與協(xié)議的同一個(gè)文件中,順便向 UIView 添加一個(gè) extension,添加布局方法,這個(gè)方法,是之后在 View Controller 中用到的一行代碼實(shí)現(xiàn)布局的方法


// MARK: - 自動(dòng)布局的 extension
extension UIView {
    func makeLayout(layouter: Layoutable) {
        snp.makeConstraints(layouter.layoutMaker())
    }
    
    func updateLayout(layouter: Layoutable) {
        snp.updateConstraints(layouter.layoutUpdater())
    }
}

可以看到,snp.makeConstraints(...) 這一句,正是我們布局時(shí)寫(xiě)得算是最多的一句代碼了。但現(xiàn)在,我們將會(huì)很少看到它了

同時(shí),我們也可以看到,snp.makeConstraints(...) 中傳入的參數(shù),都是通過(guò)協(xié)議來(lái)調(diào)用的(大致意思,意會(huì)一下吧),這也許就是 面向協(xié)議編程 的抽象吧

之后,我們的工作將會(huì)聚焦在:如何創(chuàng)建一個(gè)實(shí)現(xiàn) Layoutable 協(xié)議的類和它的實(shí)例

為 UI 控件的布局代碼創(chuàng)建一個(gè)文件

下面的工作,可能就顯得略煩瑣,甚至看起來(lái)有點(diǎn)“蠢”了

為 UI 控件創(chuàng)建一個(gè)文件

為你覺(jué)得需要的 UI 控件,創(chuàng)建一個(gè)普通的 Swift 文件,并 import SnapKit

創(chuàng)建一個(gè) struct,并聲明實(shí)現(xiàn) Layoutable 協(xié)議

struct ClipRecordPanelLayout: Layoutable {
    
}

為什么使用 struct,而不是使用 class ?

我的理解是這樣的:

對(duì)于 class,在賦值的時(shí)候,實(shí)例會(huì)對(duì)其屬性(對(duì)象的那種)默認(rèn)進(jìn)行了一個(gè)強(qiáng)引用,而這種強(qiáng)引用,很多時(shí)候就是造成內(nèi)存問(wèn)題(像循環(huán)引用)的原因

而對(duì)于 struct,在賦值的時(shí)候,實(shí)例只會(huì)對(duì)屬性進(jìn)行一系列的復(fù)制,不帶引用的復(fù)制,因此可以避免出現(xiàn)內(nèi)存問(wèn)題


定義一些屬性

struct ClipRecordPanelLayout: Layoutable {
    var views: (UIView)
    var constants: (panelHeight: CGFloat?, bottomOffset: CGFloat?)
}

在布局的時(shí)候,我們通常需要其他的一些 view 來(lái)作為參照物,并且需要寫(xiě)死一些常量,因此,在這里,使用了 views 和 constants 來(lái)分別裝載這些參照物和常量

views 和 constants 都是 tuple 的類型,使用 tuple 的原因是,tuple 可以(天生)存放不同類型的數(shù)據(jù),并且,可以為其中的值進(jìn)行命名

在這個(gè)例子中,constants 存放的都是 CGFloat? 類型,但也可以存放不同的類型,如

var constants: (bottomOffset: CGFloat, size: CGSize)

實(shí)現(xiàn)協(xié)議方法

struct ClipRecordPanelLayout: Layoutable {
    
    var views: (UIView)
    var constants: (panelHeight: CGFloat?, bottomOffset: CGFloat?)
    
    func layoutMaker() -> (ConstraintMaker) -> Void {
        let superView = views
        let (panelHeight, _) = constants
        return { make in
            make.bottom.equalTo(superView)
            make.left.right.equalTo(superView)
            make.height.equalTo(panelHeight!)
        }
    }
    
    func layoutUpdater() -> (ConstraintMaker) -> Void {
        let superView = views
        let (_, bottomOffset) = constants
        return { make in
            make.bottom.equalTo(superView).offset(bottomOffset!)
        }
    }
}

這里分別實(shí)現(xiàn)了 layoutMaker()layoutUpdater() 的方法,分別對(duì)應(yīng)的是建立約束和更新約束的操作

在獲取參照物和常量的時(shí)候,使用了 tuple 的解構(gòu),將數(shù)據(jù)分別存放到不同的變量中,不需要的數(shù)據(jù),直接使用 _ 進(jìn)行忽略

在 constants 中,其中的元素的數(shù)據(jù)類型使用的 CGFloat?,是因?yàn)檫@兩個(gè)數(shù)據(jù),并不是同時(shí)都需要用到,那么,為了方便起見(jiàn),在需要某一個(gè)參數(shù)的時(shí)候,直接傳 nil

所以,這里的一個(gè)文件,只定義了一類的約束(也許有多個(gè) UI 控件的布局方式是一樣的,就是常量不同罷了)

View Controller 中一句話完成控件的布局

定義完了一類控件的約束之后,最后,在 View Controller 中將約束應(yīng)用到 UI 控件中,像這樣:

panel.makeLayout(layouter: ClipRecordPanelLayout(with: (view), constants: (PanelHeight, nil)))

這樣,就一句話實(shí)現(xiàn)了對(duì) UI 控件的布局了,當(dāng)然這是針對(duì)簡(jiǎn)單的情況,要是含有多個(gè)參照物和常量,還是需要將 Layoutable 的實(shí)例單獨(dú)抽取出來(lái)新建,避免一行代碼過(guò)長(zhǎng)

References

MVVM與Controller瘦身實(shí)踐

對(duì)了,還有這個(gè)。。。

到我托放在 GitHub 的地方閱讀能進(jìn)行開(kāi)始跳轉(zhuǎn) ??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,036評(píng)論 25 709
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,315評(píng)論 4 61
  • 在, 無(wú)涯的生涯里, 我們, 都曾幻想過(guò)那美麗的夢(mèng) 美麗的夢(mèng)和美麗的詩(shī)一樣 可遇而不可求, 常常讓我深陷其中, 不...
    奔跑的胖胖蝸牛閱讀 354評(píng)論 2 3
  • 春節(jié)臨近,很多人在朋友圈曬旅游的照片,那份傲嬌的樣子讓人嫉妒,但機(jī)智的浪子哥又豈能會(huì)被表象所迷惑?講真,要說(shuō)風(fēng)景,...
    蕭看風(fēng)云閱讀 485評(píng)論 0 1
  • 對(duì)于剛進(jìn)入職場(chǎng)的小白來(lái)說(shuō),EXCEL可謂是必備技能,熟練掌握并使用這種工具,會(huì)明顯提高你的工作效率,減少加班時(shí)間。...
    艾斯微博閱讀 2,674評(píng)論 13 106

友情鏈接更多精彩內(nèi)容