- 原文地址:Writing Cleaner View Code in Swift By Overriding loadView()
- 原文作者:Bruno Rocha
- 譯文出自:掘金翻譯計劃
- 本文永久連結:github.com/xitu/gold-m…
- 譯者:RickeyBoy
- 校對者:徐鍵
究竟選擇使用 Storyboards 還是純程式碼書寫 view 是非常主觀的事情。在對兩種方式都進行了嘗試之後,我個人支援使用純程式碼書寫 view 來完成專案,這樣能夠允許多人編輯相同的類而不產生討厭的衝突,也更方便進行程式碼審查。
在最開始練習純程式碼寫 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 之中,而這沒有任何好處。
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 只應該起到前後傳遞資訊的路由作用。
在一個大部分程式碼都是關於自身 View 的檢視程式碼專案中,能夠清晰地拆分你的架構中各部分的職責,對於一個便於維護的專案來說非常重要。你要讓你真正構建檢視部分的程式碼完全和你的 ViewController 分離 -- 幸運的是有一個簡單的方法,就是重寫 UIViewController
中原生的 View
屬性。這樣做允許你在分離的檔案中管理你的多個 View,同時也仍能保證你的 ViewController 不用去設定任何 View。
loadView()
loadView()
是 UIViewController
中並不常見的一個方法,但它是 ViewController 的生命週期中非常重要的一部分,因為它承擔著最開始載入出 view
屬性的責任。當使用 Storyboard 的時候,它會載入出 nib 並將其附加給 view
,但當手動初始化 ViewController 時,這個方法所做的一切就是建立出一個空的 UIView
。你可以重寫這個方法並改變它的行為,並且在 ViewController 的 view
上新增任何型別的 view。
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
設定外部約束!
現在,view
成為了我自定義的 view(在本例中為 MyView
)的一個引用。你可以在這個 view 獨立的檔案內部構建其所有功能,並且 ViewController 對此毫無許可權。太棒了!
為了獲取 MyView
中的內容,你可以將 View
強制轉換為你自己的型別:
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、前端、後端、區塊鏈、產品、設計、人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。