MVC is not to blame for your Massive View Controllers

When you start building apps with UIKit, MVC is the first pattern you learn. And with good reason: UIKit is, quite literally, built to function with MVC. The pattern does a lot of the heavy lifting early on, but as your app grows, your controllers tend to grow as well. Often, they grow so large that they become brittle and difficult to work with.

Ah, the massive view controller. A problem as old as time itself ✨

I’ve seen more than a few articles that outline architectures that attempt to solve this all-too-common issue. But they all have one thing in common that I find rather curious: they all condemn MVC as the root cause of Massive View Controller.

Is that really the case? Does MVC lead to massive view controllers? Let’s dig into that a bit.

Pattern vs Architecture

We often talk about our apps as being “an MVC app”, but what does that really mean? Implicitly, we’re saying that the architecture of our app is driven by MVC. But should that be the case? Is MVC really an “architecture”?

To me, an architecture is an overarching story for every component in your application. It gives every piece a home and assigns different roles to the various objects we create. A good architecture should be able to answer the question of “I want to add X, how can I make it fit?”

Does MVC really fit that description? Does it have anything to say about your network layer, for example?

I don’t think so. Instead, I like to think of MVC simply as a design pattern.

A design pattern is a structured way to solve a common problem. We take a generic concern and figure out a reusable way to address it. Some common problems and patterns are:

  • Communicating changes to interested listeners is solved by the observer pattern
  • Changing the behaviour of an object without changing its interface is solved by the strategy pattern
  • Displaying and interacting with business objects using a GUI is solved by MVC, MVP or MVVM

So is it fair to say MVC is an architecture? I don’t think so. “MVC apps” become difficult to work with because we don’t have a clear way to handle concerns outside of the Models, Views and Controllers. In other words, we simply haven’t thought through what are architecture should be.

Not everything in your app needs to fit in M, V or C

“So is my network client a model or a controller?”

Have you ever wondered something similar? I certainly have.

With MVC, you’re inevitably going to end up with objects that don’t quite slot into the traditional definition of Model, View or Controller.

And that’s totally fine.

The goal of the MVC pattern isn’t to categorize the immense variety of possible objects and classes present in an application. Its goal is only to classify the actors responsible for getting your model onto the screen. That’s it.

Asking yourself where your network client fits into MVC is like asking yourself what the color blue tastes like. The question has no answer because the question itself flawed.

One way to solve your massive view controller problem is to break up your controller and its views into smaller controllers and views. Let’s explore that now.

How to break up massive view controllers

One of the beautiful things about MVC in UIKit is how easy it is to embed view controllers inside each other.

MVC becomes far more interesting when we let go of the fact that 1 controller equals 1 screen of content. Instead of having a single giant controller and a single giant view, we can break them up into smaller chunks that are easier to reason about.

Then, using view controller containment, you can create a tree of view controllers that all communicate with each other.

From one MVC to many MVCs

The code for this is pretty simple too. Here’s a snippet:

func embedChildViewController(_ childVC: UIViewController) {
    addChild(childVC)
    self.view.addSubview(childVC.view)
    //Layout code
    childVC.didMove(toParent: self)
}

You can call this snippet from viewDidLoad (or even better, inside loadView) and you’re good to go.

This works well, but you’ll probably be calling this a lot, so we’ll probably want to add it to an extension. Instead of hardcoding the layout code in the method, we can add a closure to keep it flexible.

extension UIViewConroller {
    func embedChildViewController(_ childVC: UIViewController, 
                                  layout: () -> Void) {
        addChild(childVC)
        self.view.addSubview(childVC.view)
        layout()
        childVC.didMove(toParent: self)
    }
}

Now, from viewDidLoad, you can call something like this:

override func viewDidLoad() {
    super.viewDidLoad()

    let childVC = MyChildViewController()
    embedChildViewController(childVC: childVC) {
        childVC.view.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            childVC.view.topAnchor.constraint(equalTo: self.view.topAnchor),
            childVC.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
            childVC.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
            childVC.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
            ])
    }
}

By using view controller hierarchies, you can refactor big view controllers into smaller ones. In doing so, you’ll often find a better place for your state to live as well.

M-V-Takeaways

MVC is not an architecture, it’s a pattern. This means that it’s your job to build your own architecture around MVC.

Despite this, the MVC can still feel like a pain to use due to massive view controllers. To fix this, break up your large controller into smaller components. This will create a hierarchy of controllers.

Child view controllers can communicate with their direct parents using the delegate pattern. If children need to speak view controllers higher up the chain, they can rely on the responder chain to send messages to the closest controller that can handle it.

Now it’s your turn! How can you use view controller containment to break up your own massive view controllers ?