The delegate pattern is a cornerstone of iOS development in UIKit, enabling a class to delegate specific tasks to another object. This pattern promotes clean and modular code by adhering to the single responsibility principle, making your apps easier to maintain and extend. If you’ve ever worked with UITableView
, UICollectionView
, or UITextField
, you’ve already encountered this powerful design pattern.
In this blog, we’ll explore the delegate pattern in-depth, understand its advantages, and walk through an example implementation.
What Is the Delegate Pattern?
The delegate pattern is a behavioral design pattern where one object, the delegator, passes off responsibility for a specific piece of functionality to another object, the delegate. The delegator typically defines a protocol (a contract) that the delegate must conform to. This ensures that the delegate object provides the expected methods or properties.
Here’s a conceptual breakdown:
- Delegator: The object that needs help performing a task or notifying an event.
- Protocol: Defines the methods or properties that the delegate must implement.
- Delegate: The object that implements the protocol and performs the delegated tasks.
Why Use the Delegate Pattern?
The delegate pattern is widely used in UIKit because of the following advantages:
- Loose Coupling: Delegators don’t need to know about the specific class of their delegate, just that it conforms to the protocol.
- Flexibility: The delegator can work with any delegate that adheres to the protocol.
- Reusability: The same delegator class can be used in different contexts with different delegates.
- Clear Communication: The protocol ensures well-defined communication between objects.
Common Examples in UIKit
UIKit uses the delegate pattern extensively. Here are some common examples:
UITableView
andUICollectionView
: Use theUITableViewDelegate
andUICollectionViewDelegate
protocols to manage user interactions and behaviors.UITextField
andUITextView
: Use theUITextFieldDelegate
andUITextViewDelegate
protocols to handle text input events.UIScrollView
: UsesUIScrollViewDelegate
to monitor scrolling behavior.
For instance, when you create a UITableView
, you implement the UITableViewDelegate
protocol to respond to user interactions, such as row selection.
Implementing the Delegate Pattern in UIKit
Let’s walk through a simple implementation of the delegate pattern. Suppose we have a custom view ColorPickerView
, and we want to notify a delegate whenever the user selects a color.
1. Define the Protocol
Define the protocol that specifies the methods the delegate should implement:
protocol ColorPickerDelegate: AnyObject {
func didPickColor(_ color: UIColor)
}
Why
AnyObject
?
Theweak
keyword (used later) requires the delegate to be a class, andAnyObject
ensures that the protocol is only adoptable by class types.
2. Create the Delegator Class
Define the ColorPickerView
that includes a delegate property:
import UIKit
class ColorPickerView: UIView {
// The delegate property
weak var delegate: ColorPickerDelegate?
private let colors: [UIColor] = [.red, .blue, .green, .yellow]
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
private func setupView() {
let stackView = UIStackView(frame: self.bounds)
stackView.axis = .horizontal
stackView.distribution = .fillEqually
// Create buttons for each color
for color in colors {
let button = UIButton()
button.backgroundColor = color
button.addTarget(self, action: #selector(colorSelected(_:)), for: .touchUpInside)
stackView.addArrangedSubview(button)
}
self.addSubview(stackView)
}
@objc private func colorSelected(_ sender: UIButton) {
guard let color = sender.backgroundColor else { return }
// Notify the delegate
delegate?.didPickColor(color)
}
}
In this class:
- The
delegate
is a weak reference to avoid retain cycles. - The
didPickColor(_:)
method is called when a color is selected, and it informs the delegate.
3. Implement the Delegate
In your view controller, adopt the ColorPickerDelegate
protocol and set the delegate property of ColorPickerView
:
import UIKit
class ViewController: UIViewController, ColorPickerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let colorPicker = ColorPickerView(frame: CGRect(
x: 0,
y: 100,
width: self.view.bounds.width,
height: 50
)
)
colorPicker.delegate = self
self.view.addSubview(colorPicker)
}
// Conform to the protocol
func didPickColor(_ color: UIColor) {
self.view.backgroundColor = color
}
}
Here:
- The
ViewController
implements theColorPickerDelegate
protocol. - The
didPickColor(_:)
method updates the background color of the view when a color is selected.
4. Test the Implementation
When you run the app and select a color in the ColorPickerView
, the didPickColor(_:)
method in the ViewController
is called, changing the background color of the screen.
Best Practices
- Use
weak
for Delegates: Always declare delegate properties asweak
to prevent retain cycles. - Document Protocols Clearly: Clearly document the purpose of each method in the protocol for better maintainability.
- Prefer Protocols Over Subclassing: When adding functionality to a class, use the delegate pattern instead of subclassing for better flexibility.
Conclusion
The delegate pattern is a powerful tool in UIKit, enabling smooth communication between objects while maintaining loose coupling. By defining clear protocols, you can create reusable and modular components that integrate seamlessly into your app. Whether you’re building a simple custom view or working with UIKit’s prebuilt components, mastering the delegate pattern will make you a more proficient iOS developer.
Try incorporating the delegate pattern in your next project and see how it simplifies your codebase!