Delegate Pattern L3.3.1

Delegate Pattern

A delegate is an object that executes a group of methods on behalf of another object. The ability to reuse views without having to subclass and modify them is important. Going deeper we want all views to be used as is with controller and model classes having the freedom to customize those views.

Example questions the view can ask the delegate

  • What should I do with these new characters in the text field?
  • How should I respond when the return button is clicked?
  • What should happen when editing begins?

A view's delegate will most likely be a control object. The pattern makes sense because a control objects are designed for tasks like passing user input to a data model.

The key to the delegate pattern is that the view establishes the questions that is needs answered and encodes them in a protocol.

Protocol

A protocol is a list of methods that a delegate must implement. Any object that fulfills the protocol can become a delegate.

Analogies to Protocols in the Physical World

There are interesting ways that connections in real life mirror delegate protocols.

  • Human Ear - The sounds involved in speech can be received by any ear that is tuned to the frequency range of the speaker.
  • Electric Plug - Any object that implements a standard electric plug protocol can receive electricity from a corresponding socket. Note that different regions of the world implement the electric plug protocol in different ways, but they all share basic components.

Object Diagram and View Controllers

Each UITextfield has it's own delegate including the third one which uses the view controller as it's delegate. The reason we would want to use a view controller as a delegate is because the view controllers role is to manage all of the views.

object diagram

Delegate Control Flow

object diagram

  1. User taps the keyboard
  2. UITextField realizes text will change
  3. UITextField invokestextField(_:shouldChangeCharactersIn:replacementString:) method
  4. View controller received the invocation from textField(_:shouldChangeCharactersIn:replacementString:)
  5. View controller assembles the new text
  6. View controller updates the label
  7. View controller returns true to allow the change

The first three step in the control flow happen behind the scenes, as we can't see where the UITextField delegate method invocations begin, but rather handle the invocations as they come in.

The first step in the control flow we can see is step 4 when the invocation comes in from the text field. So the function call is step 4, the var newText line is step 5, self.characterCountLabel.text starts step 6, and we end with step 7 on the last line.

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

    // Figure out what the new text will be, if we return true
    var newText = textField.text! as NSString
    newText = newText.replacingCharacters(in: range, with: string) as NSString

    // hide the label if the newText will be an empty string
    self.characterCountLabel.isHidden = (newText.length == 0)

    // Write the length of newText into the label
    self.characterCountLabel.text = String(newText.length)

    // returning true gives the text field permission to change its text
    return true;
}

First Responder

Any time a keyboard starts editing it becomes and is known as the "First Responder". That's why touches to the keyboard only show up on the current text view instead of all of them.

Resigning the "First Responder" status, will dismiss the keyboard

func textFieldShouldReturn(textField: UITextField) -> Bool {  
  textField.resignFirstResponder()

  return true;
}

Delegate Example

Here is a delegate used to randomly change the color of the text as a user types into the text field

import Foundation  
import UIKit

class RandomColorTextFieldDelegate: NSObject, UITextFieldDelegate {

    let colors: [UIColor] = [.red, .orange, .yellow, .green, .blue, .purple, .brown]

    func randomColor() -> UIColor {
        let randomIndex = Int(arc4random() % UInt32(colors.count))

        return colors[randomIndex]
    }

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

        textField.textColor = randomColor()

        return true;
    }
}