├── ControllerViewDesign.swift └── README.md /ControllerViewDesign.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Controller View Design Pattern 3 | // 4 | // ControllerViewDesign.swift 5 | // Created by Saoud Rizwan 6/1/17 6 | 7 | import UIKit 8 | 9 | class ControllerView: UIView { 10 | 11 | weak var controller: Controller? 12 | 13 | init(controller: Controller) { 14 | super.init(frame: controller.view.frame) 15 | controller.view.addSubview(self) 16 | self.translatesAutoresizingMaskIntoConstraints = false 17 | self.topAnchor.constraint(equalTo: controller.view.topAnchor).isActive = true 18 | self.bottomAnchor.constraint(equalTo: controller.view.bottomAnchor).isActive = true 19 | self.leftAnchor.constraint(equalTo: controller.view.leftAnchor).isActive = true 20 | self.rightAnchor.constraint(equalTo: controller.view.rightAnchor).isActive = true 21 | self.backgroundColor = UIColor.white 22 | self.controller = controller 23 | addSubviews() 24 | constrainSubviews() 25 | } 26 | 27 | required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } 28 | 29 | func addSubviews() { } 30 | func constrainSubviews() { } 31 | } 32 | 33 | class Controller: UIViewController { 34 | weak var controllerView: ControllerView? { 35 | didSet { 36 | self.setViewHandlers() 37 | } 38 | } 39 | 40 | func setViewHandlers() { } 41 | } 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Controller View Design 3 |

4 | 5 |

6 | Installation 7 | • Getting Started 8 | • Should I use CVD? 9 |

10 | 11 | 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](https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html). 12 | 13 | [Read the Medium article.](https://medium.com/@sdrzn/controller-view-design-pattern-for-swift-new-6283cb052) 14 | 15 | ## Compatibility 16 | 17 | The Controller View Design pattern is to be used primarily with Swift. 18 | 19 | ## Installation 20 | 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. 21 | 22 | ## Getting Started 23 | 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. 24 | 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. 25 | 26 | 1. [Create a ControllerView subclass.](#creating-a-controllerview-subclass) 27 | 28 | 2. [Create a Controller subclass.](#creating-a-controller-subclass) 29 | 30 | ### Creating a ControllerView subclass 31 | *ControllerView* is simply a subclass of a *UIView*, and will act as a 'container view' for all our subviews. 32 | ```swift 33 | class HomeControllerView: ControllerView { 34 | 35 | // MARK: Views 36 | // 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. 37 | 38 | let label: UILabel = { 39 | let label = UILabel() 40 | label.text = "Hello world" 41 | label.isUserInteractionEnabled = true 42 | return label 43 | }() 44 | 45 | // MARK: Layout 46 | 47 | override func addSubviews() { 48 | // Here we will add all our subviews on to self as if we would to self.view in a UIViewController. 49 | addSubview(label) 50 | // Set constraints or frames for all our subviews here as well. 51 | label.frame = CGRect(x: 100, y: 100, width: 300, height: 30) 52 | } 53 | 54 | // MARK: Methods 55 | // We also want to add any interface-changing code here, such as animations. 56 | 57 | func animateLabel() { 58 | UIView.animate(withDuration: 0.4, animations: { 59 | label.frame = CGRect(x: 0, y: 0, width: 300, height: 30) 60 | }, completion: nil) 61 | } 62 | } 63 | ``` 64 | 65 | ### Creating a Controller subclass 66 | *Controller* is simply a subclass of a *UIViewController* that's linked to a *ControllerView* instance. 67 | ```swift 68 | class HomeController: Controller { 69 | 70 | override func viewDidLoad() { 71 | super.viewDidLoad() 72 | // We have to set our controllerView property in viewDidLoad, as this is the best place to initialize and layout subviews. 73 | controllerView = HomeControllerView(controller: self) 74 | } 75 | 76 | // 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. 77 | override func setViewHandlers() { 78 | // To access our views, we first have to downcast our controllerView class property to our custom ControllerView subclass. 79 | guard let controllerView = self.controllerView as? HomeControllerView else { fatalError("Controller view has not been set") } 80 | controllerView.label.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(labelTapped))) 81 | } 82 | 83 | func labelTapped() { 84 | guard let controllerView = self.controllerView as? HomeControllerView else { fatalError("Controller view has not been set") } 85 | controllerView.animateLabel() 86 | } 87 | } 88 | ``` 89 | Now whenever we need to access a subview, we first need to get our *ControllerView* subclass. 90 | ```swift 91 | extension HomeController: UITableViewDataSource { 92 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 93 | guard let controllerView = self.controllerView as? HomeControllerView else { fatalError("Controller view has not been set") } 94 | let cell = controllerView.tableView.dequeueReusableCell(withIdentifier: "id", for: indexPath) 95 | cell.textLabel?.text = data[indexPath.row] 96 | return cell 97 | } 98 | } 99 | ``` 100 | **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. 101 | 102 | **Alternative ways of accessing a ControllerView's views and methods using CVD:** 103 | 104 | Using a computer variable with a custom getter: 105 | 106 | ```swift 107 | class HomeController: Controller { 108 | 109 | var homeControllerView: HomeControllerView { 110 | get { 111 | guard let controllerView = self.controllerView as? HomeControllerView else { fatalError("Controller view has not been set") } 112 | return controllerView 113 | } 114 | } 115 | 116 | override func viewDidLoad() { 117 | super.viewDidLoad() 118 | controllerView = HomeControllerView(controller: self) 119 | } 120 | 121 | override func setViewHandlers() { 122 | homeControllerView.label.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(labelTapped))) 123 | } 124 | 125 | func labelTapped() { 126 | homeControllerView.animateLabel() 127 | } 128 | } 129 | ``` 130 | ... or if you like optional chaining: 131 | ```swift 132 | (controllerView as? HomeControllerView)?.label.text = "Bye world" 133 | (controllerView as? HomeControllerView)?.animateLabel() 134 | ``` 135 | 136 | ## Should I use CVD? 137 | 138 | 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. 139 | 140 | ## Credits 141 | 142 | Icons in header image by [Yummygum](https://yummygum.com/) 143 | --------------------------------------------------------------------------------