Using Composition in iOS Architecture

I recently updated an old github project of mine that I created almost four years ago when I first started doing iOS development. It was an example of using container view controllers, in particular I was looking at transitioning between view controllers. In the project three child view controllers were contained in a single view controller and they rotated in a counter clockwise action periodically. This was an extremely simple and contrived example of view controller containment, just to help me understand how such a technique would work.

In revisiting view controller containment I found several recent blog posts and conference talks about using container view controllers in iOS to assist with the separation of concerns and prevent massive view controllers. People like Dave DeLong, John Sundell, and Paul Hudson. This made me think about my original implementation and how I approach building iOS apps now compared to when I first started developing apps.

View controller containment is well known to iOS developers as we use Apple’s containers all the time in the form of UINavigationController, UITabBarController, UIPageViewController and UISplitViewController. Custom container view controllers have been around since iOS 5 and were implemented by Apple, in part, to prevent what they saw as abuse of the view hierarchy in apps. This occurs when the view from one view controller is “ripped” out and placed in another controller’s view hierarchy. This leads to many of the view controller appearance and rotation methods not being called, which in turn leads to unexpected behavior and a bad user experience. Apple’s talk can be found here: WWDC 2011 — Session 102. The solution is fairly straight forward as all UIViewController classes have methods designed to add and remove child controllers. In fact there are several methods that need to be called when adding or removing a child view controller, which can be found in Apple’s View Controller Programming Guide for iOS.

There are also two appearance methods mentioned in Apple’s guide, which the parent (container) view controller could call to inform the child of pending appearance changes.

I created an example app to explore several ideas including view controller containment, composition, and pure code layout. The app contains two content view controllers; one is a simple user profile and the other story-like content. These are combined in a container view controller allowing you scroll the content vertically. If you had multiple view controllers being used your app, but then needed them to appear together in single screen, you can accomplish that using view controller containment. View controller containment helps to illustrate the concepts of composition and separation of concerns. Each view controller is a separate component with a single purpose (i.e. display a user profile). These components are then combined in a container view controller to create a more complex screen of content

Layout

The UIViewController’s content views are laid out using code only, no InterfaceBuilder and no Auto Layout. I wanted to try something different in this example, so I opted to just lay the views out in code. This has some challenges in more complex view hierarchies, but it also has some advantages. The downside is there can be some complex math involved, as you need to calculate the position and size of each view manually. In this simple hierarchy, it is relatively easy, but in a more complex view, you might want to divide the screen into separate sections, build those views manually and then combine all the views together. Composition is one of the most powerful and important concepts in programming. On the plus side it means that layout changes are handled relatively easily, there are no constraints to add/remove or adjust. We override viewWillLayoutSubviews and calculate the view’s frames. Whenever the device is rotated this method is called again and the views frames are adjusted accordingly, very nice.

In a real world app I would override loadView and place all the view layout code in a custom view class that could be reused in other parts of the app as required. We will do that in a follow-up post, Container View Controllers Redux, where we will build a more complex and complete app, that includes additional concepts like networking and flow coordinators.

Composition

The ProfileViewController contains a custom view to create a circular profile image. We see circular images in many apps today. Often the rounded image is created in each UIViewController or UITableViewCell. However, we should be thinking about composition again, and designing reusable views that we can compose with other design elements. View controller containment is another great example of composition as we can use it to combine existing components isolating their individual complexity while building more complex features. We create a ScrollingViewController class with a scrollViewContent property to hold content view controllers. The entire contents can then be scrolled similar to a news feed in Facebook or Twitter.

Containment

The ScrollingViewController class is used to contain the content of other UIViewControllers. This is loosely drawn from a project that I was involved with at my work where a screen contained a profile view, a list of items for rent by the user and some controls (e.g. add new item). The profile view was displayed in many locations in the app, and should have been a separate component, designed and built once. Instead it was being recreated in each UIViewController where it was being displayed.

The ScrollingViewController overrides loadView to replace the UIViewController‘s main view with a UIScrollView. The content views are then add to the UIScrollView when the UIViewController is initialized. The layoutContentViews method is used to calculate the frame and position of each content view in the UIScrollView. The PortraitViewController has a preferredContentSize, since it does not encompass the entire screen. We use this property to adjust that view’s frame accordingly. Apple’s documentation describes this property as:

The value in this property is used primarily when displaying the view controller’s content in a popover but may also be used in other situations. Changing the value of this property while the view controller is being displayed in a popover animates the size change; however, the change is not animated if you specify a width or height of 0.0.

The PortraitViewController‘s preferredContentSize is updated each time the view lays out its subviews. We need to handle this change in the parent view controller. The important method in this case is UIViewController’s preferredContentSizeDidChange, according to Apple’s documentation this method

Notifies an interested controller that the preferred content size of one of its children changed

We override this method to update the content views of the UIScrollView when the device is rotated. We set the UIScrollView’s contentSize property to zero, and then call layoutContentViews. This allows us to handle adjusting the content views on device rotation.

note: view controller containment may not be suitable for paginated data or infinite scrolling, since the view controllers are created up front not on demand like a UITableViews data source and reuse pool provide.

I hope you enjoyed this post. When building your apps try to actively incorporate composition in your design. Even small steps like just separating state properties into enums, making new types to hold method parameters, encapsulating networking code or moving view creation code into a separate class can go a long way to making your apps easier to understand and maintain.

The final project can be found here: ViewControllerContainment

Sources

[1]Dave DeLong Blog
[2]SwiftBySundell
[3]Hacking with Swift
[4]Hades Image
[5]Icons8
[6]AppIconMaker

Referenced Authors

If you are interested in any of the authors mentioned in this post you can follow them on Twitter at

Dave DeLong(@davedelong
John Sundell(@johnsundell)
Paul Hudson(@twostraws)
Soroush Khanlou (@khanlou)