Mastering MVC: Properly Cooking the Controller in iOS

Written by ze8c | Published 2023/06/07
Tech Story Tags: ios | swift | mvc | programming | apple | coding | software-development | software-engineering

TLDRMany people are familiar with MVC (Model-View-Controller) and its challenges, yet only a few discuss how to address them effectively. In this article, we will delve into the vital components of MVC: the View and the Controller.via the TL;DR App

Many people are familiar with MVC (Model-View-Controller) and its challenges, yet only a few discuss how to address them effectively. In this article, we will delve into the vital components of MVC: the View and the Controller.

Typically, a UIViewController is employed as both a View and a Controller, resulting in increased code within the UIViewController. So, let's explore the possibility of separating the UIViewController into distinct View and Controller entities.


Let's begin by examining the lifecycle of aUIViewController.

One of the methods in this lifecycle is loadView. According to Apple's documentation:

“You should never call this method directly. The view controller calls this method when its viewproperty is requested but is currently nil. This method loads or creates a view and assigns it to the view property.”

This insight allows us to segregate the View and Controller by utilizing the loadView method. First, we will define a protocol that encapsulates the functionality of our future View.

protocol AbstractTestView: UIView {
    func bind(_ model: String)
}

Now, let's proceed to implement the protocol for our View.

final class TestView: UIView, AbstractTestView {
    
    private let content: UILabel = {
        $0.numberOfLines = 0
        $0.textColor = .red
        return $0
    }(UILabel())
    
    init() {
        super.init(frame: .zero)
        layout()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func bind(_ model: String) {
        content.text = model
    }
    
    private func layout() {
        NSLayoutConstraint.activate([
            content.centerXAnchor.constraint(equalTo: centerXAnchor),
            content.centerYAnchor.constraint(equalTo: centerYAnchor)
        ])
    }
}

Finally, we can implement our Controller, which will communicate with the View through the protocol.

final class TestVC: UIViewController {
    private let mainView: AbstractTestView
    
    init(view: AbstractTestView) {
        mainView = view
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func loadView() {
        view = mainView
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        mainView.bind("Hello from MVC!")
    }
}

For more intricate elements like tables, the business logic will remain within our Controller. Here's an example:

extension TestVC: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
    }
}

Now, let's connect our Controller with the View.

let vc = TestVC(view: TestView())

It is crucial to maintain a clear separation between business logic and the view. Continuously ask yourself: "Does this piece of code handle the display of elements or work with data?" Initially, this may pose a challenge, but with time, it will become second nature.


Published by HackerNoon on 2023/06/07