Access Sub-Controllers from a UINavigationController in Swift

The sequence of accessing a UINavigationController's first child from within the AppDelegate or within prepareForSegue(_:sender:) always gets me. Here are a few quick snippets to help you and I quickly get up and running when working with navigation controllers in our Swift applications:

AppDelegate

Every iOS application has one root view controller that’s presented when the app finishes its launch process. Suppose we’re building a navigation controller-based app… that is, we’re building an app where the first (root) view controller is a UINavigationController. In our Storyboard, we’ve set up a Scene with some UI controls with a view controller and some properties, and we’ve embedded that view controller in a navigation controller.

What if we want to set some of the view controller’s properties after the app launches? How could we go about doing that?

I tend to always think of the “first view controller” as the first Scene in the Storyboard where I’ve set up UI components. To iOS, however, the navigation controller is actually the first (or root) view controller.

When an app incorporates a navigation controller as its first (root) view controller, we end up needing to do a little digging into the view controller hierarchy to get access what we might perceive as the true “first view controller”.

Digging for the first view controller

Here is a snippet of how to dig into the UINavigationController's view controller hierarchy to grab the first one and set some fictitious properties on it, all from within the AppDelegate:

 1class AppDelegate: UIResponder, UIApplicationDelegate {
 2
 3    var window: UIWindow?
 4
 5
 6    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
 7        // Override point for customization after application launch.
 8        
 9        let navigationController = window?.rootViewController as! UINavigationController
10        let firstVC = navigationController.viewControllers[0] as! NameOfFirstViewController
11        // set whatever properties you might want to set
12        // such as an NSmanagedObjectContext reference
13
14        return true
15    }
16
17    // ...
18}

So the workflow goes like this:

  • Get the window’s root view controller (which is the navigation controller in our case)
  • Get the navigation controller’s first view controller from its array of view controllers (which is what I always think of as the “first” view controller)
  • Set whatever properties you need to set

Note: With iOS 13, the code to access the root view controller needs to go in your app’s SceneDelegate.

You may be worried about the usage of implicitly unwrapped optionals in this snippet. I tend to avoid them wherever possible too, but I used them here because I reasoned that my navigation controller-based app hinges on the fact that the root view controller of the application is a UINavigationController. Something so fundamental to the app warranted my usage of the implicitly unwrapped optionals, since changing the navigation paradigm of the app would probably break things anyway.

If you’re not convinced by that line of reasoning, no worries – you can switch out some of the ! operators for ? operators and add in some if-let syntax to protect against encountering nil. For example:

 1class AppDelegate: UIResponder, UIApplicationDelegate {
 2
 3    // ...
 4    if let navigationController = window?.rootViewController as? UINavigationController {
 5        if let firstVC = navigationController.viewControllers[0] as? NameOfFirstViewController {
 6            firstVC.someProperty = someValue
 7        }
 8    }
 9    // ...
10}

Prepare for segue

What about in prepareForSegue(_:sender:)? When would this even be necessary?

Well, suppose that we have an app which segues into a navigation controller. We may need to pass some data off the next view controller, but that “next view controller” is technically the navigation controller, not the controller where our properties are declared.

In similar fashion to the AppDelegate situation, we want to dig into the navigation controller’s view controller hierarchy to access the first child so that we can pass the data along. Here’s an example implementation:

 1public class ViewController: UIViewController {
 2
 3    // ...
 4    override public func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
 5        
 6        let destinationVC = segue.destinationViewController as! UINavigationController
 7        let nextViewController = destinationVC.viewControllers[0] as! SecondViewController
 8        
 9        nextViewController.someProperty = someValue
10    }
11    // ...
12}

The only thing that really changes between the AppDelegate example and the prepareForSegue example is where we obtain the UINavigationController from. In AppDelegate, the navigation controller comes from the window’s root view controller. In prepareForSegue it comes from the segue’s destination view controller.

After that, though, the process for grabbing the first child of the navigation controller is the same.

Wrapping up

Accessing a navigation controller’s view controller hierarchy was just vague enough for me to write this little reference for myself, but I hope you benefited from it as well!

comments powered by Disqus