iOS

Avoiding Massive View Controller using Containment & Child View Controller


View Controller is the component that provides basic building block that we use as a foundation to build application in iOS development. In Apple MVC world, it sits as a middle man between the View and Model, acting as an orchestrator between both of them. It starts with the controller acting as an observer that reacts to Model changes, updates the View, accepts user interaction from the view using the Target Action, and then updates the Model.


Courtesy of Apple Inc.

As an iOS developer, there are many times that we will face an issue of handling Massive View Controller, even if we use architectures like MVVM, MVP, or VIPER. Sometimes, the View Controller has too many responsibilities to handle in one single screen. It’s violating the SRP (Single Responsibility Principle), creating tight coupling between modules, and making it hard to reuse and test each of the components.

We can take the app screenshot below as an example. You can see there are at least 3 responsibilities in one single screen:

  1. Display a list of movies;
  2. Display a filter list that can be selected to be applied for the list of movies;
  3. Clear selection of the selected filters.

If we are going to build this screen using Single View Controller, it is guaranteed that the view controller will become very massive and bloated as it handles too many responsibilities in one single view controller.

How can we solve this problem? One of the solution is using View Controller Containment and Child View Controller. Here are the benefits of using this solution:

  1. Encapsulate the listing of movies into MovieListViewController that has single responsibility to just display list of movies and reacts to changes in Movie Model. We can also reuse this MovieListViewController in another screen if we want to just display list of movies without filter.
  2. Encapsulate the listing and selection of filters logic into FilterListViewController that has single responsibility to just display and handles the selection of filters. We can use delegation to communicate with the parent View Controller when user select and deselect filters.
  3. Slimming down the main View Controller into one ContainerViewController that just has the responsibility to apply the selected filters from the Filter List to the Movie model in MovieListViewController. It also sets up the layout and adds the child view controllers using the container views.

You can view the complete project source code here in the GitHub Repository below.

Composition of the View Controllers using Storyboard

According to the above storyboard, here are the View Controllers that we use to build our Filter screen:

  1. ContainerViewController: The Containment View Controller provides 2 container views to embed the Child View Controller inside a horizontal UIStackView. It provides a single UIButton to clear the selected filters as well. It also embedded in a UINavigationController that acts as the initial View Controller.
  2. FilterListMovieController: The View Controller that is the subclass of the UITableViewController with Grouped style and one prototype standard Cell to display the name of the filter. It also has its Storyboard ID assigned so it can be instantiated from the ContainerViewController programatically.
  3. MovieListViewController: The View Controller that is the subclass of the UITableViewController with Plain style and one prototype subtitle Cell to display the attributes of the Movie. It also has its Storyboard ID assigned like the FilterListViewController.

The Movie List View Controller

This view controller has the responsibility to display the list of Movie model that is exposed as an instance property. We are using Swift didSet property observer to react to changes in model, and then reload the UITableView. The cell displays the title, duration, rating, and genre for the Movie using default subtitle UITableViewCellStyle.

The Filter List View Controller

The Filter List displays the MovieFilter enum in 3 separate sections: genre, rating, and duration. The MovieFilter enum itself conforms to Hashable protocol so it can be stored inside a Set uniquely using the hash value of each enum and its properties. The selections of the filters are stored under an instance property with Set containing the MovieFilter.

To communicate with other object, a delegate pattern is used using the FilterListControllerDelegate. There are 3 methods for the delegate to implement:

  1. Selection of a filter.
  2. Deselection of a filter.
  3. Clear all selected filters.

Integrating inside the Container View Controller

In the ContainerViewController, we have several instance properties:

  1. FilterListContainerView & MovieListContainerView: The container views that will be used to add the child view controllers.
  2. FilterListViewController & MovieListViewController: The reference to Movie List and Filter List View Controllers that will be instantiated using the Storyboard ID.
  3. movie: The Movie array that is instantiated using default hardcoded Movies.

When the viewDidLoad is invoked, we call the method to setup the Child View Controllers. Here are several tasks it performs:

  1. Instantiate the FilterListViewController and MovieListViewController using the Storyboard ID;
  2. Assign them to the instance properties;
  3. Assign the MovieListViewController the movies array;
  4. Assign the ContainerViewController as the delegate of FilterListViewController so it can respond to the filter selection;
  5. Set Child Views frames and add them as the Child View Controller using the helper method extension.

For the FilterListViewControllerDelegate implementation, when filter is selected or deselected, the default Movies data is filtered for each respective genre, rating, and duration. Then, the result of the filter is assigned to the MovieListViewController‘s movies property. For the deselection of all filters, it just assigns the default movies data.

Conclusion

By looking at the sample project, we can see the benefit of using View Controller Containment and Child View Controller in our app. We can divide the responsibilities of single View Controller into separate View Controllers that only has single responsibility (SRP). We also need to make sure that the Child View Controller does not know anything about its parent. For the Child View Controller to communicate back to the parent, we can use the Delegation pattern.

This approach also provides the benefit of loosely coupled modules that can lead to better reusability and testing of each components. It really helps us scale our app as it grow larger and more complex. Let’s keep learning 📖, Merry Christmas🎄, and Happy New Year🎊 to all of you! Keep on Swifting with Cocoa!!😋

This is a guest post by Alfian Losari. The article was first published on Medium.

About the Author: Alfian Losari: I am a passionate engineer that loves all about technology and its value. For me when you stop learning, you will stop going forward.
iOS
How to Integrate Google Street View in iOS Apps
iOS
Creating a Simple Game With Core ML in Swift 4
iOS
Design Patterns in Swift #3: Facade and Adapter
Shares