如何構(gòu)建優(yōu)雅的ViewController

如何構(gòu)建優(yōu)雅的ViewController

前言

關(guān)于ViewController討論的最多的是它的肥胖和臃腫,但是哪怕是采用MVC模式,ViewController同樣可以寫的很優(yōu)雅,這無關(guān)乎設(shè)計模式,對于那些以設(shè)計模式論高低的,我只能呵呵。其實這關(guān)乎的是你對設(shè)計模式的理解有多深,你對于職責(zé)劃分的認(rèn)知是否足夠清晰。ViewController也從很大程度上反應(yīng)一個程序員的真實水平,一個平庸的程序員他的ViewController永遠(yuǎn)是臃腫的、肥胖的,什么功能都可以往里面塞,不同功能間缺乏清晰的界限。而一個優(yōu)秀的程序員它的ViewController顯得如此優(yōu)雅,讓你產(chǎn)生一種竟不能修改一筆一畫的感覺。

ViewController職責(zé)

  • UI 屬性 和 布局
  • 用戶交互事件
  • 用戶交互事件處理和回調(diào)

用戶交互事件處理: 通常會交給其他對象去處理
回調(diào): 可以根據(jù)具體的設(shè)計模式和應(yīng)用場景交給 ViewController 或者其他對象處理

而通常我們在閱讀別人ViewController代碼的時候,我們關(guān)注的是什么?

  1. 控件屬性配置在哪里?
  2. 用戶交互的入口位置在哪里?
  3. 用戶交互會產(chǎn)生什么樣的結(jié)果?(回調(diào)在哪里?)

所以從這個角度來說,這三個功能一開始就應(yīng)該是被分離的,需要有清新明確的界限。因為誰都不希望自己在查找交互入口的時候 ,去閱讀一堆控件冗長的控件配置代碼, 更不愿意在一堆代碼去慢慢理清整個用戶交互的流程。 我們通常只關(guān)心我當(dāng)前最關(guān)注的東西,當(dāng)看到一堆無關(guān)的代碼時,第一反應(yīng)就是我想注釋掉它。

基于協(xié)議分離UI屬性的配置


protocol MFViewConfigurer {
    var rootView: UIView { get }
    var contentViews: [UIView] { get }
    var contentViewsSettings: [() -> Void] { get }

    func addSubViews()
    func configureSubViewsProperty()
    func configureSubViewsLayouts()

    func initUI()
}


依賴這個協(xié)議就可以完成所有控件屬性配置,然后通過extension protocol 大大減少重復(fù)代碼,同時提高可讀性


extension MFViewConfigurer {
    func addSubViews() {
        for element in contentViews {
            if let rootView = rootView as? UIStackView {
                rootView.addArrangedSubview(element)
            } else {
                rootView.addSubview(element)
            }
        }
    }

    func configureSubViewsProperty() {
        for element in contentViewsSettings {
            element()
        }
    }

    func configureSubViewsLayouts() {
    }

    func initUI() {
        addSubViews()
        configureSubViewsProperty()
        configureSubViewsLayouts()
    }
}


這里 我將控件的添加和控件的配置分成兩個函數(shù)addSubViewsconfigureSubViewsProperty, 因為在我的眼里函數(shù)就應(yīng)該遵循單一職責(zé)這個概念:
addSubViews: 明確告訴閱讀者,我這個控制器只有這些控件
configureSubViewsProperty: 明確告訴閱讀者,控件的所有屬性配置都在這里,想要修改屬性請閱讀這個函數(shù)

來看一個實例:


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

         // 初始化 UI
        initUI()
    
         // 綁定用戶交互事件
        bindEvent()

         // 將ViewModel.value  綁定至控件
        bindValueToUI()
       
    }
    
    // MARK: - UI configure

// MARK: - UI

extension MFWeatherViewController: MFViewConfigurer {
    var contentViews: [UIView] { return [scrollView, cancelButton] }

    var contentViewsSettings: [() -> Void] {
        return [{
            self.view.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.7)
            self.scrollView.hiddenSubViews(isHidden: false)
        }]
    }

    func configureSubViewsLayouts() {
        cancelButton.snp.makeConstraints { make in
            if #available(iOS 11, *) {
                make.top.equalTo(self.view.safeAreaLayoutGuide.snp.top)
            } else {
                make.top.equalTo(self.view.snp.top).offset(20)
            }

            make.left.equalTo(self.view).offset(20)
            make.height.width.equalTo(30)
        }

        scrollView.snp.makeConstraints { make in
            make.top.bottom.left.right.equalTo(self.view)
        }
    }

}


而對于UIView 這套協(xié)議同樣適用

```Swift
// MFWeatherSummaryView
    private override init(frame: CGRect) {
        super.init(frame: frame)

        initUI()
    }
    
    
// MARK: - UI

extension MFWeatherSummaryView: MFViewConfigurer {
    var rootView: UIView { return self }

    var contentViews: [UIView] {
        return [
            cityLabel,
            weatherSummaryLabel,
            temperatureLabel,
            weatherSummaryImageView,
        ]
    }

    var contentViewsSettings: [() -> Void] {
        return [UIConfigure]
    }

    private func UIConfigure() {
        backgroundColor = UIColor.clear
    }

    public func configureSubViewsLayouts() {
        cityLabel.snp.makeConstraints { make in
            make.top.centerX.equalTo(self)
            make.bottom.equalTo(temperatureLabel.snp.top).offset(-10)
        }

        temperatureLabel.snp.makeConstraints { make in
            make.top.equalTo(cityLabel.snp.bottom).offset(10)
            make.right.equalTo(self.snp.centerX).offset(0)
            make.bottom.equalTo(self)
        }

        weatherSummaryImageView.snp.makeConstraints { make in
            make.left.equalTo(self.snp.centerX).offset(20)
            make.bottom.equalTo(temperatureLabel.snp.lastBaseline)
            make.top.equalTo(weatherSummaryLabel.snp.bottom).offset(5)
            make.height.equalTo(weatherSummaryImageView.snp.width).multipliedBy(61.0 / 69.0)
        }

        weatherSummaryLabel.snp.makeConstraints { make in
            make.top.equalTo(temperatureLabel).offset(20)
            make.centerX.equalTo(weatherSummaryImageView)
            make.bottom.equalTo(weatherSummaryImageView.snp.top).offset(-5)
        }
    }
}



由于我使用的是MVVM模式,所以viewDidLoad 和MVC模式還是有些區(qū)別,如果是MVC可能就是這樣


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

         // 初始化 UI
        initUI()
    
         // 用戶交互事件入口
        addEvents()

       
    }
    
 // MARK: callBack
 ......

由于MVC的回調(diào)模式很難統(tǒng)一,有Delegate, closure, notification 等等,所以回調(diào)通常會散落在控制器各個角落。最好加個MARK flag, 盡量收集在同一個區(qū)域中, 同時對于每個回調(diào)加上必要的注釋:

  • 由哪種操作觸發(fā)
  • 會導(dǎo)致什么后果
  • 最終會留下哪里

所以從這個角度來說UITableViewDataSourceUITableViewDelegate 完全是兩種不一樣的行為, 一個是 configure UI , 一個是 control behavior , 所以不要在把這兩個東西寫一塊了, 真的很難看。

總結(jié)

基于職責(zé)對代碼進(jìn)行分割,這樣會讓你的代碼變得更加優(yōu)雅簡潔,會大大減少一些萬金油代碼的出現(xiàn),減少閱讀代碼的成本也是我們優(yōu)化的一個方向,比較誰都不想因為混亂的代碼影響自己的心情

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

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

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,656評論 1 32
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,319評論 4 61
  • 想象一下,每天早晨醒來后,你會拿起手機查看微信有沒有新消息,順便刷一下朋友圈又有哪些更新的奇聞軼事,吃完早飯上班路...
    朱晨銘閱讀 2,537評論 1 33
  • 圖片發(fā)自簡書App 水調(diào)歌頭—戲以自壽平水韻每逢佳辰日,恐捋到吟髭。生辰月宿南斗,正合退之詩。今歲掐指一算,驚覺恰...
    mr逃禪閱讀 372評論 0 0
  • 引用的MODULE里unable to resolve xxxx 主build里加allprojects {rep...
    Foo_d488閱讀 212評論 0 0

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