View Controller Containment and Delegation Pattern
Super Massive View Controller
Recently I used the delegation pattern to simplify the implementation of a very complex View Controller in a MVC application based on UIKit. This View Controller had to deal with different types of information in a single screen such as: a list of contents (a compilation of mp3 files), selected and selectable filters, a system to express votes and comments. Obviously, it would implement all the features to interact with these components (access the details of a content, filter the list, vote for the compilation, add a new comment or reply to existing ones).
Android helps developers by making available the concept of Fragments which by definition represent behaviors or portions of user interface that can be combined together in a single activity to build a multi-pane UI and reuse in multiple activities. A fragment does exactly what I needed. It could be combined with other fragments, it could be reused etc. But UIKIt does not provide fragments. I should manage it all through a view controller.
Now, let’s imagine the complexity of a View Controller that should implement all the logic necessary to manage all these features. For sure:
- That class, that inherits from UIViewController, would become very large (uh? A Massive View Controller! Bad idea);
- I would not be able to reuse any of the components that I would implement in a different scene;
- It would violate the Single Responsibility Principle (SRP);
- I would face many complications during the maintenance and evolution phase;
- If I had changed my mind and decided to remove one of these components, I would have had to dismantle part of the implementation with the risk of breaking something else.
Could we use something similar to Fragments?
Yes, obviously we do not have fragments, but we can use multiple specialized view controllers embedded in a view controller that work as a container.
We must consider two concepts: View Controller Containment and Child View Controller.
- View Controller Containment: is the ability to embed one view controller inside another;
- Child View Controller: from the concept above follows that a view controller can become the child of another view controller. When added as a child, the view controller’s view can be automatically resized and / or positioned using Auto Layout constraints.
With this in mind, we can implement multiple, simple and specialized View Controllers to be used as building blocks. We can implement the UI of our application by assembling these blocks as if we were playing with Lego having great benefits such as:
- Smaller, heavy specialized view controller implementations;
- Ease of reuse of components;
- Ease of maintenance and evolution;
- Simpler organization of our code.
So, in my case, instead of a single Super Massive View Controller (with a related single complex scene inside a storyboard or in a xib file), that implements all the features described above, I can have:
- A ContainerViewController that makes the setup of the layout of the screen by adding some child view controllers;
- A ContentsViewController that displays a list of contents and responds to user selection if he taps on an item;
- A FilterContentsViewController that implements a list of available / selected filters and implements selection / deselection logic;
- A VoteViewController that displays and implements the voting system logic;
- CommentsViewController that renders comments and implements all the required logic to let the user write a new comment or reply to an existing one;
- A storyboard with multiple simpler scenes.
How does it work?
Clear, seems a good idea. How all these assembled “components” work together ? How do you manage, for example that the selection made on the view controller that implements the filter involves an update of the list of contents that is on a different controller?
With the delegation pattern.
What is it? What purpose does it serve? Why, as an iOS developer, should I know it well? Is it so complicated?
What is it? What purpose does it serve?
A very simple but effective definition can be this:
The Delegation Pattern is a pattern (an approach) that easily allows a class (or an object, if you prefer) to communicate with another class (or another object, eventually sending data).
I think that a definition put in this way is easy to understand, especially because all of us, sooner or later, had the need to transmit information from a View Controller A to a View Controller B and to receive feedback or data back.
With this simple and short definition in mind we have the answers for the first two questions above.
Why, as an iOS developer, should I know it well?
The Delegation Pattern is the basis for the functioning of most of the frameworks that Apple makes available to us to write our Apps.
It is also the basis of numerous architectures such as VIPER, where multiple components such as Views, Interactors, Presenters, Routers (or Wireframes) interact with each other on various levels to design and operate a section of the app on the screen.
Now, we have the answer for the third question.
Is it so complicated?
Not at all. It is simple and elegant. Now we have the fourth and last answer.
It all starts with a protocol
We have to understand the roles of protocols and delegates.
A protocol is a blueprint, a sort of declaration that you write as a list of methods and properties that suit a particular task.
It’s very simple, here we have a protocol called ComplexTaskDelegate that defines the signature of three functions that have to be executed when a specific task is executed:
The signatures above are very simple, the third has a kind of prefix @objc optional that we will discuss later.
Then we need a class that implements tasks
Now think to a class, whatever it is, that implements the logic required to do the three tasks (maybe long running tasks). It is a heavy specialized class that implements some public and private functions. Imagine it as a black box. We are not interested in the details, we don’t want to deal with all the complexity of the logic inside. We only know that if we want to use that class to perform some tasks we have to call one or more functions eventually passing few parameters. When that blackbox completes the required tasks we want to be notified because we need to update the content of the screen to communicate something to the user.
To behave as described the blackbox would have a property defined as:
Functions inside the blackbox that implement all the logic that could be requested by the App should be implemented as:
Note: the blackbox does not implement the logic of the functions declared in the protocol. It simply ask this object called delegate to execute them.
Finally we need a class that should be notified when the job is done
The view controller that requested the execution of the tasks implemented by the blackbox must conform to the protocol called ComplexTaskDelegate in order to be notified when the tasks are executed.
What does it mean? Again, it’s easy. It must implement the functions that are described in that protocol. Consider this simply view controller class:
Look at the declaration of the class:
We are declaring the name of the class, its type (or the class it inherits from) and finally we are saying that it must conform to our protocol called ComplexTaskDelegate. I you read the code in the class above you see that the view controller implements the three functions declared by the protocol.
MyViewController has also a property called blackBox that represents our Black box, the class that implements all the mysterious and unknown logic.
In the function viewDidLoad there’s the most important piece of code:
please blackBox, sooner or later you will be asked to do something. Whatever you do, consider me to do things for you. I am the delegate, the one who should implement the functions you will request when you deem it necessary.
Let’s examine again one of the function that we have defined in the BlackBox class:
Before exiting, secondComplexTask executes an unspecified function called secondTaskHasBeenCompleted passing some data as input parameters. Who implements the code contained in that function? Who is this guy called delegate?
Back to the MyViewController class we can see this in the viewDidLoad function:
That’s the answer, self the current instance of MyViewController class.
This single line of code is so important that if you forget to write it, when blackBox completes a task, the view controller is not notified.
MyViewController must implement all the functions declared in the protocol. If it does not, XCode prevents the compilation and reports a specific error message. There is an exception, the third method is defined by the protocol as optional through two magic words @objc optional. So MyViewController is not obliged to implement it.
Implementation of a complex UI using View Controller Containment and Delegation Pattern
Now, it’s time to go deeper to the main topic of this article.
In my storyboard I will create the scene for the ContainerViewController, as described earlier it will setup the layout of my complex UI.
I add to the scene four views:
- Will host the FilterContentsViewController,
- Will embed the ContentsViewController
- Will embed the VoteViewController;
- Will host the CommentsViewController
Let’s go on, I create a very simple scene to host the features for the ContentsViewController that displays a list of contents. It has a tableview with a custom cell containing labels for title, artist, genre and a rating:
I create a scene for FilterContentsViewController that implements a list of available filters, in this sample I used 3 buttons.
Now is the turn of the VoteViewController that displays and implements the voting system. Again I used 5 buttons.
For the the scene for CommentsViewController that renders comments I used a UITableView with a custom UITableViewCell.
I don’t want to go deeper on the details on how to implement a table with sample data in a view controller. I don’t even want to dwell on how to create and connect a button on the screen to an outlet. Instead, I want to go directly to the point concerning the encapsulation of the 4 view controllers inside ContainerViewController.
Let’s see what I have done. Again it is very easy to understand. With Interface Builder I created and connected 4 outlets for the views that we discussed earlier.
Then, I created 4 properties, each intended to represent a specific view controller.
Finally I created a function called setupChildViewControllers executed from viewDidLoad that instantiate each view controller and add it to the view on ContainerViewController.
To do this I used the following extension.
At this point the Job is done. If you run the application you will see ContainerViewController on the screen that embeds the 4 specialized viewController.
Now It’s time to make all these view controllers communicate with the container controller to notify user actions, for example “the user did select a filter”, or ”the user did add a comment”.
So, we have already said that communication between view controllers is done with delegation pattern and the definition of a protocol is required.
I will define the protocol that FilterContentsViewController will use to notify ContentsViewController that the user made a selection. We can save it everywhere we want, eventually in a specific file called FilterContentsDelegate.swift
As you can see, I simply declared a function with a single input parameter of type String. My idea is to make sure that when the user presses a button on the FilterContentsViewController, this transmits to ContainerViewController a string containing information on the selected filter. When the container receives this data, it performs a special function that will apply a filter to the contents showing some hiding others.
Again, see this very important piece of code:
Every time the user will press a button in the filter section, a function called userDidSelectFilterwith an input argument will be executed. But FilterContentsViewController does know nothing about the implementation of this function.
Let’see how we have to update our ContainerViewController class.
- First, look that in the second row where we declare the class name, we added a comma and the name of our protocol. This means that this controller must implement all the mandatory functions declared in the protocol.
- The protocol contains the declaration of one function. Its implementation is at the end of the class above. Here I simply display an alert view with a message that contains the data received from the filter view controller.
- Finally, one of the most important things. Look at the setupChildViewController, at the end of the code that configures the filterListVC property. I added
Often we are dealing with the need to create complex UIs in which numerous and complex responsibilities are assigned to individual screens.
To avoid having to create massive view controllers, to guarantee us a simple and well organized, reusable, easy to maintain and to evolve code, to avoid violating the Single Responsibility Principle (SRP) which should always be a reference light in the design of our classes, we can take advantage of the concept of View Controller Containment and the ability for a view controller to become the child of another view controller. Delegation pattern could be used to make different encapsulated view controllers to communicate with each others or with model objects, managers etc. to provide notification of user input and actions.
You can find the complete article on Medium here.