- 原文地址: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、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。