如何构建优雅的ViewController
前言
關(guān)于ViewController討論的最多的是它的肥胖和臃腫,即使使用傳統(tǒng)的MVC模式,ViewController也可以寫的很優(yōu)雅,這無關(guān)乎設(shè)計模式,更多的是你對該模式理解有多深,你對于職責(zé)劃分的認(rèn)知是否足夠清晰。ViewController也從很大程度上反應(yīng)一個程序員的真實水平,初級程序員他的ViewController永遠是臃腫的、肥胖的,什么功能都可以往里面塞,不同功能間缺乏清晰的界限。而一個優(yōu)秀的程序員它的ViewController顯得如此優(yōu)雅,讓你產(chǎn)生一種竟不能修改一筆一畫的感覺。
ViewController職責(zé)
- UI 屬性配置 和 布局
- 用戶交互事件
- 用戶交互事件處理和回調(diào)
用戶交互事件處理: 通常會交給其他對象去處理 回調(diào): 可以根據(jù)具體的設(shè)計模式和應(yīng)用場景交給 ViewController 或者其他對象處理
而通常我們在閱讀別人ViewController代碼的時候,我們關(guān)注的是什么?
所以從這個角度來說,這三個功能一開始就應(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() }復(fù)制代碼依賴這個協(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()} }復(fù)制代碼這里 我將控件的添加和控件的配置分成兩個函數(shù)addSubViews和configureSubViewsProperty, 因為在我的眼里函數(shù)就應(yīng)該遵循單一職責(zé)這個概念: addSubViews: 明確告訴閱讀者,我這個控制器包含哪些控件 configureSubViewsProperty: 明確告訴閱讀者,控件的所有屬性配置都在這里,想要修改屬性請閱讀這個函數(shù)
來看一個實例:
override func viewDidLoad() {super.viewDidLoad()// Do any additional setup after loading the view.// 初始化 UIinitUI()// 綁定用戶交互事件bindEvent()// 將ViewModel.value 綁定至控件bindValueToUI()}// MARK: - UI configure// MARK: - UIextension 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 inif #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 inmake.top.bottom.left.right.equalTo(self.view)}}}而對于UIView 這套協(xié)議同樣適用```Swift // MFWeatherSummaryViewprivate override init(frame: CGRect) {super.init(frame: frame)initUI()}// MARK: - UIextension 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 inmake.top.centerX.equalTo(self)make.bottom.equalTo(temperatureLabel.snp.top).offset(-10)}temperatureLabel.snp.makeConstraints { make inmake.top.equalTo(cityLabel.snp.bottom).offset(10)make.right.equalTo(self.snp.centerX).offset(0)make.bottom.equalTo(self)}weatherSummaryImageView.snp.makeConstraints { make inmake.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 inmake.top.equalTo(temperatureLabel).offset(20)make.centerX.equalTo(weatherSummaryImageView)make.bottom.equalTo(weatherSummaryImageView.snp.top).offset(-5)}} }復(fù)制代碼由于我使用的是MVVM模式,所以viewDidLoad 和MVC模式還是有些區(qū)別,如果是MVC可能就是這樣
override func viewDidLoad() {super.viewDidLoad()// Do any additional setup after loading the view.// 初始化 UIinitUI()// 用戶交互事件入口addEvents()}// MARK: callBack......復(fù)制代碼由于MVC的回調(diào)模式很難統(tǒng)一,有Delegate, Closure, Notification、KVC等,所以回調(diào)通常會散落在控制器各個角落。最好加個MARK flag, 盡量收集在同一個區(qū)域中, 同時對于每個回調(diào)加上必要的注釋:
- 由哪種操作觸發(fā)
- 會導(dǎo)致什么后果
- 最終會通往哪里
所以從這個角度來說UITableViewDataSource 和 UITableViewDelegate 完全是兩種不一樣的行為, 一個是 configure UI , 一個是 control behavior , 所以不要在把這兩個東西寫一塊了, 真的很難看。
總結(jié)
基于職責(zé)對代碼進行分割,這樣會讓你的代碼變得更加優(yōu)雅簡潔,會大大減少一些萬金油代碼的出現(xiàn)。減少閱讀代碼的成本也是我們優(yōu)化的一個方向,畢竟誰都不想因為混亂的代碼影響自己的心情
總結(jié)
以上是生活随笔為你收集整理的如何构建优雅的ViewController的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SQL语句汇总(三)——聚合函数、分组、
- 下一篇: 好程序员web前端分享数组及排序、去重和