Using Segues and Delegates for Navigation Controllers in Swift 3.0

It should be one of the easiest things we do, and yet for many it is the most confusing. Getting data from one view controller to another as you switch views never seems easy. Segues might be slightly difficult, but delegates to get the information back have given many a developer a headache or two. In this lesson, we’ll discuss this often used technique. This lesson will make a new small app which is nothing but a segue, a delegate, and a few controls to prove they work.

Make a New App

The First Scene

Make a new single-view Swift project named DelegateExample. In the storyboard click on the view controller, and embed it in a navigation controller by selecting Editor>Embed In> Navigation Controller.

2016-06-27_05-19-05

Press Command-N to make a new file.  Select a Cocoa Touch Class for the file and name the file FooOneViewController with UIViewController as a subclass:

2016-06-27_05-27-35

Go back to the storyboard, select the view controller and change the class to FooOneViewController in the identity inspector

Add a label in the center of the view, centered and with the text Unknown Color. Drag a bar button item to the right side of the navigation bar. Change its title to Color. It should look something like this.

2016-06-27_05-36-13

Open the assistant editor. In the view controller, remove everything but the viewDidLoad() method. Control-drag  connect an outlet for the Unknown Color label called colorLabel. Your code should look like this:

class FooOneViewController: UIViewController {

    @IBOutlet weak var colorLabel: UILabel!
    override func viewDidLoad() {
        super.viewDidLoad()
    }

}

The Second Scene

Like our first controller, Press Command-N and make a new Cocoa Touch class that subclasses UIViewController. Call it FooTwoViewController. In the code, remove everything but the viewDidLoad() method.

Go back to the storyboard and drag in another view controller. From the Foo One Navigation bar, control-drag the Color Bar button item to the new controller to connect the segue. Set the segue to show.

2016-06-27_05-44-53

In the attributes inspector set the identifier to mySegue.

2016-06-27_05-47-23

Select the new view controller again in the storyboard. Once we’ve set the segue, in the identity inspector set the custom class for this view controller to be FooTwoViewController like we did the first class

Now we’ll add a label, a navigation item, a bar button item, and three buttons. First add the label and place it towards the center of the view. Make the label the width of the view. Place the three buttons under the label. Title the buttons Red, Green and Blue respectively. Drag a navigation item into the view and in the label, title it Foo Two. Finally add the bar button item to the navigation bar. Your finished view should look like this:

2016-06-27_05-53-43

Open the assistant editor if not already open. Control-drag the label into the FooTwoViewController class. Create an outlet named colorLabel. Select the bar button, and control-drag it to the class code.  Make an action named saveColor.

2016-06-27_05-57-36

Select the Red, Green and Blue buttons, and control drag them into FooTwoViewController. Make an action for a UIButton called colorSelectionButton.

2016-06-27_05-59-48

In the colorSelectionButton method add this code:

@IBAction func colorSelectionButton(_ sender: UIButton) {
        colorLabel.text = sender.titleLabel!.text!
    }

Above the colorLabel outlet add a string variable colorString:

var colorString = "I don't know the color"

Finally, add this to viewDidLoad() to set the label correctly from the property:

colorLabel.text = colorString

Your code for FooTwoViewController should look like this:

import UIKit
class FooTwoViewController: UIViewController {
    var colorString = "I don't know the color"
    @IBOutlet weak var colorLabel: UILabel!
    @IBAction func saveColor(_ sender: UIBarButtonItem) {
    }
    @IBAction func colorSelectionButton(_ sender: UIButton) {
        colorLabel.text = sender.titleLabel!.text!
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        colorLabel.text = colorString
    }
  }

Build and Run. Tap the color button, and make sure in Foo Two you can tap the red, green and blue buttons and see the color name in the label. If you can’t stop the simulator and go to the assistant editor. Place your mouse cursor over the plus in front of the color selection button. The three buttons should highlight like this:

2016-06-27_06-09-49

If they don’t highlight the button did not get connected. Drag from that plus to a unhighlighted button to connect it.

Adding prepare for segue

To move data from Foo One to Foo Two, we will override prepare(for segue:). If you have the assistant editor open, close it.  Go into the FooOneViewController.Swift file and under the viewDidLoad method but before the end of the class, type prepare. Xcode will show you choices in the drop down menu

2016-06-27_06-19-48

Click the selection for prepare( for segue:  on the drop down.  You’ll have this:

override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
        code
    }

Add the following code to this method so the prepare(for Segue: ) reads:

override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "mySegue"{
        let vc = segue.destinationViewController as! FooTwoViewController
        vc.colorString = colorLabel.text!
    }
}

In line 1, the first parameter is segue, which is a UIStoryboardSegue object. This is the segue we call for our transition between controllers. In line 2 we check to see if this is the correct segue. This is line of code where a lot of bugs happen. Be sure that the string you are comparing matches exactly the segue identifier set in the storyboard, otherwise the code won’t do anything. Line 3 we create vc which is the pointer to the segue’s destination view controller. destinationViewController is of type AnyObject!. Swift will only assume vc is a type UIViewController, not our specific instance of it. We need to downcast to the correct object type with the as! operator, and make vc an instance of FooTwoController. Once we do that, we can access the colorString property in line 4, sending our current text to the property in the new controller.

Build and Run. We now can send “Unknown Color” to the new controller.

2016-06-27_06-32-38

Now comes the slightly more challenging part: getting stuff back. For that we will need a delegate.

Protocols and Delegates in Swift

Delegation uses protocols. Protocols are a set of  properties and methods that while declared in one place, another class implements. They allow for a layer of abstraction. A class adopts a protocol to do something. The protocol defines what the something is.  The adopting class will have the code how it gets done.  Using delegates and protocols is one of the hardest concepts for most developers to understand. For details about how this all works, I suggest reading the lesson Why Do We Need Delegates?

We are going to set up a protocol to dismiss the Foo Two controller, and then adopt it in the main view controller. This way we can indirectly access properties of the Foo Two controller. Such an access is a delegate. In the Foo Two controller above the class definition for FooTwoViewController, add the following:

protocol FooTwoViewControllerDelegate{
    func myVCDidFinish(controller:FooTwoViewController,text:String)
}

This sets up the protocol FooTwoViewControllerDelegate with a required method myVCDidFinish(). I used an incredibly generic name here, you can of course make it more specific for readability. Next, in the FooTwoViewController class, add the following just after the class definition:

var delegate:FooTwoViewControllerDelegate? = nil

We define a delegate of optional type FooTwoViewControllerDelegate and set its value to nil. We are familiar with simple values such as a Double or Int. If we want a state for any object that has a nil state, we use an optional value. Declare an optional with a question mark after the type, as it is above. An optional value of a Double? can be nil, along with any number for example. There is a cost to this extra power. To get back that number you have to unwrap the optional value. You can use the ! or ? operator to return a value as long as the optional is non-nil. If nil, it will break program execution with a run-time error.
unexpectedly found nil while unwrapping an Optional value
Before unwrapping an optional value, it’s usually a good idea to check for nil. There are several ways of doing that. For example, we could write the saveColor method like this:

    @IBAction func saveColor(sender : UIBarButtonItem) {
        if (delegate != nil) {
            delegate!.myVCDidFinish(self, text: colorLabel!.text!)
        }
    }

This will prevent run-time errors. However there are situations where run time errors are telling you exactly what’s wrong in your application. For delegates, I often don’t check delegate for nil. If I get the nil error message, I know I didn’t set the delegate right.

@IBAction func saveColor(sender : UIBarButtonItem) {
            delegate?.myVCDidFinish(self, text: colorLabel!.text!)
 }

There is one other way to handle this situation, and that is to use the guard keyword to unwrap your optional. Add this one to our code.

@IBAction func saveColor(_ sender: UIBarButtonItem) {
    guard let delegate = self.delegate else {
        print("Delegate for FooTwoDelegateController not Set")
        return
    }
    delegate.myVCDidFinish(controller: self, text: colorLabel.text!)
}

Guard creates a new local constant called delegate that isn’t optional. If nil it runs code to print to the console we didn’t set the delegate. If not we run the myVCDidFinish method, sending to this function colorLabel.text!. As a protocol, it isn’t defined here. The adopting class defines it, so our next stop is adopting the protocol back at the original view controller.

Open the FooOneViewController.swift file. Change the class definition to:

class FooOneViewController: UIViewController,FooTwoViewControllerDelegate

You should immediately get this error:
Type ViewController does not conform to protocol FooTwoViewControllerDelegate
We need to write the protocol. On a blank line just above prepare(for segue: ), add this code:

func myVCDidFinish(controller: FooTwoViewController, text: String) {
    colorLabel.text = "The Color is " +  text
    controller.navigationController?.popViewController(animated: true)
}

This is the sneaky part. The arguments go the function are values from FooTwoViewController, but the function is in FooOneViewController. We take the value of text and place it in our label. We then use the controller to dismiss the controller with popViewControllerAnimated.(beta note: I’m getting a warning on this, but it works. I’m assuming a bug here)

Build and run. Tap Color, then Green then Save. On the Console you’ll get an error message:
Delegate for FooTwoDelegateController not Set

This is the most common error with delegates(and why I prefer a fatal error to a soft notification on the console). We need to set up the delegate in the segue. Change the prepare(for segue:), adding the highlighted line

override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "mySegue"{
            let vc = segue.destinationViewController as! FooTwoViewController
            vc.colorString = colorLabel.text!
            vc.delegate = self
        }
    }

Build and run. You should be able to send “unknown” to Foo Two. Click a color and send the color back.

2016-06-27_07-34-17

The Steps to a Delegate

Here’s a summary of this steps to setting up delegation in Navigation controllers

  1. Between two view controllers, set up a segue with a segue identifier
  2. In the source view controller, set up the prepare( for segue:) to send the destination any values necessary. Test this works.
  3. Add a protocol to the destination view controller with a method declaration.
  4. Make the protocol and optional value named delegate in the destination controller.
  5. When you are ready to dismiss the controller and send back values, call the delegate method.
  6. Adopt the protocol in the source view controller
  7. Add the required delegate method to the source view controller
  8. Add to prepare( for segue:) the statement vc.delegate=self

If you are not understanding what we did here, you might need a little help with a few background concepts of delegation, MVC and encapsulation. This is the how, not why. For the why,  read Why Do We Need Delegates

The Whole Code

FooOneViewController.swift

//
//  FooOneViewController.swift
//  DelegateExample
//
//  Created by Steven Lipton on 6/27/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit

class FooOneViewController: UIViewController,FooTwoViewControllerDelegate {

    @IBOutlet weak var colorLabel: UILabel!
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    func myVCDidFinish(controller: FooTwoViewController, text: String) {
        colorLabel.text = "The Color is " +  text
        controller.navigationController?.popViewController(animated: true)
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "mySegue"{
            let vc = segue.destinationViewController as! FooTwoViewController
            vc.colorString = colorLabel.text!
            vc.delegate = self
        }
    }

}

FooTwoViewController.swift

//
//  FooTwoViewController.swift
//  DelegateExample
//
//  Created by Steven Lipton on 6/27/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit
protocol FooTwoViewControllerDelegate{
    func myVCDidFinish(controller:FooTwoViewController,text:String)
}

class FooTwoViewController: UIViewController {
    var colorString = "I don't know the color"
    var delegate:FooTwoViewControllerDelegate? = nil
    @IBOutlet weak var colorLabel: UILabel!
    @IBAction func saveColor(_ sender: UIBarButtonItem) {
        guard let delegate = self.delegate else {
            print("Delegate for FooTwoDelegateController not Set")
            return
        }
        delegate.myVCDidFinish(controller: self, text: colorLabel.text!)
    }
    @IBAction func colorSelectionButton(_ sender: UIButton) {
        colorLabel.text = sender.titleLabel!.text!
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        colorLabel.text = colorString
    }
  }

19 thoughts on “Using Segues and Delegates for Navigation Controllers in Swift 3.0”

  1. i noticed that if Save is clicked multiple times without a color selection, the colorLabel goes erroneously repeated as “The color is ” how come?

    1. Let me ask you a question: what argument is the delegate method getting the color from? Trace the code if there is a case where you dont press one of the three colors.

    2. Because the first time you clicks save without selecting color. Foo Two send “Unknown Color” to Foo One.

      Then you go to Foo Two again and Foo One send “The Color is Unknown Color” to Foo Two.

      You click save again. This time Foo Two send “The Color is Unknow Color” back to Foo One

      Foo One then do ‘colorLabel.text = “The Color is ” + text’ in myVCDidFinish.

      So, the text in Foo One will be The Color is + The Color is Unknown Color.

      Again and again.

  2. I think I’m beginning to understand what the delegates are for now that I’ve been through this tutorial and and your article. Many thanks.

  3. At the very end of “Adding prepare for segue” I can not seem to get Unknown Color to display on Foo Two? It just displays the initial value of colorString “I don’t know the color”

    My function:
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

    if segue.identifier == “mySegue” {
    let vc = segue.destinationViewController as! FooTwoViewController
    vc.colorString = colorLabel.text!
    }
    }

    Thanks a lot for this tutorial.

  4. I hadn’t gotten to the delegate part, I was at the part of getting “Unknown color” to display on Foo Two. Turns out I fell victim to mySegue string not matching exactly due to leaving a whitespace after the e. lol, thanks a lot for the quick response.

  5. Thanks for the clear explanation. Things are much clearer now. Some tweaks, using Xcode 8.2.1:
    – let vc = segue.destination as! FooTwoViewController
    – Attributes inspector; you talk about ‘In the property inspector set the identifier to mySegue’

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