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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.