I have a template I've been using with Swift for UIViewController
and UIView
subclasses without a .xib
or .storyboard
. I want to know how this stacks up against other people's implementations. One weird thing about it is you have to use contentView
to access the view, which is kind of similar to UITableViewCell
.
Example of a view controller / view that was made using the template:
MyViewController:
import UIKit
class MyViewController: UIViewController {
// MARK: - View
var contentView: MyView {
guard let contentView = self.view as? MyView else {
fatalError("Cannot create content view")
}
return contentView
}
// MARK: - View lifecycle
override func loadView() {
self.view = MyView()
// Relationships between view controller and view
contentView.addingButton.addTarget(self, action: #selector(myButtonTapped), for: .touchUpInside)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
// MARK: Actions
func amyButtonTapped(sender: UIButton) {
// ...
}
// MARK: - Memory manager
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
MyView
import UIKit
class OneWayView: UIView {
// MARK: - Subviews var
let myButton: UIButton = {
let button = UIButton(type: .custom)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle(NSLocalizedString("myButton", comment: ""), for: .normal)
button.setTitleColor(.orange, for: .normal)
button.setTitleColor(.lightGray, for: .highlighted)
return button
}()
// MARK: - View lifecycle
override init(frame: CGRect) {
super.init(frame: frame)
// Setup this view
addSubview(myButton)
myButton.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
myButton.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
1 Answer 1
Grouping all of the view components into a single view can make views easier to reuse, which can be useful in some circumstances.
However, it is not necessary to create a separate view for each view controller, and view controllers often have individual properties for the subview.
While there is nothing technically wrong with your code, it does add an extra burden of having to define a view class for each view controller.
It is also not the "standard" approach, so it might seems out of place to other developers working on the code, which may or may not be a factor to you.
Usually the views are kept as simple as possible, with the individual controls defined on the view controller instead of the view. The view controller becomes the point of reuse instead of the view. View controller composition can be used to create a hierarchy of views.
Most of the code you have shown in the view can be moved into the view controller, which will remove the need to define a class for each view for each view controller.
When in doubt, aim for code that looks the least unusual, or even code that looks boring or obvious.
For example, instead of creating a OneWayView
, create a OneWayViewController
which contains the same logic as the previous view and view controller.
class OneWayViewController: UIViewController {
// Probably better to keep this as a weak property.
let myButton: UIButton = {
let button = UIButton(type: .custom)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle(NSLocalizedString("myButton", comment: ""), for: .normal)
button.setTitleColor(.orange, for: .normal)
button.setTitleColor(.lightGray, for: .highlighted)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
// Add the button to the root view, which is just a plain view.
view.addSubview(myButton)
// Setup constraints.
myButton.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
myButton.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
// Add target actions.
myButton.addTarget(self, action: #selector(myButtonTapped), for: .touchUpInside)
}
}
This new view controller can be composed with other view controllers:
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let viewController = OneWayViewController()
viewController.willMoveToParentViewController(self)
addChildViewController(viewController)
let oneWayView = viewController.view
view.addSubview(oneWayView)
// Add constraints to position the oneWayView...
}
}
On the other hand, if you prefer to stick to your original plans, you could use Swift generics to make the view controller more reusable:
class BaseViewController<T: UIView>: UIViewController {
var contentView: T {
guard let contentView = self.view as? T else {
fatalError("Cannot create content view")
}
return contentView
}
override func loadView() {
self.view = T()
}
}
-
\$\begingroup\$ Thank you, this is great! i didn't think about generics, that could be totally useful! \$\endgroup\$richy– richy2017年04月20日 14:55:43 +00:00Commented Apr 20, 2017 at 14:55