Make App Pie

Training for Developers and Artists

Using Segues and Delegates for Navigation Controllers in Swift 4.0

[Updated 2/13/2018 to Xcode 9 / Swift 4.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.

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.  Change the font to System Black 26point. Drag a bar button item to the right side of the navigation bar. Change its title to Color. It should look something like this.

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.

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. Make their background colors red, green and blue.  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:

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 button, and control drag it into FooTwoViewController. Make an action for a UIButton called colorSelectionButton.

2016-06-27_05-59-48

Place your mouse cursor over the circle in front of the color selection button. A plus should appear Drag from that plus to the green button to connect it. Do the same for the blue button.

Close the assistant editor. Go to FooTwoViewController Above the colorLabel outlet add a string variable colorString:

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

In the colorSelectionButton method add this code:

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

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: Any) {
  
    }
    
    @IBAction func colorSelectionButton(_ sender: UIButton) {
        colorString = (sender.titleLabel?.text)!
        colorLabel.text = colorString
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        colorLabel.text = colorString
    }

}

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

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

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        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.destination 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. destination is of type UIViewController. 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.

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 didSelectColor(controller:FooTwoViewController,text:String)
}

This sets up the protocol FooTwoViewControllerDelegate with a required method didSelectColor. 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!.didSelectColor(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?.didSelectColor(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.didSelectColor(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 didSelectColor 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:

We need to write the protocol. An easy way to have all the required protocols added for you is to click the Fix button.

Click the button, and you get this under the class declaration.

func didSelectColor(controller: FooTwoViewController, text: String) {
    code
}

I don’t like my delegate methods on the top of my class, but the bottom. Select and cut this stub. Below the prepare(for segue:_, add a MARK: comment for delegates, and paste the code in

//MARK: - Delegates
func didSelectColor(controller: FooTwoViewController, text: String) {
    code    
}

I’ll do three things in this delegate. first I’ll change colorLabel‘s text:

func didSelectColor(controller: FooTwoViewController, text: String) {
        colorLabel.text = "The Color is " + text

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. I can use this to change the color for the label text too

case "Red":
    colorLabel.textColor = .red
case "Blue":
     colorLabel.textColor = .blue
case "Green":
     colorLabel.textColor = .green
default:
     colorLabel.textColor  = .black
}

The third thing we can do is use the controller parameter to dismiss the controller with popViewControllerAnimated.

controller.navigationController?.popViewController(animated: true)

I use this sometimes if I know I’m dismissing immediately from the destination controller. If I’m not, and have some housekeeping to do, I’ll just transfer the data in the delegate and dismiss from the destination controller, in this case fooTwo.

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. It is also 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: Any?) {
        if segue.identifier == "mySegue"{
            let vc = segue.destination 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.

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

Below you’ll find the complete listing. You’ll find the completed code over on gitHub here:

FooOneViewController.swift

//
//  FooOneViewController.swift
//  DelegateExample
//
//  Created by Steven Lipton on 2/13/18.
//  Copyright © 2018 Steven Lipton. All rights reserved.
//

import UIKit

class FooOneViewController: UIViewController,FooTwoViewControllerDelegate {
    
    

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

        // Do any additional setup after loading the view.
    }
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "mySegue"{
            let vc = segue.destination as! FooTwoViewController
            vc.colorString = colorLabel.text!
            vc.delegate = self
        }
    }
    //MARK: - Delegates
    func didSelectColor(controller: FooTwoViewController, text: String) {
        colorLabel.text = "The Color is " + text
        switch text{
        case "Red":
            colorLabel.textColor = .red
        case "Blue":
            colorLabel.textColor = .blue
        case "Green":
            colorLabel.textColor = .green
        default:
            colorLabel.textColor  = .black
        }
        controller.navigationController?.popViewController(animated: true)
    }
}

FooTwoViewController.swift

//
//  FooTwoViewController.swift
//  DelegateExample
//
//  Created by Steven Lipton on 2/13/18.
//  Copyright © 2018 Steven Lipton. All rights reserved.
//

import UIKit
protocol FooTwoViewControllerDelegate{
    func didSelectColor(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: Any) {
        guard let delegate = self.delegate else {
            print("Delegate for FooTwoDelegateController not set")
            return
        }
        delegate.didSelectColor(controller: self, text: colorString)
    }
    
    @IBAction func colorSelectionButton(_ sender: UIButton) {
        colorString = (sender.titleLabel?.text)!
        colorLabel.text = colorString
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        colorLabel.text = colorString
    }

}

24 responses to “Using Segues and Delegates for Navigation Controllers in Swift 4.0”

  1. […] Click here for the Swift 3.0 version of this post 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. Swift does streamline the process a bit, but much of the setup is the same as Objective-C. Today we’ll discuss this often used technique. […]

  2. Oh, I like the font. Can tell me the name of the font in Xcode?

  3. 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.

  4. […] 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 […]

  5. 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.

  6. 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.

    1. You forgot vc.delegate=self.

  7. 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.

  8. It works – if I change Bar Buttons with ordinary buttons. All my bar buttons appear att the bottom of the view and then are not visible at all after build. Any clues?

  9. Great post man! I had to tweak a bunch of things to make it work. But those challenges just made the effort much more fulfilling, GREAT!

    1. I’m glad. I still have to look as to why you needed to tweak it in the first place.

  10. 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’

    1. did I do that again!?!? silly me.

  11. When you want to send data from ChildViewController to ParentViewcontroller, you have to do with delegation as above described. Nice tutorials,

  12. Wow, this just help me solve a bug I have been working of for about 4 hours. Thanks so much for this example.

  13. […] Using Segues and Delegates for Navigation Controllers in Swift 3.0   (Swift 3 ) by Steven Lipton on June 27, 2016 for Making App Pie. Although this was an older tutorial, I still managed to complete the tutorial with Xcode 9 and Swift 4.1. […]

    1. It’s no longer an older tutorial. Updated it today. You’ll also find a downloadable copy of the finished project at GitHub here: https://github.com/MakeAppPie/DelegateExample

Leave a Reply to Daniel Cancel 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 )

Facebook photo

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

Connecting to %s

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

%d bloggers like this: