Make App Pie

Training for Developers and Artists

Programmatic Navigation View Controllers in Swift 3.0

Navigation controllers are the workhorse of  organizing view controllers. I’ve covered much of their use in other posts about MVC, segues and delegates. In this chapter, we’ll go through some of the Swift code for the Navigation controller.

The View Controller Stack

Navigation view controllers are stack based. The newest view controller is the visible one. It is on top of the last one we saw.
If you are not familiar with the stack data structure, it is useful to understand stacks and their nomenclature a little better. Stacks work a lot like a deck of cards that are face up.

2016-07-06_06-20-22

You can only see the top card. When you place a card on top of the stack, you push a card on the stack. When you remove a card from the stack and show the card below it, you pop it off the stack. We use the terms push and pop a lot to talk about stacks. The two major methods we’ll be talking about popViewController and pushViewController use this nomenclature to explain what they do.

Opening a View Controller in a Xib

Let’s look at a few ways to programmatically move through view controllers by pushing and popping to the navigation stack directly.
Start a new single view project in Swift called SwiftProgNavControllerDemo. Go into the storyboard. Set your preview for  iPhone 8 selected for View as: in the lower left of the storyboard”

Select the blank view controller. Be sure to select the controller by clicking the view controller icon  icon and not the view. From the drop down menu select Editor>Embed in > Navigation Controller.
In the view controller, add a label and a button so your code looks like the diagram below. If you wish you can also color the background:

2016-07-06_07-49-58

Open the assistant editor. Control-drag the button and make an action for a UIButton called nextButton. Remove everything else from the class for now. Add the following two lines to the nextButton

let vc = TwoViewController(
     nibName: "TwoViewController",
     bundle: nil)
navigationController?.pushViewController(vc,
     animated: true)

Line 1 creates a view controller of class TwoViewController, using the XIB of the same name. Line 2 pushed the view controller on the navigation controller stack maintained by ViewController. Your code should look like this when done:

class ViewController: UIViewController {
    @IBAction func nextButton(_ sender: UIButton){
    let vc = TwoViewController(
        nibName: "TwoViewController",
        bundle: nil)
     navigationController?.pushViewController(vc,
        animated: true)
    }
}

We need another view controller as a destination. Press Command-N or click File>New>File… Choose a iOS source template of Cocoa Touch Class. Make the new file a subclass of UIViewController and name the file TwoViewController. We’ll work with a xib for our destination controller. Check on the option Also create XIB file.

2016-07-06_07-45-10

You will find a new xib in interface builder. Set it up to look like the illustration below.

2016-07-08_05-44-23

In the assistant editor, remove everything to have an empty class. Control-drag the Next button inside the TwoViewController class. Make an @IBAction method named nextButton  as an UIButton.  Add the following code to the nextButton() method:

let vc = ThreeViewController(
            nibName: "ThreeViewController",
            bundle: nil)
        navigationController?.pushViewController(vc,
                animated: true )

Your code should look like this:

class TwoViewController: UIViewController {
    
    @IBAction func nextButton(_ sender: UIButton) {
        let vc = ThreeViewController(
            nibName: "ThreeViewController",
            bundle: nil)
        navigationController?.pushViewController(vc,
            animated: true )
    }
}

Let’s do this one more time so we end up with three view controllers to push onto the view controller stack. Follow the same procedure as you did for TwoViewController, but name it ThreeViewController. Set the view to look like this:

2016-07-08_05-44-43

There’s no buttons here, so you have no actions to set. Change your simulator to iPhone 8. Build and run. Tap the Next button and move between the three view controllers.

2016-07-06_08-25-30    2016-07-08_05-43-07    2016-07-08_05-43-43

Pushing a view controller from a  xib  is two lines of code:

let vc = ViewControllerName(nibName: "nameOfNib", bundle: nil)
navigationController?.pushViewController(vc, animated: true )

The first line creates the view controller. I tend to keep it simple and use vc, though if I had more than one, I’d be more descriptive.
Xibs are probably one of the most common uses for pushing a view controller. There are situations where you cannot use the storyboard and this is a good traditional alternative. Often self-contained reusable  modules will use a xib instead of a storyboard.

Programmatic Segues to View Controllers

For most uses, I prefer the storyboard over xibs for two reasons: first it is better documentation of the user interface. Secondly, I prefer to let the system do as much of the background work as possible by using the storyboard. The deeper into code you go, the more you have to worry about unexpected bugs.

We can programmatically push a view controller from a storyboard in two ways: segues or storyboard identifiers.  We’ll start with segues. One of the first ways anyone learns to use storyboards is direct segues, connecting up a button directly to a view. You can also do segues programmatically, which is useful when you conditionally go to a view.

Go to the storyboard. Add  two more view controllers to the storyboard. Label one View Controller Four and the other View Controller five.

2016-07-07_05-47-25

Click on the ViewController scene title in the storyboard. From the view controller Icon view controller iconon ViewController, control-drag from ViewController to somewhere on View Controller Four’s content so it highlights.

2016-07-07_05-55-32

Release the mouse button.  In the menu that appears, select Show.

2016-07-07_05-59-44

Click on the Show segue icon show segue icon to select the segue. Go into the properties inspector and set the segue’s Identifier to Four.

2016-07-07_06-03-18

Drag another button out to the View Controller scene and make the title Four.

2016-07-07_06-05-28

Go to ViewController class and add the following method:

@IBAction func fourFiveToggleButton(_ sender: UIButton){
    performSegue(withIdentifier: "Four",
        sender: self)
}

Open the assistant editor and drag from the circle next to the fourButton() method over the Four button and release.
The code above is a mere one line: it runs the segue. If you set the segue identifier correctly, that is all you need to do.

One use is conditional cases. Conditions in the current view controller or model might change. The app may display different view controllers based on those conditions. Let’s try a simple example. Just as we did with View Controller Four, make a segue with an identifier Five by control-dragging from the view controller icon view controller iconon View Controller One to the view of  View Controller Five.

2016-07-07_06-18-05

Select a Show segue. Select the segue by clicking the show segue icon show segue icon.  In the attributes inspector change the Identifier to Five.

Change the code for fourFiveToggleButton to this

@IBAction func fourFiveToggleButton(_ sender: UIButton){
    let normal = UIControlState(rawValue: 0) //beta 1 has no .normal bug#26856201
    if sender.titleLabel?.text == "Four"{
        performSegue(withIdentifier: "Four",
                     sender: self)
        sender.setTitle("Five", for: normal)
    } else{
        performSegue(withIdentifier: "Five",
                     sender: self)
        sender.setTitle("Four", for: normal)
    }
}

The code checks the contents of the title label. If the label is Four it goes to View Controller Four and changes the title label. If the label is Five, it goes to View Controller Five and toggles the label back to Four

Build and run.  we can toggle between the two views.

2016-07-07_06-44-06

An Interesting Stack Demonstration

Stacks are linear collections. How we compose that collection on the storyboard might be nonlinear. For example, View controllers four and five might both segue into view controller six.  On the storyboard drag out another view controller. Set a background color for it. Label it View Controller Six

 2016-07-08_05-55-47

For this example, we’ll use the storyboard directly. On View Controller Four, drag a Navigation Item. Title the navigation Item Four. Drag a bar button item to the navigation controller.  Title it Six

2016-07-08_05-57-26

Do the same for View Controller Five so the navigation bar looks like this:

2016-07-08_05-57-47

Control drag from View controller Four’s Six button to the Six View Controller. Select a Show Segue. Repeat for the  Five View Controller. Control drag from the Six button to the Six View Controller. Select a Show Segue.  Your storyboard now has this:

2016-07-08_06-06-24

We’ve set a storyboard where we go to Five or Four, and then go to six.  Build and run. We can go to Six from both view controllers.

2016-07-08_06-09-46

Closing a View Controller

Up to now, we’ve relied on the navigation controller’s Back button. Dismissal of view controllers with  popViewController() are common in an application. Almost every delegate between view controllers in a navigation stack will use it. There are several versions of the popping off controller for different uses.

Add two buttons to View Controller Six, titled Back and Root.

2016-07-08_06-14-23

Press Command-N to make a new class called SixViewController, subclassing UIViewController. Remove all the methods in the class.  In the SixViewController class, create an action  backButton:

 @IBAction func backButton(_ sender: UIButton) {
        navigationController?.popViewController(animated:true)
    }

You will get a warning  Expression of type 'UIViewController?' is unused. Ignore it for now, we’ll discuss it later.

Also in SixViewController,  add to the rootButton() method:

@IBAction func rootButton(_ sender: UIButton) {
  navigationController?.popToRootViewController(animated:true)
    }

Go to the story board and open the assistant editor. Drag from the circle next to backButton to the Back button. Drag from the circle next to the rootButton to the Root Button. Build and Run.  Go to Six. Press the new Back button. You go back to Four. Go to Six again and press the root button. You go back to One.

There are three versions of pop: popViewController(), popToRootController(), and popToViewController() The most Common is popViewController() which removes the top view controller from the stack. popToRootViewController() and popToViewController() pops everything or everything up to a specific view controller off the stack, returning what it popped off.

Because popViewController returns a value, we are getting the two warnings. We have to do something with the return value. You’ll notice in the error message that we get an optional returned.  If nil, there were no view controllers to pop off.  Use this to make sure you do have a navigation stack under the current controller. Change the class to this:

class SixViewController: UIViewController {
    @IBAction func backButton(_ sender: UIButton) {
        guard (navigationController?.popViewController(animated:true)) != nil
            else {
                print("No Navigation Controller")
                return
        }
    }
    @IBAction func rootButton(_ sender: UIButton) {
        guard navigationController?.popToRootViewController(animated: true) != nil
        else {
            print("No Navigation Controller")
            return
        }
    }
    
    }

By using guard and checking for nil the application checks to make sure there is a navigation stack. If not, code handles the error. If there is a segue set to Present modally by mistake and tries popping off the controller, the error gets handled. This is especially important in two situations: when you use a xib in a navigation controller and when you use a Storyboard ID. Both cases are independent of segues. Both can be used as a modal controller and a navigation controller. It’s likely you have modules set up for use in different applications, and sometime they are modal and sometimes navigation controllers. For example, go to the code for TwoViewController. Add the following action:

 @IBAction func backButton(_ sender:UIButton){
        guard navigationController?.popViewController(animated: true) != nil else { //modal
            print("Not a navigation Controller")
            dismiss(animated: true, completion: nil)
            return
        }
    }

We expanded the guard clause slightly here from the previous example. If there is no navigation controller, we must be in a modal. Instead of popping the controller, we dismiss it.

Add a Back button to the Two View Controller:

2016-07-08_06-58-13

With the assistant editor open, Control-drag from the Back button we created to the backButton code.

Add another button to View Controller One titled Two Modal.

2016-07-08_07-00-43

Open the assistant editor. Control drag the Two Modal Button to make a new action  named modalTwoButton. Add the following code to the new action to present a modal view:

@IBAction func modalTwoButton(_ sender: UIButton) {
    let vc = TwoViewController(
        nibName: "TwoViewController",
        bundle: nil)
    present(vc,
        animated: true,
        completion: nil)
    }

Build and run. Tap the TwoModal Button, and the modal view slides up from the bottom.

2016-07-08_07-23-36

Tap the Back button and it goes back to View Controller one. Tap the Next button  and you slide sideways into a navigation view.

2016-07-08_07-24-36

Tap Back and you are back in View Controller One

2016-07-08_07-27-12

Using Storyboard ID’s With Navigation Controllers

In between Xibs and the Storyboard are storyboard ID’s.  When you want all of your view controllers on one storyboard, but also want to call the view controller from several different controllers you might want to use a storyboard ID. Storyboard ID’s can programmatically called both by modals and navigation controllers. Some view controllers might have a segue at one place and called by a Storyboard ID in another.  On the storyboard find View Controller Six

2016-07-08_07-32-27

In the Identity inspector, set the Storyboard ID to Six

2016-07-08_07-34-45

On View Controller One add two more Buttons labeled Six Navigation and Six Modal.

2016-07-08_07-38-37

Control-Drag the Six Navigation Button into the assistant editor set to Automatic.  Make an action sixNavigationButton. Now do the same with the modal button. Control-Drag the Six Modal Button into the assistant editor.  Make an action sixModalButton.

The two actions are very similar. They will get a view controller from the method

 storyboard?.instantiateViewController(withIdentifier:String)

then present it for a modal or push it for a navigation controller. Add this code to the two actions:

@IBAction func sixNavigationButton(_ sender: UIButton) {
        guard let vc = storyboard?.instantiateViewController(withIdentifier: "Six") else {
            print("View controller Six not found")
            return
        }
        navigationController?.pushViewController(vc, animated: true)
    }
    
    @IBAction func sixModalButton(_ sender: UIButton) {
        guard let vc = storyboard?.instantiateViewController(withIdentifier: "Six") else {
            print("View controller Six not found")
            return
        }
        present(vc, animated: true, completion: nil)
    }

Go over to the SixViewController class. Add the dismiss method to dismiss the modal in the backButton action :

    @IBAction func backButton(_ sender: UIButton) {
        guard (navigationController?.popViewController(animated:true)) != nil
            else {
                dismiss(animated: true, completion: nil)
                return
        }
    }

Build and run. Tap Six Navigation, and you get to the navigation

2016-07-10_13-35-01

Tap back and you get back to One again. Tap six modal and you get the modal

2016-07-10_13-35-24

Tap back and you get back to One again.

2016-07-10_13-35-40

You’ll notice I left the Root button doing nothing for a modal since it has no meaning for modal.

One More Place to Explore: The Navigation Back Button.

For most cases we get a Back button like this on the navigation bar:

2016-07-08_08-08-37

But you may notice that the Six controller does this, depending where it is pushed from:

2016-07-08_08-07-53

2016-07-08_08-08-16

2016-07-08_08-11-38

The title for the Back button comes from the view controller below it on the stack. When I push Six from Five, Five shows up as the title in the back button. This is another exploration you might want to take about navigation controllers, which you can find in the post Using the Navigation Bar Title and Back Button

The Whole Code

<h2>ViewController.swift</h2>
//
//  ViewController.swift
//  SwiftProgNavControllerDemo
//
//  Created by Steven Lipton on 7/6/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
//    
// Action for using a story board id for navigation controller
//  let vc = storyboard?.instantiateViewController(withIdentifier: "Six")
//
    @IBAction func sixNavigationButton(_ sender: UIButton) {
        guard let vc = storyboard?.instantiateViewController(withIdentifier: "Six") else {
            print("View controller Six not found")
            return
        }
        navigationController?.pushViewController(vc, animated: true)
    }
    
//    
// Action for using a story board id for modal controller
//  let vc = storyboard?.instantiateViewController(withIdentifier: "Six")
//
    @IBAction func sixModalButton(_ sender: UIButton) {
        guard let vc = storyboard?.instantiateViewController(withIdentifier: "Six") else {
            print("View controller Six not found")
            return
        }
        present(vc, animated: true, completion: nil)
    }
    
//
// modal example for use with dismissal see TwoViewController
//
    @IBAction func modalTwoButton(_ sender: UIButton) {
        let vc = TwoViewController(
            nibName: "TwoViewController",
            bundle: nil)
        present(vc, animated: true, completion: nil)
    }

//
// Example of pushing a view controller 
//navigationController?.pushViewController(vc, animated: true)
//
    @IBAction func nextButton(_ sender: UIButton) {
        let vc = TwoViewController(
            nibName: "TwoViewController",
            bundle: nil)
        navigationController?.pushViewController(vc, animated: true)
    }

//
// Example of Logically pushing a view controller from a segue
// performSegue(withIdentifier: "Four",sender: self)
//
//
    
    @IBAction func fourFiveToggleButton(_ sender: UIButton){
        let normal = UIControlState(rawValue: 0) //beta 1 has no .normal bug#26856201
        if sender.titleLabel?.text == "Four"{
            performSegue(withIdentifier: "Four",
                         sender: self)
            sender.setTitle("Five", for: normal)
        } else{
            performSegue(withIdentifier: "Five",
                         sender: self)
            sender.setTitle("Four", for: normal)
        }
    }

}




<h2>TwoViewController.swift</h2>




//
//  TwoViewController.swift
//  SwiftProgNavControllerDemo
//
//  Created by Steven Lipton on 7/6/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit

class TwoViewController: UIViewController {

//
// A back button for dismissal of both modals and navigation controllers
//
//
    @IBAction func backButton(_ sender:UIButton){
        guard navigationController?.popViewController(animated: true) != nil else { //modal
            print("Not a navigation Controller")
            dismiss(animated: true, completion: nil)
            return
        }
    }
}




<h2>ThreeViewController.swift</h2>




//
//  ThreeViewController.swift
//  SwiftProgNavControllerDemo
//
//  Created by Steven Lipton on 7/6/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit

class ThreeViewController: UIViewController {
    }





<h2>SixViewController.swift</h2>




//
//  SixViewController.swift
//  SwiftProgNavControllerDemo
//
//  Created by Steven Lipton on 7/8/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit

class SixViewController: UIViewController {
 
//
// Another back button for dismissal of both modals and navigation controllers
//
//   
    @IBAction func backButton(_ sender: UIButton) {
        guard (navigationController?.popViewController(animated:true)) != nil
            else {
                dismiss(animated: true, completion: nil)
                return
        }
    }

//
// A pop to root of the navigation controller example 
//  navigationController?.popToRootViewController(animated: true)
//
    @IBAction func rootButton(_ sender: UIButton) {
        guard navigationController?.popToRootViewController(animated: true) != nil
        else {
            print("No Navigation Controller")
            return
        }
    }
    
    }


11 responses to “Programmatic Navigation View Controllers in Swift 3.0”

  1. very nicely presented and was very helpful clearing up many unanswered questions. Thank you.

    one question from my side is I would like to have a back two controller backButton. Say someone goes to a page and my popup states they must be logged in. They log in and it pops back, no worries.
    But, what if they go to the login page, but need to register. They register and now need to go back 2 controllers. This is where I am getting the issue. The original view controller is built from an array, so I cannot use a segue.

    Thanks
    David

    1. That’s where a popToViewController comes in. IF the view controller you a popping to is your root controller you can just use a popToRootController.

      https://developer.apple.com/reference/uikit/uinavigationcontroller/1621871-poptoviewcontroller

  2. Hello,
    Opening a View Controller in a Xib is not working.
    You forget to give us an important information in order to make it work.
    Do you have to use a segue ?
    Do you have to put something in the main storyboard ?

    Jamal Ait Lhassan.

    1. The whole point is not to use a segue or anything in the main storyboard.
      Its working for me in Xcode 8.2. Make sure you spelled the nibName right and the bundle is nil so this line of code works:

      let vc = TwoViewController(
                  nibName: "TwoViewController",
                  bundle: nil)

      Also be sure the .xib file is in the current bundle. If that is true you should have a TwoViewController instance you can either present or push.

  3. EditOR->Embed in->Navigation Controller

  4. Hi, I’m using this technique to push my VC’s but I’d like to apply a custom segue transition to it. How would I go about it?

    1. I’ve never used a custom segue before.

  5. Hi, this tutorial is great. I’m wondering how I would push from an action sheet button to a new view controller that I already have set up?

    1. Set your code in the UIAlertAction to open the new view controller programatically. If you are not familiar with doing this in closures there’s an example at the end of this: https://makeapppie.com/2016/09/12/understand-and-use-closures-in-swift/

  6. One tableview should display a list of categories using arrays(eg: vegetables, fruits etc). On selecting a particular item should lead to another page showing a list of items(arrays) under that category (Eg: Mango, apple, orange etc)in a tableview. On selecting a particular item in this tableviewshould lead to another tableviewshouls show an image, the first selected category name and subcategory name in one cell.
    how to do this using navigation controller?

Leave a reply to Steven Lipton Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.