Swift From Scratch: Delegation and Properties

In the previous lesson, you created a simple Swift project in Xcode, a basic to-do application. In this installment of Swift From Scratch, we’re going to add the ability to create to-do items. Along the way, you learn about actions, delegation, and properties.

Swift From Scratch: Delegation and Properties

Prerequisites

If you’d like to follow along with me, then make sure that you have Xcode 8.3.2 or higher installed on your machine. You can download Xcode 8.3.2 from Apple’s App Store.

1. Adding Items

At the end of this lesson, the user will be able to add new to-do items by tapping a button in the navigation bar, presenting a view with a text field and a button. Let’s start by creating the view controller that will handle adding new to-do items, the AddItemViewController class.

Step 1: Create AddItemViewController

Choose New > File… from Xcode’s File menu and select the Cocoa Touch Class template from the list of iOS > Source templates.

Swift From Scratch: Delegation and Properties

Name the class AddItemViewController and make sure it inherits from UIViewController. Double-check that Language is set to Swift and that Also create XIB file is unchecked.

Swift From Scratch: Delegation and Properties

Tell Xcode where you’d like to save the file for the AddItemViewController class and click Create.

Step 2: Add Outlets and Actions

Before we create the user interface of the AddItemViewController class, we need to create an outlet for the text field and two actions, one for a cancel button in the navigation bar and another one for the create button below the text field.

Adding an outlet should be familiar by now. Create an outlet in the AddItemViewController class and name it textField as shown below.

class AddItemViewController: UIViewController {

    @IBOutlet var textField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

}

Creating an action is very similar to creating an instance method. In fact, the @IBAction attribute is nothing more than a hint for Interface Builder. By prefixing a method with the @IBAction attribute, we ensure that Interface Builder is aware of the method, which enables us to connect it in the storyboard. We’ll leave the bodies of both actions empty for now.

class AddItemViewController: UIViewController {

    @IBOutlet var textField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    @IBAction func cancel(_ sender: Any) {

    }

    @IBAction func create(_ sender: Any) {

    }

}

Step 3: Create the User Interface

Open Main.storyboard in the Project Navigator and drag a View Controller from the Object Library on the right. With the view controller selected, open the Identity Inspector on the right and set Custom Class > Class to AddItemViewController.

To add a navigation bar to the add item view, select the Add Item View Controller and choose Embed In > Navigation Controller from the Editor menu. This will make the Add Item View Controller the root view controller of a navigation controller.

The next step is to add a bar button item to the navigation bar of the View Controller—not the Add Item View Controller—and set its Identifier to Add in the Attributes Inspector.

Swift From Scratch: Delegation and Properties

When the user taps the Add button, the Add Item View Controller should be presented modally. To accomplish this, press the Control key and drag from the Add button to the Navigation Controller, selecting Present Modally from the menu that pops up. This will create a segue from the Add Item View Controller to the new Navigation Controller.

Swift From Scratch: Delegation and Properties

Drag a text field and a button from the Object Library and add them to the Add Item View Controller scene. Select the Add Item View Controller and connect the textField outlet with the text field and the create(_:) action with the button. The create(_:) action should be triggered when the Touch Up Inside event is fired. Change the title of the button to Create and add the necessary layout constraints to the text field and button.

Swift From Scratch: Delegation and Properties

To finish the user interface, add a bar button item to the top left of the navigation bar of the Add Item View Controller and set its Identifier to Cancel. With the Add Item View Controller selected, open the Connections Inspector and connect the cancel(_:) action to the Cancel button.

Build and run the application by pressing Command-R to verify that everything is hooked up correctly.

2. Implementing a Delegate Protocol

When the user taps the Create button to add a to-do item, the add item view controller needs to notify the view controller. Delegation is a perfect solution for this scenario. The process is pretty simple.

We create a delegate protocol the ViewController class conforms to. When the AddItemViewController instance is created—when the user taps the Add button—the ViewController object is set as the delegate of the AddItemViewController instance, enabling the latter to notify the ViewController instance when a new to-do item is created. Let’s break it down to better understand this process.

Step 1: Declare the AddItemViewControllerDelegate Protocol

Open AddItemViewController.swift and declare the AddItemViewControllerDelegate protocol below the import statement at the top. The protocol declaration looks similar to a class declaration. The protocol keyword is followed by the name of the protocol.

import UIKit

protocol AddItemViewControllerDelegate {

    func controller(_ controller: AddItemViewController, didAddItem: String)

}

The concept is very similar to protocols in Objective-C. The name of the protocol is AddItemViewControllerDelegate and it defines one method, controller(_:didAddItem:).

Step 2: Declare the delegate Property

The object that needs to implement the delegate protocol is the delegate of AddItemViewController. We first need to create a property for the delegate as shown in the snippet below.

class AddItemViewController: UIViewController {

    @IBOutlet var textField: UITextField!

    var delegate: AddItemViewControllerDelegate?
    
    ...
}

The delegate property is of type AddItemViewControllerDelegate?, an optional type, since we cannot be certain that the delegate property isn’t nil. Note that the name of the protocol isn’t wrapped in angle brackets as in Objective-C.

Step 3: Implement Actions

The delegate method, controller(_:didAddItem:), will be invoked in the create(_:) action as shown below. To keep the example simple, we don’t do any validation on the user’s input.

We use optional chaining to invoke the delegate method on the delegate object, which means the delegate method is only invoked if the delegate property is set. The value of the text field is temporarily stored in a constant, item.

@IBAction func create(_ sender: Any) {
    if let item = textField.text {
        delegate?.controller(self, didAddItem: item)
    }
}

The implementation of the cancel(_:) action is easy. All we do is dismiss the AddItemViewController instance.

@IBAction func cancel(_ sender: Any) {
    dismiss(animated: true)
}

Step 4: Set the Delegate

There’s one piece of the puzzle missing though. The delegate property of the AddItemViewController instance is not being set at the moment. We can resolve this by implementing the prepare(for:sender:) method in the ViewController class. First, however, we need to revisit the storyboard.

Open Main.storyboard and select the segue connecting the Add button with the Navigation Controller. Open the Attributes Inspector and set the segue’s Identifier to AddItemViewController.

Next, implement the prepare(for:sender:) method in the ViewController class as shown below. Note the override keyword prefixing the method. This should be familiar by now.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "AddItemViewController" {
        let navigationController = segue.destination as? UINavigationController
        let addItemViewController = navigationController?.topViewController as? AddItemViewController

        if let viewController = addItemViewController {
            viewController.delegate = self
        }
    }
}

We start by checking the identifier of the segue, making sure we’re preparing for the correct segue. We then ask the segue for its destination view controller. You may expect this to be the AddItemViewController instance, but remember that we made the view controller the root view controller of a navigation controller. This means that we need to ask the navigation controller, the segue’s destination view controller, for its top view controller.

The addItemViewController constant is of type AddItemViewController? because of the use of the as? keyword. In other words, by using as? we downcast the value of the topViewController property to an optional type.

In the if statement, we unwrap the optional and set the delegate property to the ViewController instance.

I’m sure you’ve noticed the use of several optionals in the implementation of the prepare(for:sender:) method. When interacting with Objective-C APIs, it’s always better to play it safe. While sending messages to nil is perfectly fine in Objective-C, it isn’t in Swift. Because of this key difference, you always need to be careful when interacting with Objective-C APIs in Swift. The above example illustrates this well.

Step 5: Implement the AddItemViewControllerDelegate Protocol

Implementing the AddItemViewControllerDelegate protocol is similar to the implementation of the UITableViewDataSource protocol. We start by conforming the ViewController class to the protocol as shown below.

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AddItemViewControllerDelegate {
    ...
}

Next, we implement the methods of the AddItemViewControllerDelegate protocol, which boils down to implementing the controller(_:didAddItem:) method. We add the new item to the view controller’s items property, reload the table view, and dismiss the add item view controller.

// MARK: Add Item View Controller Delegate Methods

func controller(_ controller: AddItemViewController, didAddItem: String) {
    // Update Data Source
    items.append(didAddItem)

    // Reload Table View
    tableView.reloadData()

    // Dismiss Add Item View Controller
    dismiss(animated: true)
}

Step 6: Build and Run

Build and run the application to test if you can add new items to the to-do list. We currently don’t validate the user’s input. As an exercise, show an alert view to the user if they tap the Create button and the text field is empty. Adding a blank to-do isn’t very useful. Right?

Conclusion

In this lesson, you learned how to declare and implement a custom protocol. You also learned how to create actions and hook them up in Interface Builder. In the next lesson, we’re going to complete our to-do application by adding the ability to delete to-do items, and we’ll also improve the application’s user experience.

In the meantime, check out some of our other courses and tutorials about Swift language iOS development!

  • Swift From Scratch: Delegation and Properties
    Swift
    Create iOS Apps With Swift 3
    Markus Mühlberger
  • Swift From Scratch: Delegation and Properties
    iOS
    Go Further With Swift: Animation, Networking, and Custom Controls
    Markus Mühlberger
  • Swift From Scratch: Delegation and Properties
    Swift
    Code a Side-Scrolling Game With Swift 3 and SpriteKit
    Derek Jensen
  • Swift From Scratch: Delegation and Properties
    iOS SDK
    The Right Way to Share State Between Swift View Controllers
    Matteo Manferdini