I am the author of iOS 17 Fundamentals, Building iOS User Interfaces with SwiftUI, and eight other courses on Pluralsight.
Deepen your understanding by watching!
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!