View Controller Transitions

One of the first things developers learn when beginning iOS development is how to transition between the various scenes of the application and how to pass data between view controllers.1

The typical approach to this is to drag a few UIViewControllers on your storyboard (if you are using storyboards), connect them with segues, give the segues identifier names and when an event that asks for a transition occurs, make the appropriate preparations inside the prepareForSegue: method.

This approach is fine and works great for small-sized apps but when an app uses more than 2-4 scenes, then a much cleaner solution is needed to help with the maintenance and extension of the code base. 2

For our example, we will be using three view controllers in a horizontal navigation controller hierarchy: ViewControllerA, ViewControllerB and ViewControllerC.3 View Controller Transitions

Each view controller has a button for transitioning to the next view controller and an object that needs to be passed from one controller to the next. This usually leads to the following lines of code:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([[segue identifier] isEqualToString:@"SEGUE_NAME_AS_IN_STORYBOARD"])
    {
        // Get reference to the destination view controller
        ViewControllerB *vc = [segue destinationViewController];
    }
}

 

The Problem

Every view controller “knows” about the view controllers it’s connected to.

In the above example, ViewControllerA holds a reference to ViewControllerB. How is this a problem you ask?

Well, it’s not …. until you find out that the destination view controller is messy and you need to replace it with a new one or you want to try a collection view controller instead of a table view controller to see if it fits your use case better or the CEO doesn’t like the name of the view controller (and of course she has a say in naming conventions) and you need to change it to SuperMegaCorpViewController ThatDoesGreatStuff AndIsABeautyToLookAt or …. or …. or

To do that, you would have to edit the source view controller rather heavily; @import statements need to be deleted/added, the prepareForSegue: method too. You would also need to update your storyboard(s) obviously. And this would have to happen every time a change is required. It’s a tedious and time-consuming process.

This kind of change is exactly what we want to avoid when developing software. It’s a direct violation of the Open Closed Principle. Ideally we want our code to be open for extension but closed for modification.4

And it gets worse if you throw dependency injection management into the mix.

 

The Solution

The above problem can be solved by introducing an intermediate class that handles the whole process of transitioning between view controllers, and can be implemented using the Mediator Design Pattern.

According to the Gang Of Four book, the goal of the Mediator Pattern is to “Define an object that encapsulates how a set of objects interact”. The participant classes in the context of the Mediator Pattern are the mediator class and the classes (in our case the view controller classes) that need to communicate.5 View Controller Transitions with the Mediator Pattern So how does the pattern solve the problem discussed above?

The first obvious change is that the view controllers don’t keep references to each other anymore. If any of the three view controllers has to be replaced/refactored or if we add a new controller, there is no longer a need to also touch any of the other view controller classes.

When using the typical approach (the “before” diagram above), the situation can get messy fast. With the Mediator Pattern, the only class that needs to be modified is the Mediator class. This way we have encapsulated the interaction of the view controllers in a single class that has this function as its sole responsibility.

We can now extend our codebase without having to dig in the existing code of the controllers.

For more fine-grained control and to avoid ending up with a huge and hard to maintain Mediator class, check this article that combines the Mediator and State Patterns to delegate the transition handling to State objects. The code used to implement the design in this article can be found at github.

Further Reading

Books:

Design Patterns: Elements of Reusable Object-Oriented Software (The Gang Of Four Book)

Head First Design Patterns (In my experience, the best introductory book on Design Patterns)

Pro Objective-C Design Patterns for iOS

Agile Principles, Patterns, and Practices in C# (Uncle Bob’s seminal book, a must read)

Articles:

Lighter View Controllers (Give your view controllers some space to breathe)

Single Responsibility Principle (The principle defined by the godfather of the term)


  1. This problem is of course hardly unique to iOS. Any application with a GUI faces this situation. The methodology used in this article can be applied to most technologies/languages.

  2. We will be using a simple 3 screen example here, but the methodology described in this article can be applied to both simple and complex (many scenes deep) hierarchies. For more complex cases, take a look at this example that goes one step further and uses the State Design Pattern to avoid cluttering the Mediator class.

  3. I won’t go through the setup and wiring-up process of the view controllers since if you are reading this, you have probably done this a few thousand times already. If not, check this guide.

  4. For more info on SOLID principles, make sure to read Agile Principles, Patterns, and Practices in C#. Don’t be afraid of that C# there. This book is a must read for anyone involved in software development.

  5. It’s usually advisable to introduce an extra layer of abstraction by using a protocol/interface/abstract class that defines the behavior of the Mediator concrete class, but this is not crucially important in this use case, especially if the State Design Pattern is also used to encapsulate the interactions of every scene (trimming the Mediator’s interface to just one one-line method) into separate state objects. Over – design is sometimes worse than no design at all. As always, decisions should be made based on the domain in discussion.