如何构建优雅的ViewController
前言
關于ViewController討論的最多的是它的肥胖和臃腫,即使使用傳統的MVC模式,ViewController也可以寫的很優雅,這無關乎設計模式,更多的是你對該模式理解有多深,你對于職責劃分的認知是否足夠清晰。ViewController也從很大程度上反應一個程序員的真實水平,初級程序員他的ViewController永遠是臃腫的、肥胖的,什么功能都可以往里面塞,不同功能間缺乏清晰的界限。而一個優秀的程序員它的ViewController顯得如此優雅,讓你產生一種竟不能修改一筆一畫的感覺。
ViewController職責
- UI 屬性配置 和 布局
- 用戶交互事件
- 用戶交互事件處理和回調
用戶交互事件處理: 通常會交給其他對象去處理 回調: 可以根據具體的設計模式和應用場景交給 ViewController 或者其他對象處理
而通常我們在閱讀別人ViewController代碼的時候,我們關注的是什么?
所以從這個角度來說,這三個功能一開始就應該是被分離的,需要有清晰明確的界限。因為誰都不希望自己在查找交互入口的時候 ,去閱讀一堆控件冗長的控件配置代碼, 更不愿意在一堆代碼中去慢慢理清整個用戶交互的流程。 我們通常只關心我當前最關注的東西,當看到一堆無關的代碼時,第一反應就是我想注釋掉它。
基于協議分離UI屬性的配置
protocol MFViewConfigurer {var rootView: UIView { get }var contentViews: [UIView] { get }var contentViewsSettings: [() -> Void] { get }func addSubViews()func configureSubViewsProperty()func configureSubViewsLayouts()func initUI() }復制代碼依賴這個協議就可以完成所有控件屬性配置,然后通過extension protocol 大大減少重復代碼,同時提高可讀性
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()} }復制代碼這里 我將控件的添加和控件的配置分成兩個函數addSubViews和configureSubViewsProperty, 因為在我的眼里函數就應該遵循單一職責這個概念: addSubViews: 明確告訴閱讀者,我這個控制器包含哪些控件 configureSubViewsProperty: 明確告訴閱讀者,控件的所有屬性配置都在這里,想要修改屬性請閱讀這個函數
來看一個實例:
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 這套協議同樣適用```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)}} }復制代碼由于我使用的是MVVM模式,所以viewDidLoad 和MVC模式還是有些區別,如果是MVC可能就是這樣
override func viewDidLoad() {super.viewDidLoad()// Do any additional setup after loading the view.// 初始化 UIinitUI()// 用戶交互事件入口addEvents()}// MARK: callBack......復制代碼由于MVC的回調模式很難統一,有Delegate, Closure, Notification、KVC等,所以回調通常會散落在控制器各個角落。最好加個MARK flag, 盡量收集在同一個區域中, 同時對于每個回調加上必要的注釋:
- 由哪種操作觸發
- 會導致什么后果
- 最終會通往哪里
所以從這個角度來說UITableViewDataSource 和 UITableViewDelegate 完全是兩種不一樣的行為, 一個是 configure UI , 一個是 control behavior , 所以不要在把這兩個東西寫一塊了, 真的很難看。
總結
基于職責對代碼進行分割,這樣會讓你的代碼變得更加優雅簡潔,會大大減少一些萬金油代碼的出現。減少閱讀代碼的成本也是我們優化的一個方向,畢竟誰都不想因為混亂的代碼影響自己的心情
總結
以上是生活随笔為你收集整理的如何构建优雅的ViewController的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SQL语句汇总(三)——聚合函数、分组、
- 下一篇: Go:json包的坑