
原創(chuàng)作者:Paul Hudson
原文鏈接:How to move view code out of your view controllers
這是解決 “臃腫的ViewController” 這個問題系列教程中的第三部分:
- 如何在 iOS app 中使用協(xié)調(diào)模式
- 如何把數(shù)據(jù)源和代理從你的 ViewController 抽離出來
- 如何把關(guān)于視圖構(gòu)建的代碼搬離 ViewController
想要用代碼而不是使用 interface Builder 來編寫你的用戶界面的原因有很多: 你可能會發(fā)現(xiàn)使用源代碼管理更容易,也可能會發(fā)現(xiàn)它是一個更具表現(xiàn)力的環(huán)境,用于編寫復雜的自動布局約束,或者你可能更喜歡通過組合函數(shù)來創(chuàng)建界面。
但是,這樣做有正確的方法也有錯誤的方法,太多的應用程序會做出錯誤的決定。你會看到,即使你將視圖控制器視為視圖層的一部分,而不是控制器層(MVC中的V而不是C),它們?nèi)匀粦搶嶋H的視圖代碼留給 UIView 及其許多子類。
盡管應該由 UIView 或其子類來處理視圖代碼是無需爭論的,但是你經(jīng)常會在視圖控制器的 viewDidLoad() 方法內(nèi)部看到各種視圖的創(chuàng)建和配置,這幾乎肯定是錯誤的。 在視圖加載完成時調(diào)用該方法,而不是在開始創(chuàng)建視圖時調(diào)用該方法。
將此類代碼移動到自定義 UIView 子類中,不僅使視圖控制器更加簡單,而且還允許你根據(jù)需要在不同的地方重用視圖代碼。 更好的是,一旦將視圖控制器變得更小,更簡單,您就可以開始更多地依賴于視圖控制器的封閉性以使其也可重復使用,最終得到的是松散耦合的更靈活的代碼。
與其抽象地討論所有這些,不如看一個具體的例子來說明人們?nèi)绾位旌弦晥D和視圖控制器。 將您的視線投向這種代碼:
backgroundColor = UIColor(white: 0.9, alpha: 1)
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.spacing = 10
view.addSubview(stackView)
stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
stackView.axis = .vertical
let notice = UILabel()
notice.numberOfLines = 0
notice.text = "Your child has attempted to share the following photo from the camera:"
stackView.addArrangedSubview(notice)
let imageView = UIImageView(image: shareImage)
stackView.addArrangedSubview(imageView)
let prompt = UILabel()
prompt.numberOfLines = 0
prompt.text = "What do you want to do?"
stackView.addArrangedSubview(prompt)
for option in ["Always Allow", "Allow Once", "Deny", "Manage Settings"] {
let button = UIButton(type: .system)
button.setTitle(option, for: .normal)
stackView.addArrangedSubview(button)
}
那甚至不是一個復雜的用戶界面,但這是你會在 viewDidLoad() 中看到的那種東西,盡管那是放置它的可怕地方。
上面的所有代碼都是視圖代碼,因此需要這樣對待。 它不是控制器代碼,即使使用 Apple 的混亂定義,它也不是視圖控制器代碼。 它是 View 的代碼,屬于 UIView 的子類。
進行此更改很容易:復制所有代碼,將其粘貼到 UIView 的新子類 SharePromptView 中,然后將視圖控制器視圖的類更改為新的子類。
最終的 SharePromptView 類應如下所示:
class SharePromptView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
createSubviews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
createSubviews()
}
func createSubviews() {
// all the layout code from above
}
}
所有的 UIView 子類必須實現(xiàn) init(coder:),但是當你在代碼中創(chuàng)建你的 UI 時,你還需要添加 init(frame:),createSubviews()方法來構(gòu)建視圖。
由于有了自定義的 UIView 子類,你現(xiàn)在可以從視圖控制器中提取大量代碼:
class ViewController: UIViewController {
var shareView = SharePromptView()
override func loadView() {
view = shareView
}
}
loadView()方法是以編程方式加載視圖的正確位置。
注意:擁有專用的 shareView 屬性可讓您訪問在 SharePromptView 中聲明的任何屬性,而不必強制轉(zhuǎn)換視圖類型。
那么,視圖控制器應該是什么?
我經(jīng)常問這個問題。 我已經(jīng)討論過如何使用協(xié)調(diào)器完成導航,以及如何從視圖控制器中分離出委托和源代碼,所以視圖控制器應該做什么?
在我自己的代碼中,我努力使視圖控制器盡可能簡單,因為我親眼目睹了失去控制會發(fā)生什么。 這意味著他們:
- 負責視圖生命周期事件,例如
viewDidLoad(),viewWillAppear()和traitCollectionDidChange()。 - 有一些
@IBOutlets和@IBActions,盡管這些動作實際上應該只是在其他地方運行一個方法。 請記住,你可以根據(jù)需要在視圖中添加出口。 - 在模型和視圖之間穿梭數(shù)據(jù)。 這不會增加諸如格式化數(shù)據(jù)之類的代碼,它只是將值綁定到他們的視圖并將更改從用戶發(fā)回。
他們還可以根據(jù)我在做什么來處理模型的獲取和存儲; 我覺得非常實用。
正如 Dave DeLong 所說的那樣,“ 300行視圖控制器或崩潰”。當你編寫301行時,不會發(fā)生任何不好的事情,當然我不會為了讓 SwiftLint 通過檢測脫身而將視圖控制器重構(gòu)為擴展。 但這確確實實表示你正在使視圖控制器承擔過多的責任。