UIViewController Containment

UIViewController Containment is an excellent way to break up large controllers. We talked about unit testing UITabBarController last time, which is an example of UIViewController containment. However, it is not so obvious how to setup containment using Interface Bulder for your own controllers. It is also not obvious how to test containment.

Unit Test Setup

The first thing we are going to do is ensure that our top level ViewController is correctly setup to be our system under test.

class ViewContainmentTests: XCTestCase {
    
    var sut: ViewController!
    
    override func setUp() {
        super.setUp()
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        sut = storyboard.instantiateInitialViewController() as! ViewController
        UIApplication.shared.keyWindow?.rootViewController = sut
    }
    
    override func tearDown() {
        sut = nil
        super.tearDown()
    }
}

Outlet Setup

Then we will setup our ViewController to have a TopViewController and a BottomViewController.

class ViewController: UIViewController {
    var topViewController: TopViewController!
    var bottomViewController: BottomViewController!
}

class TopViewController: UIViewController {
}

class BottomViewController: UIViewController {
}

Next comes a failing unit test.

func testViewContainment() {
    XCTAssertNotNil(sut.topViewController)
    XCTAssertNotNil(sut.bottomViewController)
}

It is not obvious what the next few steps are.

  1. Drop a Container View from the Object Library for the top and bottom views
  2. Set the Custom Classes to TopViewController and BottomViewController

I’ve also changed the background colors of the views to make them more prominent.

Interface Builder: UIViewController Containment

Also notice the “Embed segue to View Controller” in the View Controller list on the left. This is a hint as to what needs to be done next.

We’ll go back to our ViewController class and handle these segues:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let vc = segue.destination as? TopViewController {
        topViewController = vc
    } else if let vc = segue.destination as? BottomViewController {
       bottomViewController = vc
    }
}

And now when we run our tests they all pass.