3️⃣Delegation

Fall 2023 | Vin Bui

This is one of the most important concepts to understand regarding iOS development using UIKit, and is one of concepts that students in the past have struggled with the most. Please read this section over and over again until you understand this concept. Feel free to ask the course staff if you are still confused.

The main purpose of the delegate pattern is to allow a child to communicate back with its parent without the child knowing its parent’s type. This makes it much easier to maintain and write reusable code. Delegation is a 1:1 relationship with a child and its parent.

Protocols

To implement delegation in Swift, we use protocols. According to Swift’s official documentation,

A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements. Any type that satisfies the requirements of a protocol is said to conform to that protocol.

In other words, protocols are a set of properties and methods that classes must implement when conforming to it (similar to interfaces in Java).

protocol SomeProtocol: AnyObject {
    // Protocol definition here
}

You can learn more about protocols here, but for our purpose, we will only need to define functions inside of the protocol. For example, If we wanted to create a protocol so that a child can tell its parent to update some text, we could do the following:

protocol UpdateTextDelegate: AnyObject {
    func updateText(newText: String)
}

Make sure to conform the protocol to AnyObject!

It’s convention to name our protocol with its purpose followed by the word Delegate at the end. We also created a function called updateText that takes in a string called newText. Now, if we had a class called ParentViewController and we wanted to conform to this protocol, we could do the following:

// Include `UpdateTextDelegate` in the class header
class ParentViewController: UIViewController, UpdateTextDelegate {
    // Class definition here

    // Must implement the function `updateText`
    func updateText(newText: String) {
        // Update some text given `newText`
    }
}

Now, it’s common to use an extension to implement these functions to keep our code a lot more neat:

// Remove `UpdateTextDelegate` from the class header
class ParentViewController: UIViewController {
    // Class definition here
}

// Add `UpdateTextDelegate`
extension ParentViewController: UpdateTextDelegate {
    func updateText(newText: String) {
        // Update some text given `newText`
    }
}

The only changes that we made was removing UpdateTextDelegate from the original class header and moved it to the extension header, followed by the function implementation required by the protocol.

Delegate vs Delegator

In the code above, we conformed ParentViewController to the UpdateTextDelegate protocol. The class ParentViewController is known as the delegate. The delegate is the class that conforms to that protocol.

Now, if we had a view controller called ChildViewController that wants to communicate back with the parent, then this view controller is known as the delegator. The delegator is the class that wants the delegate to do something by calling the delegate.

We’ve looked at the code required by the delegate, but what about the delegator? Inside of ChildViewController, we would create a property whose type will be UpdateTextDelegate:

class ChildViewController: UIViewController {
    // Class definition here

    // Create the property
    // If we make this private, an initializer is required
    weak var updateTextDelegate: UpdateTextDelegate?
}

How does this child class know who the delegate is? The parent class would need to specify that it itself is the delegate:

// Assume this class conforms to `UpdateTextDelegate`
class ParentViewController: UIViewController {
    // Class definition here

    // When we create an instance to `ChildViewController`, pass in
    // `self` as the delegate
    
    private func pushChildVC() {
        let childVC = ChildViewController()
        childVC.updateTextDelegate = self
        navigationController?.pushViewController(childVC, animated: true)

        // If the property `updateTextDelegate` is private, we will need
        // an initializer in `ChildViewController` that initializes it
	// and use this line instead
        let childVC = ChildViewController(updateTextDelegate: self)
    }
}

Now, once the child has a reference to its parent (specifically, a weak reference, will explain this in another chapter), we can now call that function:

class ChildViewController: UIViewController {
    // Class definition here

    // Create the property
    // If we make this private, an initializer is required
    weak var updateTextDelegate: UpdateTextDelegate?

    // Communicate with parent
    private func communicateBack() {
        let newString = "Here you go parent"
	updateTextDelegate.updateText(newText: newString)
    }
}

Note: In the example code above, we created the functions pushChildVC and communicateBack. These functions could be anything. What’s important is the code inside of the function.

Putting everything together

There was a lot of code and it could be quite difficult to wrap our head around, so let's put everything together.

There are two classes: ParentViewController (delegate) and ChildViewController (delegator). ParentViewController is the delegate so it conforms to the protocol, meaning that it is required to implement the functions and properties defined by that protocol. When ParentViewController creates the ChildViewController, it will need to tell it that it itself is the delegate. We do this by creating a property in ChildViewController containing the reference of the delegate (which is ParentViewController). To communicate from the child to the parent, the child calls the function defined in the protocol using the delegate property that was created earlier. This child could then pass in whatever it wants to the function (since it’s the delegator) and the parent (the delegate) will use whatever the child passed in and do whatever it needs to do.

Let’s take a real life example to understand this better. Say we went to a bar to have a couple of drinks. The bar contains a menu, a bartender, and a customer. The menu is the protocol, the bartender is the delegate, and the customer is the delegator. The bartender must conform to the menu. In other words, the bartender can only make drinks that are on the menu. But how does the bartender know what drinks to make? Well, the customer (delegator), tells the bartender (delegate) what drinks to make. In other words, the customer cannot make the drinks themself but requires the bartender to do it for them.

Last updated