2️⃣Navigation

Fall 2023 | Vin Bui

So far, we've only worked with one screen which may contain many views. For this single screen, we controlled the models and views with a single UIViewController. However, many of the apps that we use today contain multiple screens that we can navigate between. In UIKit, we represent every distinct screen with a separate UIViewController. There are two ways to navigate between screens: 1) pushing/popping and 2) presenting/dismissing.

Pushing/Popping

Before we dive in to the technical details, let’s observe what pushing and popping looks like.

As we can see, when we tap on one of the cells, a new screen shows up. Each new screen that we push is a separate UIViewController. So then, how do we keep track of the view controllers that have been pushed to figure out which one needs to be popped? We use a navigation stack. Every time a view controller is pushed, it goes on top of the navigation stack. Think of the navigation stack as a stack of books where each book is a view controller. The last item to be pushed into this stack will be the first one to be popped out (LIFO). How do we represent this navigation stack in UIKit? We use a UINavigationController.

Inside of SceneDelegate.swift add this code to the function scene:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // 1. Capture the scene
    guard let windowScene = (scene as? UIWindowScene) else { return }
    
    // 2. Create a new UIWindow and pass in a UIWindowScene
    let window = UIWindow(windowScene: windowScene)
    
    // 3. Create a view hierarchy programmatically
    let rootVC = ViewController()
    let navController = UINavigationController(rootViewController: rootVC)
    
    // 4. Set the navigation controller as the window's root view controller
    window.rootViewController = navController
    
    // 5. Set the window and call makeKeyAndVisible()
    self.window = window
    window.makeKeyAndVisible()
}

The important step to remember here is step 3 where we first initialize the view controller (rootVC) that we want to be displayed when the app launches. Now, we have to place rootVC into the navigation stack by creating a UINavigationController with rootVC as the root view controller.

One thing to keep in mind is that the line let rootVC = ViewController() creates a view controller whose class name is ViewController. When we create a new project, by default, a class called ViewController is created for us. If we created a new class or renamed the class to HomeViewController, then we would use HomeViewController() instead.

Now, to push and pop a view controller is very simple:

// Push
navigationController?.pushViewController(_ viewController: UIViewController, animated: Bool)

// Pop
navigationController?.popViewController(animated: Bool)

UIViewController will be the view controller object that we want to push. Most of the time, we want to set animated to true. For example, if we had a class called ProfileViewController and wanted to push it, we would write the following code in the view controller class that is pushing it (not inside ProfileViewController):

let profileVC = ProfileViewController()
navigationController?.pushViewController(profileVC, animated: true)

To pop the ProfileViewController, we would write this code in ProfileViewController:

navigationController?.popViewController(animated: true)

We could then link this code to some action such as a tapping on a button, cell, image, etc.

Presenting/Dismissing

Let’s observe what presenting and dismissing looks like.

As we can see, a modal sheet is presented from the bottom of the screen and gradually transitions up. This is presenting. To dismiss, we can simply click on the cancel button or more commonly, swipe downwards from the top of the modal sheet. The view controller that is being presented is displayed on top of the previous view controller. There is not a navigation stack at play here ⇒ No UINavigationController.

To present/dismiss a view controller:

// Presenting
present(_ viewControllerToPresent: UIViewController, animated: Bool, completion: (() -> Void)?)

// Dismissing
dismiss(animated: Bool, completion: (() -> Void)?)

For example, if we had a class called ProfileViewController and wanted to present it, we would write the following code in the view controller class that is pushing it (not inside ProfileViewController):

let profileVC = ProfileViewController()
present(profileVC, animated: true)

To dismiss the ProfileViewController, we would write this code in ProfileViewController:

dismiss(animated: true)

As we may have notice from the function header above, there is an optional parameter called completion. This is known as a completion handler which is a function that gets executed when the function call is complete. We will discuss this in detail once we get into networking.

Last updated