Skip to content

saoudrizwan/ControllerViewDesign

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

39 Commits
 
 
 
 

Repository files navigation

Controller View Design

InstallationGetting StartedShould I use CVD?

The Control View Design pattern, or CVD, is a new & elegant approach to delegating subviews' initialization, layout, and animation code to a separate class from UIViewControllers. Managing user interfaces programatically can result in massive view controller classes; however with CVD, view-related code is contained in a ControllerView subclass while data model management, user interaction, etc. is handled by a Controller subclass (previously known as a ViewController.) In many ways, CVD ensures you follow proper MVC guidelines recommended by Apple.

Read the Medium article.

Compatibility

The Controller View Design pattern is to be used primarily with Swift.

Installation

CVD is a design pattern and not a framework, but there is some boilerplate code (~30 lines) required to get started. The required boilerplate code is included in ControllerViewDesign.swift, which you can add anywhere in your project. You can also drag and drop ControllerViewDesign.swift into your project.

Getting Started

Before, we would create a UIViewController subclass, where we initialize subviews, lay them out in our viewDidLoad() method, and then add any animation functions if we needed them. However with CVD, we put all that view-related code in a ControllerView subclass and set view handlers (like targets, gesture recognizers, delegates, data sources, etc.) & call any animation functions in a Controller subclass.

  1. Create a ControllerView subclass.

  2. Create a Controller subclass.

Creating a ControllerView subclass

ControllerView is simply a subclass of a UIView, and will act as a 'container view' for all our subviews.

class HomeControllerView: ControllerView {
    
    // MARK: Views
    // This is where you want to declare all your subviews' instances. Creating custom views as computed objects is much faster and easier than creating custom subclasses.
    
    let label: UILabel = {
        let label = UILabel()
        label.text = "Hello world"
        label.isUserInteractionEnabled = true
        return label
    }()
    
    // MARK: Layout
    
    override func addSubviews() {
        // Here we will add all our subviews on to self as if we would to self.view in a UIViewController.
        addSubview(label)
        // Set constraints or frames for all our subviews here as well.
        label.frame = CGRect(x: 100, y: 100, width: 300, height: 30)
    }
    
    // MARK: Methods
    // We also want to add any interface-changing code here, such as animations.
    
    func animateLabel() {
        UIView.animate(withDuration: 0.4, animations: { 
            label.frame = CGRect(x: 0, y: 0, width: 300, height: 30)
        }, completion: nil)
    }
}

Creating a Controller subclass

Controller is simply a subclass of a UIViewController that's linked to a ControllerView instance.

class HomeController: Controller {

    override func viewDidLoad() {
        super.viewDidLoad()
        // We have to set our controllerView property in viewDidLoad, as this is the best place to initialize and layout subviews.
        controllerView = HomeControllerView(controller: self)
    }
    
    // Use this function to set up any targets, gesture recognizers, delegates, data sources, etc. for our subviews. Our ControllerView subclass automatically calls this function for us in the background at the proper time.
    override func setViewHandlers() {
        // To access our views, we first have to downcast our controllerView class property to our custom ControllerView subclass.
        guard let controllerView = self.controllerView as? HomeControllerView else { fatalError("Controller view has not been set") }
        controllerView.label.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(labelTapped)))
    }
    
    func labelTapped() {
        guard let controllerView = self.controllerView as? HomeControllerView else { fatalError("Controller view has not been set") }
        controllerView.animateLabel()
    }
}

Now whenever we need to access a subview, we first need to get our ControllerView subclass.

extension HomeController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let controllerView = self.controllerView as? HomeControllerView else { fatalError("Controller view has not been set") }
        let cell = controllerView.tableView.dequeueReusableCell(withIdentifier: "id", for: indexPath)
        cell.textLabel?.text = data[indexPath.row]
        return cell
    }
}

NOTE: Don't let the fatalError() bit scare you. That's simply there to throw an error if you forget to set the controllerView property in the viewDidLoad() function.

Alternative ways of accessing a ControllerView's views and methods using CVD:

Using a computer variable with a custom getter:

class HomeController: Controller {
    
    var homeControllerView: HomeControllerView {
        get {
            guard let controllerView = self.controllerView as? HomeControllerView else { fatalError("Controller view has not been set") }
            return controllerView
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        controllerView = HomeControllerView(controller: self)
    }
    
    override func setViewHandlers() {
        homeControllerView.label.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(labelTapped)))
    }
    
    func labelTapped() {
        homeControllerView.animateLabel()
    }
}

... or if you like optional chaining:

(controllerView as? HomeControllerView)?.label.text = "Bye world"
(controllerView as? HomeControllerView)?.animateLabel()

Should I use CVD?

If you create, layout, animate, and manage your app's subviews programatically, then CVD is a clean & easy approach to ensuring you don't end up with massive view controllers. CVD is a design pattern you can use alongside other design patterns like MVC or MVVM, so it isn't a complete replacement for them. In fact, it even helps ensure you follow proper MVC guidelines if that's what you're using in your project. However, if you're using only storyboards for your particular project, then CVD may not be the best solution for managing all your IBOutlets.

Credits

Icons in header image by Yummygum

About

This new design pattern focuses on separating view-related code from controller classes.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages