- Writing Cleaner View Code in Swift By Ancient loadView()
- Bruno Rocha
- The Nuggets translation Project
- Permanent link to this article: github.com/xitu/gold-m…
- Translator: RickeyBoy
- Proofreader: Xu Jian
The choice between using Storyboards or writing a view in pure code is very subjective. Having tried both approaches, I personally favor using pure code to write views for projects, which allows multiple people to edit the same class without annoying collisions and makes code review easier.
One of the common problems people encounter when they first practice writing views in pure code is that they don’t know where to put the code at first. If you take a normal storyboard approach and put all the relevant code into your ViewController, it’s easy to end up with a giant God class:
final class MyViewController: UIViewController {
private let myButton: UIButton = {
//
}()
private letMyView: UIView = {//}() // Other 10 views override funcviewDidLoad() {
super.viewDidLoad()
setupViews()
}
private func setupViews() {setupMyButton() setupMyView() // Set other views} private funcsetupMyButton() {view.addSubView (myButton)} private funcsetupMyView() {view.addSubView (myView) // ten lines of constraint code} // all other Settings // all ViewModel logic // all Button click logic etc... }Copy the code
You can improve this by moving the view to a different file and adding references to the original ViewController, but you still need to fill the ViewController with stuff that shouldn’t be in the ViewController, Such as constraint code and other code that sets the view — not to mention that you now have two view properties (myView and native View) in the ViewController, which doesn’t do any good.
final class MyViewController: UIViewController {
let myView = MyView()
override func viewDidLoad() {
super.viewDidLoad()
setupMyView()
}
private func setupMyView() {view.addSubView (myView) // 10 lines of constraint code myView.delegate = self // Now we have both view and myView... }}Copy the code
Bloated viewcontrollers and overly logical viewcontrollers are very difficult to manage and maintain. In an architecture like MVVM, viewControllers should act primarily as routers between their own views and viewModels — it’s not their job to set up and constrain views, The ViewController should only serve as a route for passing information back and forth.
In a View code project where most of the code is about its own View, being able to clearly split the responsibilities of each part of your architecture is important for a maintainable project. You want to keep your code that actually builds the View part completely separate from your ViewController — luckily there’s an easy way to override the View properties native to UIViewController. This allows you to manage multiple views in separate files while still ensuring that your ViewController doesn’t have to set any views.
loadView()
LoadView () is not a common method in UIViewController, but it is an important part of the ViewController’s life cycle because it is responsible for loading the view properties in the first place. When you use the Storyboard, it loads out the NIB and attaches it to the View, but when you manually initialize the ViewController, all this method does is create an empty UIView. You can override this method and change its behavior, and add any type of view to the View of the ViewController.
final class MyViewController: UIViewController {
override func loadView() {
let myView = MyView()
myView.delegate = self
view = myView
}
override func viewDidLoad() {
super.viewDidLoad()
print(view) // An instance of MyView}}Copy the code
Note that the view automatically binds itself to the ViewController boundary, so there is no need to set external constraints for myView!
The view now becomes a reference to my custom view (in this case, MyView). You can build all of its functionality inside this view-independent file, and the ViewController has no access to it. That’s great!
To get the contents of MyView, you can cast the View to your own type:
var myView: MyView {
return view as! MyView
}
Copy the code
This may seem a little strange, but that’s because the view will still be defined as a UIView type, not the type you defined for it.
To avoid repeating such code in my ViewController, I like to create a CustomView protocol and define the behavior in it that contains the association type:
The HasCustomView protocol defines a customView property for UIViewController, which is intended to replace the normal view property. To implement this, you must provide a custom View for your UIViewController in the loadView() method. public protocol HasCustomView { associatedtype CustomView: UIView } extension HasCustomViewwhereSelf: UIViewController {/// UIViewController's custom view. public var customView: CustomView { guardlet customView = view as? CustomView else {
fatalError("Expected view to be of type \(CustomView.self) but got \(type(of: view)) instead")}return customView
}
}
Copy the code
Will:
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() // Some MyView methods}}Copy the code
If defining the CustomView alias every time gets a little annoying, you can go a step further and define these behaviors in generic classes:
class CustomViewController<CustomView: UIView>: UIViewController {
var customView: CustomView {
returnview as! CustomView // Because we are rewriting the view, the resolution will never fail. } override funcloadView() {
view = CustomView()
}
}
final class MyViewController: CustomViewController<MyView> {
override func loadView() {
super.loadView()
customView.delegate = self
}
}
Copy the code
Personally, I’m not a big fan of generics because the compiler doesn’t allow extensions of @objc methods that generic classes have, which prevents you from having protocols like UITableViewDataSource in your extension. However, unless you need to do something special (such as setting up a delegate), it will allow you to skip overwriting loadView() and keep the ViewController clean.
conclusion
Overwriting loadView() is a great way to make your view code projects easier to understand and maintain, and I’ve used the HasCustomView method to great effect, especially in recent projects. Writing the view part of the code may not be your choice, but it has many obvious benefits. Give it a try and see if it suits you better.
If you have a better way to define a view that doesn’t require a storyboard, or if you have any questions, comments or feedback, please let me know.
References and recommended reading
LoadView ()
If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.
The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.