- 原文地址:Writing Cleaner View Code in Swift By Overriding loadView()
- 原文做者:Bruno Rocha
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:RickeyBoy
- 校對者:徐鍵
究竟選擇使用 Storyboards 仍是純代碼書寫 view 是很是主觀的事情。在對兩種方式都進行了嘗試以後,我我的支持使用純代碼書寫 view 來完成項目,這樣可以容許多人編輯相同的類而不產生討厭的衝突,也更方便進行代碼審查。html
在最開始練習純代碼寫 view 的時候,人們廣泛遇到的一個問題是最開始不知道將代碼放在哪裏。若是你採用普通 storyboard 的方式,將全部相關代碼都放進你的 ViewController 之中,這樣很容易會最終產生一個巨大的上帝類:前端
final class MyViewController: UIViewController {
private let myButton: UIButton = {
//
}()
private let myView: UIView = {
//
}()
// 其餘 10 個左右的 view
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
private func setupViews() {
setupMyButton()
setupMyView()
// 設置其餘的 view
}
private func setupMyButton() {
view.addSubview(myButton)
// 十行約束代碼
}
private func setupMyView() {
view.addSubview(myView)
// 十行約束代碼
}
// 全部其餘的設置
// 全部 ViewModel 的邏輯
// 全部 Button 的點擊邏輯等東西...
}
複製代碼
你能夠經過把 view 移動到不一樣的文件並添加引用到原來的 ViewController 之中來改善這樣的狀況,可是你仍然須要用本不該該在 ViewController 中的內容填滿 ViewController,就好比約束代碼和其餘設置 view 的代碼 — 更不用說你如今有兩個 view 屬性(myView
和原生 view
)在 ViewController 之中,而這沒有任何好處。android
final class MyViewController: UIViewController {
let myView = MyView()
override func viewDidLoad() {
super.viewDidLoad()
setupMyView()
}
private func setupMyView() {
view.addSubview(myView)
// 10 行左右的約束代碼
myView.delegate = self
// 如今咱們同時有了 view 和 MyView...
}
}
複製代碼
臃腫的 ViewController 以及邏輯過多的 ViewController 都很是難以管理和維護。在像 MVVM 這樣的架構下,ViewController 應該主要做爲自身的 View 以及 ViewModel 之間的路由器 -- 設置而且約束 View 並非它們的職責,ViewController 只應該起到先後傳遞信息的路由做用。ios
在一個大部分代碼都是關於自身 View 的視圖代碼項目中,可以清晰地拆分你的架構中各部分的職責,對於一個便於維護的項目來講很是重要。你要讓你真正構建視圖部分的代碼徹底和你的 ViewController 分離 -- 幸運的是有一個簡單的方法,就是重寫 UIViewController
中原生的 View
屬性。這樣作容許你在分離的文件中管理你的多個 View,同時也仍能保證你的 ViewController 不用去設置任何 View。git
loadView()
是 UIViewController
中並不常見的一個方法,但它是 ViewController 的生命週期中很是重要的一部分,由於它承擔着最開始加載出 view
屬性的責任。當使用 Storyboard 的時候,它會加載出 nib 並將其附加給 view
,但當手動初始化 ViewController 時,這個方法所作的一切就是建立出一個空的 UIView
。你能夠重寫這個方法並改變它的行爲,而且在 ViewController 的 view
上添加任何類型的 view。github
final class MyViewController: UIViewController {
override func loadView() {
let myView = MyView()
myView.delegate = self
view = myView
}
override func viewDidLoad() {
super.viewDidLoad()
print(view) // 一個 MyView 的實例
}
}
複製代碼
注意 view
會自動的約束本身到 ViewController 的邊界,因此並不須要爲 myView
設置外部約束!swift
如今,view
成爲了我自定義的 view(在本例中爲 MyView
)的一個引用。你能夠在這個 view 獨立的文件內部構建其全部功能,而且 ViewController 對此毫無權限。太棒了!後端
爲了獲取 MyView
中的內容,你能夠將 View
強制轉換爲你本身的類型:bash
var myView: MyView {
return view as! MyView
}
複製代碼
這樣看起來有點奇怪,但這是由於 view
將仍然被定義爲 UIView
類型,而不是你爲它定義的類型。架構
爲了不個人 ViewController 中重複出現這樣的代碼,我喜歡建立一個 CustomView
協議,並在其中定義包含關聯類型的行爲:
/// HasCustomView 協議爲 UIViewController 定義了一個 customView 屬性,它是爲了去代替普通的 view 屬性。
/// 爲了實現這些,你必須在 loadView() 方法時爲你的 UIViewController 提供一個自定義的 View。
public protocol HasCustomView {
associatedtype CustomView: UIView
}
extension HasCustomView where Self: UIViewController {
/// UIViewController 的自定義 view。
public var customView: CustomView {
guard let customView = view as? CustomView else {
fatalError("Expected view to be of type \(CustomView.self) but got \(type(of: view)) instead")
}
return customView
}
}
複製代碼
最終會:
final class MyViewController: UIViewController, HasCustomView {
typealias CustomView = MyView
override func loadView() {
let customView = CustomView()
customView.delegate = self
view = customView
}
override func viewDidLoad() {
super.viewDidLoad()
customView.render() // 一些 MyView 的方法
}
}
複製代碼
若是每次都定義這個 CustomView
類型別名會讓你有點煩,那麼你能夠進一步在泛型類中定義這些行爲:
class CustomViewController<CustomView: UIView>: UIViewController {
var customView: CustomView {
return view as! CustomView // 由於咱們正在重寫 view,因此永遠不會解析失敗。
}
override func loadView() {
view = CustomView()
}
}
final class MyViewController: CustomViewController<MyView> {
override func loadView() {
super.loadView()
customView.delegate = self
}
}
複製代碼
我我的不太喜歡泛型的方式,由於編譯器並不容許泛型類具備的 @objc
方法的擴展,這會禁止你在擴展中擁有 UITableViewDataSource
之類的協議。可是,除非你須要作一些特殊的事情(好比設置委託),它會容許你跳太重寫 loadView()
這一步,從而能保持 ViewController 的整潔。
重寫 loadView()
是一個讓你的視圖代碼項目更加易於理解、易於維護的好方法,而且我已經使用 HasCustomView
方法得到了很是良好的效果,特別是在最近幾個項目中。編寫視圖部分的代碼也許不是你的選擇,可是它帶來了不少顯而易見的好處。嘗試一下吧,看看它是否是更適合你。
若是你有更好的定義 view 而且不須要 storyboard 的方法,或者你可能有一些疑問、意見或者反饋,請讓我知道。
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。