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.
Delegate Control Flow
- User taps the keyboard
- UITextField realizes text will change
- UITextField invokes
textField(_:shouldChangeCharactersIn:replacementString:)
method - View controller received the invocation from
textField(_:shouldChangeCharactersIn:replacementString:)
- View controller assembles the new text
- View controller updates the label
- 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;
}
}