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.