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.
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.
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.
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.
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.
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.
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!
- SwiftCreate iOS Apps With Swift 3
- iOSGo Further With Swift: Animation, Networking, and Custom Controls
- SwiftCode a Side-Scrolling Game With Swift 3 and SpriteKit
- iOS SDKThe Right Way to Share State Between Swift View Controllers