Model-View-Controller (MVC) in iOS – A Modern Approach

Learn some tips and tricks to go one up on MVC, Apple’s recommended architecture pattern, and modify your code to be scalable and extensible! By Felipe Laso-Marsetti.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

The View (V)

When users interacts with your app, they are interacting with the view layer. It should not contain any business logic. In code terms, you’ll normally see:

  • UIView subclasses: These range from a basic UIView to complex custom UI controls.
  • Classes that are part of UIKit/AppKit.
  • Core Animation.
  • Core Graphics.

Typical code smells found in this layer manifest in different ways but boil down to include anything unrelated to UI in your view layer. This can include network calls, business logic, manipulating models and so on.

Use the following as a checklist when inspecting your view layer:

  • Does it interact with the model layer?
  • Does it contain any business logic?
  • Does it try to do anything not related to UI?

If you answer “yes” to any of these questions, you have an opportunity to clean up and refactor.

Of course, these rules aren’t written in stone and sometimes you’ll need to bend them. Nonetheless, it’s important to keep them in mind.

Well-written view components are often reusable, but don’t focus on that from the start. It’s easy to get caught up in the thought of taking a cool button you built and making it compatible for a dozen different scenarios that your app may never need.

Consider making components reusable only when you actually need to. When you have multiple use cases, that’s the time to make a component more generic.

From a testing perspective, UI testing in iOS allows you to test things like transitions, UI elements having specific attributes or UI interactions working as intended.

The Controller (C)

The controller layer is the least reusable part of your app as it often involves your domain-specific rules. It should be no surprise that what makes sense in your app won’t always make sense in someone else’s. The controller will then use all the elements in your model layer to define the flow of information in your app.

Usually, you’ll see classes from this layer deciding things like:

  • What should I access first: the persistence or the network?
  • How often should I refresh the app?
  • What should the next screen be and in which circumstances?
  • If the app goes to the background, what should I tidy up?
  • The user tapped on a cell; what should I do next?

Think of the controller layer as the brain, or engine, of the app; it decides what happens next.

You likely won’t do much testing in the controller layer as, for the most part, it’s meant to kick things off, trigger data loads, handle UI interactions, mediate between UI and model, etc.

Putting it all Together

Now that you have a better understanding of the layers involved in the MVC pattern, it’s time to see it in action with this tutorial’s sample app.

Open the tutorial’s sample project and run the app. This app is a WWDC-like app that shows sessions and attendees for the event. The app loads data from JSON files in the app bundle on launch. Loading the data triggers the controllers to update the view and begin waiting for user interaction.

To start, expand the Model folder. The three “pure” model classes are Event, Attendee and Session. Note how these conform to Decodable so parsing from a JSON response is simple.

From there, you have other model files that are part of your network calls. This tutorial’s project loads a file from the app’s bundle, but you can add a remote URL to load a file or call a web service. The main classes are:

  • Network: A wrapper around URLSession
  • NetworkError: To handle different errors during the request/response process
  • WebService: To add a helper method for loading both attendees and sessions

Of note is Network.swift, which has a method that uses generics to request JSON from a URL and parse it into an object type you specify in the method call. It handles HTTP responses and codes, errors, parsing, concurrency and making the URLSession call for you.

These files are small, straightforward, contain their own logic and you can test them with, at most, some mock methods.

Moving on to the view, it’s mostly contained in your storyboard files. This is a good thing as it’s not only more visual, but reducing the lines of code you write means reducing the amount of possible bugs you can introduce.

You may also have view logic in files, as is the case with SessionCell.swift, particularly when you do custom animations or UI elements. Your views should not know about business logic, interact with or know about model objects or do anything that doesn’t pertain to the view itself.

Testing components in the view layer, from a pure unit test perspective, is not so simple or useful, especially when dealing with XIBs or storyboards. This is because programmatically creating views or controllers will not trigger all methods in the view lifecycle or set up non-programmatic constraints. You can do UI testing and ensure that your tests also cover the view layer.

Finally, you get to the controller layer, this is where things get interesting as they act as the glue between view and model. AttendeeViewController and SessionViewController have the same concept. These display a list of items that you can select to view details. When you present the details for a selected item, you use the DetailViewController

Some things to note are:

  • The AppDelegate is in charge of loading the event data and then passing it to each of the controllers.
  • When the controller receives data, it triggers a reload of the table with the data.
  • The controllers act as the data source and delegate of the table view.
  • The controllers handle passing the details information when the user selects a session or event.

It’s all very lean, clean and easy to understand. You could even, for more robust projects, create separate classes or objects to act as data sources or delegates, further simplifying your controllers.

There is no heavy reliance on completion blocks, delegation (outside of what the table view needs) or mixing up business logic in views or controllers. You also don’t have the dreaded “MVC: massive view controllers.” This occurs when too much of your business logic lives inside of the controller.

Some tips to help you notice when an item in a layer may be overstepping its boundaries are:

  • The controller is doing network requests, parsing or persistence.
  • The controller (or any file) has hundreds or thousands of lines of code.
  • Views accept model objects to “configure” themselves or set up UI elements for display.
  • Controllers are doing a lot of business logic, calculations, data wrangling or manipulation.