Swift Swift: Programmatic Navigation View Controllers in Swift

[Converted to Swift 2.0 — SJL 9/17/15 ]
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. 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, and you will find they describes methods often.

Opening a View Controller in a Xib

I’ve shown elsewhere how to move to navigation controllers through segues. Let’s look at a few ways to do so programmatically by pushing and popping to the navigation stack directly.
Start a new single view project in Swift called SwiftProgNavControllerDemo . Go into the storyboard and select the blank view controller. Be sure to select the controller and not the view. From the drop down menu select Edit>Embed in > Navigation Controller.
In the view controller, Add a label and a button so your code looks like the diagram:

Screenshot 2014-09-13 17.05.30
Open the assistant editor. Control-drag the button and make an action for a UIButton called nextButton. Remove the commented out method except the viewDidLoad(). 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)
    }
    override func viewDidLoad() {
       super.viewDidLoad()
       // Do any additional setup after loading the view,
       // typically from a nib.
    }
}

Close the assistant editor.
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. To prove we are doing nothing with the storyboard, Click the option Also create XIB file to yes.
You will find a new XIB in interface builder. Set it up to look like the illustration below.

Screenshot 2014-09-13 17.04.18

Open the assistant editor and control-drag the back button inside the TwoViewController class. Make an @IBAction method named backButton as an UIButton. Do the same for the Next button, but label the action method nextButton as we did in the last view controller. 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 )
    }
    @IBAction func backButton(sender: AnyObject) {
    }
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup
        // after loading the view.
    }
}

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, Instead of a Next button make a button Root like the diagram shows.Screenshot 2014-09-13 17.04.08

Control-drag the buttons to make two @IBAction methods named backButton and rootButton . Once again, remove the extra code except viewDidLoad(). Your ThreeViewController class should look like this:

class ThreeViewController: UIViewController {
    @IBAction func rootButton(sender: UIButton) {
    }
    @IBAction func backButton(sender: UIButton) {
    }
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

}

Now that we have set up the views, build and run. Tap the Next button and move between the three view controllers. Pushing a view controller in these controllers is a mere two lines of code:

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

The first line creates the view controller, which of course we can name whatever we want. I tend to keep it simple and use vc, though if I had more than one, I’d be more descriptive. We used xibs here, which work a lot better than trying to connect to views on a storyboard. There’s a bunch of weak variables you have to dance over in that case, so often it just doesn’t work.
I rarely push view controllers. there are situations where you cannot use the storyboard and this is the alternative. I prefer the storyboard for two reasons: first it is better documentation of my user interface. Secondly, I prefer to let the system do as much of the background work as possible. The deeper into code you go, the more you have to worry about unexpected bugs. Swift and ARC Together often set nil when you don’t expect or want it. Crashing is a good way to catch those in early development, so you don’t have bigger more subtle bugs later.

Manual Segues to View Controllers

In other discussions of Segues, we talked about direct segues, connecting up a button directly to a view. You can do segues programmatically, which is useful when you conditionally go to a view. Now add another subclass of UIViewController called FourViewController.  This time you do not need a XIB. Go into the storyboard and drag a view controller onto the storyboard. Click on its view controller icon. In the identity inspector make the custom class FourViewController. Add a label to the view just so you know it is there.

Screenshot 2014-09-13 17.04.53

Click on the ViewController scene title in the storyboard. From the view controller Icon on ViewController, control-drag from ViewController to somewhere on FourViewController’s content, and release the mouse button. In the popup, select Show. Go into the properties inspector and set the segue’s identifier to four.
Drag another button out to the View Controller scene and make the title Four. Go to ViewController class and add the following method:

@IBAction func fourButton(sender: UIButton){
        performSegueWithIdentifier("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.
Where this is useful is conditional cases. We may want to go to different view controllers based on conditions in the current view controller or model. Let’s try an example: Drag another view controller out to the storyboard. Set it up just like FourViewController except call it FiveViewController .

Screenshot 2014-09-13 17.04.42

Add a label with the text View Controller Five in the new scene. Make a segue with an identifier five by control-dragging from ViewController to FiveViewController. Add a switch to the content view as in the illustration,

2015-09-17_06-11-17

Hook up an outlet by control dragging from the switch to the ViewController class in the assistant editor.

@IBOutlet weak var fourFiveSwitch: UISwitch!
Change the code for the fourButton() method to the following:
@IBAction func fourButton(sender: UIButton){
        if fourFiveSwitch.on{
            performSegueWithIdentifier("four", sender: self)
        }else{
            performSegueWithIdentifier("five", sender: self)
        }
    }

Build and run. Tap the four button and then hit the back button in the navigation bar.  Now change the switch setting and tap the Four again.

Closing a View Controller

Up to now, we’ve relied on the navigation controller’s back button. While I may not use pushViewController() a lot, I do use popViewController() often. Almost every delegate between view controllers in a navigation stack will use it. There are several versions of the controller and I wanted to explore them with you.
In the TwoViewController class, change the backButton code to read:

 @IBAction func backButton(sender: UIButton) {
        navigationController?.popViewControllerAnimated(true)
    }

Now do the same in ThreeViewController:

@IBAction func backButton(sender: UIButton) {
        navigationController?.popViewControllerAnimated(true)
    }

Also in ThreeViewController, Let’s add to the rootButton() method the following.

@IBAction func rootButton(sender: UIButton) {
        navigationController?.popToRootViewControllerAnimated(true)
    }

Build and Run. you will find the Next and Root button now take you around the application.
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.

Moving Values

So far we have not moved values between view controllers. If using a storyboard and segues with a performSegueWithIdentifier(), It is the same way we have already talked about for segues and storyboard. If we don’t use segues, it is rather simple,  Be careful how you use it, since it is easy to break MVC.
Let’s start by changing the view controller class we have written to this:

class ViewController: UIViewController {
    var vcCount = 0
    @IBAction func nextButton(sender: UIButton) {
        let vc = TwoViewController(nibName: "TwoViewController", bundle: nil)
        vc.vcCount = vcCount++
        navigationController?.pushViewController(vc, animated: true)
    }
    @IBOutlet weak var fourFiveSwitch: UISwitch!
    @IBAction func fourButton(sender: UIButton){
        if fourFiveSwitch.on{
            performSegueWithIdentifier("four", sender: self)
        }else{
            performSegueWithIdentifier("five", sender: self)
        }
    }
}

We assign an integer property vcCount a value of zero in line 2. Line 5 increments the count, then sends that to the property of the TwoViewController instance we created in line 4. Change TwoViewController to this:

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

    override func viewDidLoad() {
        super.viewDidLoad()
        print("\(vcCount) ")
    }
}

On line 2, we added a integer property vcCount. In viewDidLoad(), we printed the property to the console.

Using the Delegate

We can send data to other controllers now, but we need delegates to return it from a popped controller to the next visible controller. The process is the same as discussed in my delegates post, except there is no prepareForSegue() In the ViewTwoController.swift file, make a protocol

protocol TwoVCDelegate{
    func didFinishTwoVC(controller:TwoViewController)
}

We will need to add the delegate to TwoViewController:

var delegate:TwoVCDelegate! = nil

We’ll use the protocol by changing our backButton method in TwoViewController to:

@IBAction func backButton(sender: UIButton) {
        //navigationController?.popViewControllerAnimated(true)
        delegate.didFinishTwoVC(self)
    }

Our final steps are to adopt the protocol in ViewController, by changing the code like this:

 class ViewController: UIViewController,TwoVCDelegate {
    var vcCount:Int = 0
    func didFinishTwoVC(controller: TwoViewController) {
        vcCount = controller.vcCount + 1
        controller.navigationController?.popViewControllerAnimated(true)
    }
    @IBAction func nextButton(sender: UIButton) {
        let vc = TwoViewController(nibName: "TwoViewController", bundle: nil)
       vc.vcCount = vcCount
       vc.delegate = self
        navigationController?.pushViewController(vc, animated: true)
    }
    @IBOutlet weak var fourFiveSwitch: UISwitch!
    @IBAction func fourButton(sender: UIButton){
        if fourFiveSwitch.on{
            performSegueWithIdentifier("four", sender: self)
        }else{
            performSegueWithIdentifier("five", sender: self)
        }
    }
}

Line 1 adopts the delegate, and lines 3-6 is the required method didFinishTwoVC. In that method we take the current value in the vcCount property in TwoViewController and increment it, then pop the controller off the stack. In line 10, we added the assignment of the TwoViewController‘s delegate to our current view controller.

One More Thing: Setting the Navigation Title Bar

We have been using the console to tell us the value of vcCount in the view controllers. We can also place it in the title of the navigation bar. Change the viewDidLoad() in TwoViewController to this:

override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = "Count: \(vcCount)"
    }

All you need to do to change the navigation bar title is assign a string to navigationItem.title. The navigationItem is a property of type UINavigationItem, and has a property to quickly change the title of the bar. There is a lot you can change in the UINavigationItem , but that is a topic for another post.
Also change the vcCount declaration to this in ViewController:

var vcCount:Int = 0{
        didSet{
            navigationItem.title = "Count: \(vcCount)"
        }
    }

We cannot use viewDidload() in this case since the root view controller will load only once. We could have used viewWillAppear() instead, but we can also use the property observer feature of Swift. Using didSet, any time vcCount changes, the title changes with it. Build and run. Go back and forth from view one to view two.

Screenshot 2014-09-13 17.15.04

The Whole Code

//
//  ViewController.swift
//  SwiftNavControllerDemo
//
//  Created by Steven Lipton on 9/10/14.
//  Copyright (c) 2014 MakeAppPie.Com. All rights reserved.
//

import UIKit

class ViewController: UIViewController,TwoVCDelegate {
    var vcCount:Int = 0{
        didSet{
        navigationItem.title = "Count: \(vcCount)"
        }
    }
    func didFinishTwoVC(controller: TwoViewController) {
        vcCount = controller.vcCount + 1
        controller.navigationController?.popViewControllerAnimated(true)
    }
    @IBAction func nextButton(sender: UIButton) {
        let vc = TwoViewController(nibName: "TwoViewController", bundle: nil)
        vc.vcCount = vcCount
        vc.delegate = self

        navigationController?.pushViewController(vc, animated: true)
    }
    @IBOutlet weak var fourFiveSwitch: UISwitch!
    @IBAction func fourButton(sender: UIButton){
        if fourFiveSwitch.on{
            performSegueWithIdentifier("four", sender: self)
        }else{
            performSegueWithIdentifier("five", sender: self)
        }
    }
}

//
//  TwoViewController.swift
//  SwiftNavControllerDemo
//
//  Created by Steven Lipton on 9/10/14.
//  Copyright (c) 2014 MakeAppPie.Com. All rights reserved.
//

import UIKit
protocol TwoVCDelegate{
    func didFinishTwoVC(controller:TwoViewController)
}

class TwoViewController: UIViewController {
    var vcCount:Int = 0
    @IBAction func nextButton(sender: UIButton) {
        let vc = ThreeViewController(nibName: "ThreeViewController", bundle: nil)
        navigationController?.pushViewController(vc, animated: true )
    }
    var delegate:TwoVCDelegate!=nil
    @IBAction func backButton(sender: UIButton) {
        //navigationController?.popViewControllerAnimated(true)
        delegate.didFinishTwoVC(self)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = "Count: \(vcCount)"
    }

}

//
//  ThreeViewController.swift
//  SwiftNavControllerDemo
//
//  Created by Steven Lipton on 9/11/14.
//  Copyright (c) 2014 MakeAppPie.Com. All rights reserved.
//

import UIKit

class ThreeViewController: UIViewController {

    @IBAction func rootButton(sender: UIButton) {
        navigationController?.popToRootViewControllerAnimated(true)
    }
    @IBAction func backButton(sender: UIButton) {
        navigationController?.popViewControllerAnimated(true)
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = "Three"
        // Do any additional setup after loading the view.
    }

   }
//
//  FourViewController.swift
//  SwiftNavControllerDemo
//
//  Created by Steven Lipton on 9/11/14.
//  Copyright (c) 2014 MakeAppPie.Com. All rights reserved.
//

import UIKit

class FourViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = "Four" //another example of a bar title
    }

}
//
//  FiveViewController.swift
//  SwiftNavControllerDemo
//
//  Created by Steven Lipton on 9/11/14.
//  Copyright (c) 2014 MakeAppPie.Com. All rights reserved.
//

import UIKit

class FiveViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = "Five" //another example of a bar title
    }
}

25 Replies to “Swift Swift: Programmatic Navigation View Controllers in Swift”

  1. The section

    @IBAction func nextButton(sender: AnyObject) {
    let vc = TwoViewController(nibName:”TwoViewController”, bundle: nil)
    vc.vcCount = self.vcCount
    vc.delegate = self
    navigationController?.pushViewController(vc, animated: true)

    }

    ist not compatible with 6.1 Seed 2. The error message “Type “ViewController” does not conform to protocoll “TwoVCDelegate”

    1. I’ll investigate this today. Just as a first step, you do have this method in the ViewController class and thus implemented the protocol, correct?

      func didFinishTwoVC(controller: TwoViewController) {
              self.vcCount = controller.vcCount + 1
              controller.navigationController?.popViewControllerAnimated(true)
          }
      
    2. I just updated to Xcode 6.1 seed 2. I am not seeing any problems. The error message you report “Type “ViewController” does not conform to protocol “TwoVCDelegate” is Xcode’s classic complaint there is no implementation of required methods in this class that adopted the protocol. Make sure you have this in your code in ViewController, and check for spelling errors or changes of type in the first line.

      func didFinishTwoVC(controller: TwoViewController) {
              self.vcCount = controller.vcCount + 1
              controller.navigationController?.popViewControllerAnimated(true)
          }
      

      If that is the case, check the protocol matches the function:

      protocol TwoVCDelegate{
          func didFinishTwoVC(controller: TwoViewController)
      }
      

      these two have to match. If they do not you will get that error.

  2. Thank you so much for the tutorial and for helping me to solidify my (beginner) understanding of IOS. I have gone through the tutorial to the point where I have set up the first three view controllers with the navigation controller. For some reason — when I click the next button in the second controller, my navigation controller directly moves to third view controller(but doesn’t stay on that view) then immediately moves back to the second view. Any ideas why this could be occurring?

    1. Make sure the popviewcontroller in the third controller is in the method for backButton and not in view did load. Also check if there is a push of a second view controller somewhere in the third view controller. A way to test this is to hit the back button when you get your strange behavior. Where does it go? if you get back to the first view controller, you got an extra pop you shouldn’t have or is in the wrong place. If you go back to the third or second view controller. You got a push you shouldnt have or a push in the wrong place.

      1. Thank you for this! Also there are a few missing pieces in your tutorial not explained (that do appear in “The Whole Code” section)
        1. var delegate:TwoVCDelegate! = nil;
        This is never explicitly added to the ViewTwoController class until “the whole code” section. If added during the tutorial would clarify things for future tutorial users.
        2.
        The backButton action for TwoViewController is never explicitly changed in the tutorial. You do say the following:
        ————————————————
        In our case we’ll use the protocol by changing our backButton method in TwoViewController to:
        @IBAction func backButton(sender: UIButton) {
        navigationController?.popViewControllerAnimated(true)
        }
        You probably meant to add this instead:
        @IBAction func backButton(sender: UIButton) {
        //navigationController?.popViewControllerAnimated(true)
        delegate.didFinishTwoVC(self)
        }

        Just wanted to give you a heads up to help future tutorial users.

        Thanks my man for this tutorial!
        Alex

  3. i got this error while click on TwoViewController back button
    fatal error: unexpectedly found nil while unwrapping an Optional value

  4. Great article – just what I was looking for. Quick question though, we created FourViewController and FiveViewController in storyboard and never made a swift file. I saw the swift file for Four and Five in “The Whole Code” so I made a Cocoa Touch Class for FourViewController and FiveViewController and added in the code…but my nav title doesn’t change on FourViewController or FiveViewController (I copied the code for these two pages). My storyboard VC’s have the correct name and the Cocoa touch class were a subclass of UIViewController. Any ideas?

    1. The way the storyboard is set up, you don’t need FourViewController or FiveViewController. That was a little extra code for a sneak peek at another lesson ( you can find that lesson here BTW: ) Both run on segues and since they don’t do anything, don’t need UIViewController subclasses assigned — at least for a demo like this. You can set in the Identity Inspector both to UIVIewController instead of FourViewController or FiveViewController respectively. They should work showing different labels with four and five. If they don’t it’s probably something to do with fourFiveSwitch. You can also check that link above for more information on the navigation bar.

  5. The article is great, just what I needed, however it use is limited because the coding examples are being truncated as follows:
    1
    2
    let vc = TwoViewController(nibName: “TwoViewController”, bundle: nil)
    navigationController?.pushViewController(vc, animated: true)

  6. I’m trying to navigate from a view to another, I used your code but it’s no working:

    let vc = Inicio(nibName: “Inicio”, bundle: nil)
    self.navigationController?.pushViewController(vc, animated: true)

    1. Since I have no error messages to go on and you are using a view controller class Inicio I didn’t write, all I can suggest is check that your Xib is identified correctly to the controller and that everything is in the main bundle.

  7. There are so many errors … the “whole code” section is missing tons of items.
    On the individual steps, you refer to view controllers but neglect to mention which exact VC you’re referring to. On VC 4, you have directions that indicate dragging from the View controller icon at the top of VC 4, to the body of VC 4.

    This article of your’s clearly took a lot of time and effort and you undoubtedly know what you’re talking about.. just seems like some oversight.

    1. Thank you. I’ll get onto correcting this. I think a few more illustrations might help too. THis will be the first post re-worked after the WWDC16 keynote. I’m planning to do a major round of updates as soon as I have some idea of what is getting updated. It seems silly to do a lot right now but I’ll see what I can do.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s