View Controller containment in Swift

kevinmaarek

Maarek

Posted on November 14, 2020

View Controller containment in Swift

Containment of view controllers is an often used technique in UIKit.
It allows to drastically reduce the complexity of a view controller, very easily, simply by splitting it into multiple smaller ones.
Having smaller view controllers, with a reduced functional scope, makes testing easier, so as reusability across your codebase.

Pratical example

Let’s say you have a video player in your app. The first third of the screen would be used for the actual video player, with the controls and everything (play/pause, time indicator), and the rest of the screen would display the playlist. A very common layout - the exact one you would find on the YouTube app.

YouTube-like player layout

I would be very suitable to split this PlayerViewController into :

  • VideoPlayerViewController
  • PlaylistViewController

The two smaller view controller would have simple interfaces like delegates :

    func playerDidEndPlaying(player: Player, media: Media)
    func playlistDidSelect(playlist: Playlist, media: Media)
Enter fullscreen mode Exit fullscreen mode

Allowing you to build a “main” view controller that would implement these methods and be used as data source for your two child view controller.
The best part is if you wish you swap the playlist for a comment section - say, after a user action, you would just replace the Playlist child view controller by a CommentViewController.

Alt Text

You would then be able to test independently the comment, player and playlists view controllers.
As you can see, view controller containment is great for reusability, a better code organization, reducing complexity and better testing.

Here is a very helpful extension to add and remove a child view controller.

extension UIViewController {
    /// Add a child UIViewController to a given UIView. If `view` is set to nil or not set, it will add the child the ViewController's view.
    /// - Parameters:
    ///   - child: Child view controller as a UIViewController
    ///   - view: Target UIVew that will contain the child
    func add(_ child: UIViewController, view: UIView? = nil) {
        let view: UIView = view ?? self.view
        addChild(child)
        child.view.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(child.view)
        NSLayoutConstraint.activate([
            child.view.topAnchor.constraint(equalTo: view.topAnchor),
            child.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            child.view.leftAnchor.constraint(equalTo: view.leftAnchor),
            child.view.rightAnchor.constraint(equalTo: view.rightAnchor),
        ])
        child.didMove(toParent: self)
    }


    /// Remove a child viewController from it's container
    func remove() {
        guard parent != nil else {
            return
        }
        willMove(toParent: nil)
        view.removeFromSuperview()
        removeFromParent()
    }
}
Enter fullscreen mode Exit fullscreen mode

A simple use-case based on the given example with a PlayerViewController would render as follow :

class PlayerViewController: UIViewController {
    let player = VideoPlayerViewController()
    let playlist = PlaylistViewController()
    let comment = CommentViewController()

    let playerViewContainer = UIView()
    let bottomViewContainer = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.add(self.player, view: self.playerViewContainer)
        self.add(self.playlist, view: self.bottomViewContainer)
    }

    func showComment() {
        self.playlist.remove()
        self.add(self.comment, view: self.bottomViewContainer)
    }
}
Enter fullscreen mode Exit fullscreen mode

As you can see, it is very simple and makes the "main" view controller clean, and free from player, playlist or comment related business logic!
Aside of the reusability and cleanness of view controllers containment, the main benefit is that the child view controllers can interact with the parent's life cycle (didLoad, didAppear...).

I hope you enjoyed this little post. I like sharing about my way of coding, if you have any question, feel free to hit me on Twitter or use the comment section :)

Happy coding!

💖 💪 🙅 🚩
kevinmaarek
Maarek

Posted on November 14, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related