iOS Swift優雅的拆分UIViewController與View

MVC對於iOS開發的意義

對於iOS開發而言 始終沒法繞開UIKit這個框架, 加之SwiftUI並不成熟, 因此你懂的, 而UIKit框架就是基於的MVC的設計模式, 因此這也是爲何MVC是蘋果官方推薦的設計模式.git

爲何我我的比較推薦項目中用MVC.

一. 官方推薦這個標籤確定是有必定份量的.github

二. 在一個基於MVC的框架上強行使用MVVM或MVP的設計模式, 不是不能夠, 但總有些地方會差強人意, 當你使用各類知名框架時就會體會到這種感受, 固然你能夠選擇忽略不計, 但它們確實存在.swift

三. 簡單, 很是的簡單, 沒有學習成本, 是個開發者就懂, 基本沒有"交流"障礙. 越簡單越利於維護(這也是我比較推崇的開發風格 一切從簡).設計模式

四. 可擴展性強, 由於足夠通用, 因此在MVC基礎上能夠根據具體業務須要轉變成其餘設計模式, 總的來講 整個項目的基礎設計模式仍是MVC, 根據不一樣業務模塊狀況能夠再使用最合適的設計模式.框架

老生常淡 MVC的最大弊端

沒錯 臃腫的C層代碼. 因此不少優化方案都是圍繞這點展開的, 我們也不例外.ide

UIViewController與UIView的糾葛

UIViewController在實際開發中會遇到不少問題, 這裏咱們只說View相關的問題.佈局

按照蘋果的設計理念: UIViewController對應MVCC, UIView對應MVCV, XXXModel對應MVCM.學習

但尷尬的是 咱們使用UIViewController時 難免會有不少View的處理在其中, 純代碼的方式還好一些, 能夠經過封裝自定義View類來解決, StoryboardXib的方式就尤其明顯了.優化

純代碼:ui

自定義View類來編寫視圖相關的代碼, 能夠將V的處理從UIViewController中分離出去, 可是難免要在UIViewController中再次編寫初始化 佈局等代碼. 試想每一個UIViewController都要寫一遍某個View的初始化 添加父視圖 佈局等.

Storyboard或XIB:

拖線連接的控件一般會在UIViewController中, 常常見到UIViewController中拖了一堆視圖控件對象. 固然除了拖進來還要寫一下其餘視圖相關的代碼. 也有使用自定義View類來承載全部拖線連接的控件對象 和上面純代碼的方式差很少.

一樣的困境:UIViewController中view的類型永遠都是UIView, 上面兩種方式遇到的問題同樣, 須要作類型轉換才能訪問到自定義View類中的屬性和方法, 這無疑是很麻煩的.

Swift 泛型優雅的解決類型轉換問題

  • 建立一個基類 (實際項目開發中應該要有一個ViewController基類, 並保證基類的乾淨, 這是一個很好的習慣)
class ViewController<Container: UIView>: UIViewController {

    var container: Container { view as! Container }
    
    override func loadView() {
        super.loadView()
        if view is Container {
            return
        }
        view = Container()
    }
}
複製代碼
  • 全部視圖控制器都繼承自該基類, 並明確聲明該控制器所使用的View類型
class HomeController: ViewController<HomeView> {

    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
}
複製代碼
  • 經過container屬性直接訪問上面聲明的自定義View類型對象, 固然你也能夠改成其餘名字
class XXXXController: ViewController<XXXXView> {

    // CODE
  
    override func viewDidLoad() {
        super.viewDidLoad()
        // CODE
        container.xxxx()
    }
}
複製代碼

使用演示:

純代碼:

class XXXXView: UIView {

    private lazy var titleLabel = UILabel()
    private lazy var iconImageView = UIImageView()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        titleLabel.textColor = .black
        titleLabel.font = .systemFont(ofSize: 13, weight: .semibold)
        
        iconImageView.contentMode = .scaleAspectFill
        
        addSubview(titleLabel)
        addSubview(iconImageView)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        titleLabel.frame = .init(x: 100, y: 100, width: 100, height: 40)
        iconImageView.frame = .init(x: 100, y: 100, width: 100, height: 100)
    }
    
    func set(title: String) {
        titleLabel.text = title
    }
    
    func set(image: UIImage) {
        iconImageView.image = image
    }
}
複製代碼
class XXXXController: ViewController<XXXXView> {

    private let model = XXXXModel()
    // CODE
  
    override func viewDidLoad() {
        super.viewDidLoad()
        // CODE
        container.set(title: model.title)
        container.set(image: model.image)
    }
}
複製代碼
let controller = XXXXController()
present(controller, animated: true)
複製代碼

Storyboard或XIB:

設置Controller類:

設置Controller類

設置View類:

設置View類

向View中拖線連接:

向View中拖線連接

在Controller中爲視圖設置數據:

爲視圖設置數據


總結

方法簡單, 很好的解決了上面提到的這些問題, 使Controller與View的分離更加優雅.

class HomeController: ViewController<HomeView> { }
複製代碼

頭部的聲明能夠直觀的看到Controller的View類型, 可讀性強.

因明確了類型 調用更加順暢天然, 省去了多餘的類型轉換代碼.

使 Controller 能夠更專一於Model與View的協調和調用, 職責更明確.

截止如今, 這個方法我本身已經使用2年了 其中也經歷了幾個項目的洗禮, 仍是沒什麼問題的, 你們若是感興趣能夠放心採納.

Demo傳送門

若是你有更好的想法 歡迎評論交流.

相關文章
相關標籤/搜索