What is MVC in iOS? - is one of the first questions that an interviewer might ask a potential iOS developer. MVC is one of the easiest concepts to comprehend. Hence, I will not be introducing here. Instead, I'll be touching on what MVC really is in practice.

MVC in reality

Although an easy concept to grasp, a lot of developers seem to not get MVC right in practice. I have been an iOS consultant for three years and have been given opportunities to work on a variety of projects with approximately two dozen developers from around the world. I've also had the privilege to experience and see how other developers build iOS applications. Sadly, almost 90% of the projects made the similar mistakes of mixing views with controllers, which would result in truly massive view controllers. No jokes here! This mistake will most likely cost applications to have shorter lifecycles.

Why does Massive View Controller exist?

It is definitely not difficult to start developing iOS applications. One of the most intelligent IDEs, XCode, will handle almost every computer science philosophy for developers behind the scene. That's also what Apple tends to deliver: everyone can become an iOS application developer. Obviously, people do not necessarily need knowledge in computer science to become iOS developers. It is without a doubt that other developers will be more forgiving towards those without a background in computer science. "They can always learn." It is absolutely right.

The tutorial is another critical cause. Due to the booming mobile application market, there are tons of tutorials out in the internet to help people build certain functionalities, this is awesome and I appreciate it A LOT! However, tutorial writers might think it's JUST for demonstration purposes and thus, see no need to stick with a certain software architecture pattern. I have observed that majority of the demo codes is on the controller part. But, some developers just follow the tutorial blindly. What comes next then? Welcome to the hell of the massive-view-controller world.

List of MVC in bad practice

  • Model describes data structure without ownership of the data
  • Controller handles the part of model and view
  • View holds nothing; worse if not being separated into a file

Codes comparison

I will be demonstrating the above three points from a demo application. At the same time, another demo application with same functionalities will show how it's supposed to be in MVC software architecture pattern.

Basically, it's a simple iOS application that owns a table view. Each record represents a remote user. The table view can be used to add or delete users. When you click on one of a record, it will navigate you to another view controller that contains a clickable button and a view. You can tap on/off the button to change the state of this record. Let's rolling in!

Model describes data structure without ownership of the data

// In Contacts.swift
class Contact {
    var name: String?
    var url: String?
    var connected: Bool
    init(name: String, url: String) {
        self.name = name
        self.url = url
        self.connected = false
    }
}

The codes above correctly describe the data structure of Contact, but what should I do if I want to add, retrieve and remove multiple Contact from Contacts? This piece normally goes to the controllers in tutorials which violate our rule. Below is my modification:

// In Contacts.swift
class Contact {
    private(set) var name: String?
    private(set) var url: String?
    private(set) var connected: Bool
    
    init(name: String, url: String) {
        self.name = name
        self.url = url
        self.connected = false
    }
    
    func reverseConnected() {
        connected = !connected
    }
}

class Contacts {
    private static let sharedInstance = Contacts()
    private var contacts = [Contact]()
    
    class func countContact() -> Int {
        return sharedInstance.contacts.count
    }
    
    class func addContact(contact: Contact) {
        sharedInstance.contacts.append(contact)
    }
    
    class func getContactFromIndexPath(indexPath: NSIndexPath) -> Contact? {
        if 0 > indexPath.row || indexPath.row > sharedInstance.contacts.count {return nil}
        return sharedInstance.contacts[indexPath.row]
    }
    
    class func removeContactFromIndexPath(indexPath: NSIndexPath) -> Contact? {
        if 0 > indexPath.row || indexPath.row > sharedInstance.contacts.count {return nil}
        let contact = sharedInstance.contacts[indexPath.row]
        sharedInstance.contacts.removeAtIndex(indexPath.row)
        return contact
    }
}

CountContact, addContact, getContactFromIndexPath and removeContactFromIndexPath can be called directly from controllers. This way, controllers do not need to know anything about the model.

Controller handles the part of model and view

// PublishViewController.swift
var contact: Contact? // it's okay to cache a local variable
@IBOutlet weak var mineView: UIView!
@IBOutlet weak var connectButton: UIButton!

// style logic is held by vc
// which we don't recommend here
private func updateConnectButton() {
    if let contact = contact {
        if contact.connected {
            connectButton.setTitle("DISCONNECT WITH \(contact.name!)", forState: .Normal)
            connectButton.setTitleColor(UIColor.redColor(), forState: .Normal)
            mineView.layer.borderColor = UIColor.redColor().CGColor
        }
        else {
            connectButton.setTitle("CONNECT WITH \(contact.name!)", forState: .Normal)
            connectButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
            mineView.layer.borderColor = UIColor.clearColor().CGColor
        }
    }
}
// In MainViewControllerViewController.swift
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("MainViewTableViewCellIdentifier")!
    let label = cell.viewWithTag(1000) as! UILabel
    label.textColor = UIColor.grayColor()
    if 0 > indexPath.row || indexPath.row > contacts.count {
        return cell
    }
        
    // contacts is data structure held by the vc
    // which don't recommend here
    if let name = contacts[indexPath.row].name {
        label.text = name
    }
    else {
        label.text = "Unknown"
    }
    return cell
}

The code above looks fine and it's working. What if you have more than ten UI elements to configure in a table view cell? Perhaps you have different kinds of cells? Or perhaps, you will need to change view appearance under some situations? If so, the controller will generate a large piece of codes for sure. How about doing it this way:

// PublishView.swift
// this class view property of PublishViewController
@IBOutlet weak var mineView: UIView!
@IBOutlet weak var connectButton: UIButton!
private func updateConnectButton(contact: Contact) {
    if let contact = contact {
        if contact.connected {
            connectButton.setTitle("DISCONNECT WITH \(contact.name!)", forState: .Normal)
            connectButton.setTitleColor(UIColor.redColor(), forState: .Normal)
            mineView.layer.borderColor = UIColor.redColor().CGColor
        }
        else {
            connectButton.setTitle("CONNECT WITH \(contact.name!)", forState: .Normal)
            connectButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
            mineView.layer.borderColor = UIColor.clearColor().CGColor
        }
    }
}
// In MainViewControllerViewController.swift
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier(MainViewTableViewCellIdentifier.Normal.rawValue) as! MainViewTableViewCell
    let contact = Contacts.getContactFromIndexPath(indexPath)
    cell.updateCell(contact) // MainViewTableCell holds style logic
    return cell
}

When codes get changed, you need only to concentrate on getContactFromIndexPath, updateCell. As for PublishView, we put the styling logic in the view part so we can focus on this piece of codes when the design is changed.

View holds nothing; worse if not being separated into a file

Instead, I will create another file called MainViewTableViewCell.swift to hold styling logic. It's all about the SEPARATION. Here's something we should put it in regarding the cell:

// MainViewTableViewCell.swift
class MainViewTableViewCell: UITableViewCell {
    @IBOutlet weak var titleLabel: UILabel!
    override func awakeFromNib() {
        super.awakeFromNib()
        titleLabel.textColor = UIColor.grayColor()
    }
    func updateCell(contact: Contact?) {
        if let contact = contact,
            let name = contact.name {
            titleLabel?.text = name
        }
        else {
            titleLabel?.text = "Unknown"
        }
    }
}

You make it

The projects are available to download from the links below so you have a general idea of the differences:
MVC-Demo
MVC-Demo-Finish

That's it. In short, these are two main points I am suggesting developers to achieve:

  • Clean responsibility of each part and better separation
  • Ease of maintainability and testability
  • Think before you write and Cheers!

Thank you for taking the time to read this! Please let me know your thoughts on this article. Press the like or share buttons if you like it so that others can read it too :)