1
\$\begingroup\$

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")
 }
}
asked Apr 17, 2017 at 14:05
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

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()
 }
}
answered Apr 17, 2017 at 19:37
\$\endgroup\$
1
  • \$\begingroup\$ Thank you, this is great! i didn't think about generics, that could be totally useful! \$\endgroup\$ Commented Apr 20, 2017 at 14:55

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.