Skip to content

Delegates and Protocols Communication Pattern in iOS

Overview

The Delegates and Protocols pattern is a fundamental design pattern in iOS development, particularly within UIKit, for enabling communication between objects. It allows one object (the delegate) to act on behalf of, or in coordination with, another object, facilitating loose coupling and modular code. This document explains the concept, its implementation, and provides practical examples using Swift.

What are Delegates and Protocols?

  • Protocol: A blueprint of methods, properties, and other requirements that a conforming type must implement. In Swift, protocols define a contract for communication without specifying the implementation.
  • Delegate: An object that conforms to a protocol and handles tasks or events on behalf of another object. The delegate pattern is a one-to-one communication mechanism.
  • Purpose: This pattern enables objects to communicate without tightly coupling them, promoting reusability and flexibility.

Key Characteristics

  • Loose Coupling: The delegating object doesn't need to know the concrete type of the delegate, only that it conforms to the protocol.
  • Event-Driven: Commonly used for handling user interactions, lifecycle events, or data updates.
  • Common in UIKit: Used extensively in components like UITableView, UITextField, and UIAlertController.

How Delegates and Protocols Work

  1. Define a Protocol: Declare a protocol with methods or properties that the delegate must implement.
  2. Adopt the Protocol: A class, struct, or enum adopts the protocol by implementing its requirements.
  3. Set the Delegate: The delegating object holds a reference to the delegate (usually a weak reference to avoid retain cycles).
  4. Call Delegate Methods: The delegating object invokes protocol methods on the delegate when specific events occur.

Implementing the Pattern

Step 1: Define a Protocol

Protocols are defined using the protocol keyword. Methods can be required or optional (using @objc protocols for optional methods).

swift
protocol DataEntryDelegate: AnyObject {
    func didEnterData(_ data: String)
    func didCancel()
}
  • AnyObject ensures only class types can conform, enabling weak references.
  • Methods like didEnterData and didCancel define the communication contract.

Step 2: Create a Delegating Object

The delegating object declares a weak delegate property to avoid retain cycles and calls delegate methods when needed.

swift
class DataEntryViewController: UIViewController {
    weak var delegate: DataEntryDelegate?
    
    @objc func saveButtonTapped() {
        delegate?.didEnterData("Sample Data")
    }
    
    @objc func cancelButtonTapped() {
        delegate?.didCancel()
    }
}

Step 3: Adopt the Protocol

The delegate (e.g., another view controller) adopts the protocol and implements its methods.

swift
class ParentViewController: UIViewController, DataEntryDelegate {
    func didEnterData(_ data: String) {
        print("Received data: \(data)")
    }
    
    func didCancel() {
        print("Data entry cancelled")
    }
}

Step 4: Set the Delegate

The parent object sets itself as the delegate of the delegating object.

swift
let dataEntryVC = DataEntryViewController()
dataEntryVC.delegate = self // ParentViewController

Practical Example: Custom Alert with Delegate

Below is a complete example of a custom alert view controller using the delegate pattern to communicate user actions.

swift
import UIKit

// Step 1: Define the protocol
protocol CustomAlertDelegate: AnyObject {
    func alertDidConfirm()
    func alertDidCancel()
}

// Step 2: Create the delegating view controller
class CustomAlertViewController: UIViewController {
    weak var delegate: CustomAlertDelegate?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }
    
    private func setupUI() {
        view.backgroundColor = .white
        
        let confirmButton = UIButton(type: .system)
        confirmButton.setTitle("Confirm", for: .normal)
        confirmButton.addTarget(self, action: #selector(confirmTapped), for: .touchUpInside)
        confirmButton.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(confirmButton)
        
        let cancelButton = UIButton(type: .system)
        cancelButton.setTitle("Cancel", for: .normal)
        cancelButton.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)
        cancelButton.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(cancelButton)
        
        // Auto Layout
        NSLayoutConstraint.activate([
            confirmButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            confirmButton.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -20),
            cancelButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            cancelButton.topAnchor.constraint(equalTo: confirmButton.bottomAnchor, constant: 20)
        ])
    }
    
    @objc private func confirmTapped() {
        delegate?.alertDidConfirm()
        dismiss(animated: true)
    }
    
    @objc private func cancelTapped() {
        delegate?.alertDidCancel()
        dismiss(animated: true)
    }
}

// Step 3: Adopt the protocol in a parent view controller
class MainViewController: UIViewController, CustomAlertDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        let showAlertButton = UIButton(type: .system)
        showAlertButton.setTitle("Show Alert", for: .normal)
        showAlertButton.addTarget(self, action: #selector(showAlert), for: .touchUpInside)
        showAlertButton.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(showAlertButton)
        
        NSLayoutConstraint.activate([
            showAlertButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            showAlertButton.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
    
    @objc private func showAlert() {
        let alertVC = CustomAlertViewController()
        alertVC.delegate = self
        present(alertVC, animated: true)
    }
    
    // Step 4: Implement delegate methods
    func alertDidConfirm() {
        print("User confirmed the alert")
    }
    
    func alertDidCancel() {
        print("User cancelled the alert")
    }
}

How It Works

  • Tapping "Show Alert" in MainViewController presents CustomAlertViewController.
  • The alert has "Confirm" and "Cancel" buttons that trigger delegate methods.
  • The delegate (MainViewController) handles the events and prints messages to the console.

UIKit Examples of Delegate Pattern

  • UITableViewDelegate: Handles table view events like row selection (tableView(_:didSelectRowAt:)).
  • UITextFieldDelegate: Manages text field interactions, e.g., textFieldShouldReturn(_:).
  • UIScrollViewDelegate: Responds to scroll events, e.g., scrollViewDidScroll(_:).

Example with UITextField:

swift
class TextInputViewController: UIViewController, UITextFieldDelegate {
    let textField = UITextField()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        textField.delegate = self
        textField.placeholder = "Enter text"
        textField.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(textField)
        
        NSLayoutConstraint.activate([
            textField.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            textField.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
    
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        print("Text entered: \(textField.text ?? "")")
        return true
    }
}

Best Practices

  • Use Weak References: Always declare the delegate property as weak to prevent retain cycles.
  • Clear Protocol Naming: Name protocols descriptively, e.g., CustomAlertDelegate instead of Delegate.
  • Optional Methods: Use @objc protocols with optional for non-mandatory methods, but prefer required methods for clarity.
  • Type Safety: Use AnyObject in protocol definitions to ensure delegate compatibility with UIKit’s class-based system.
  • Documentation: Clearly document the purpose of each delegate method.

Delegates vs. Other Patterns

  • Closures: Suitable for simple, one-off callbacks but can lead to retain cycles if not handled carefully.
  • Notifications: Better for broadcasting events to multiple observers but less structured than delegates.
  • When to Use Delegates: Ideal for one-to-one communication, especially in UIKit, where objects need to respond to specific events.

Resources

Conclusion

The Delegates and Protocols pattern is a cornerstone of iOS development, enabling modular and maintainable communication between objects. By defining clear protocols and using the delegate pattern, you can build flexible UIKit-based apps that handle user interactions and events efficiently. Start with simple delegate implementations and explore UIKit’s built-in delegate protocols to deepen your understanding.

Released under the MIT License.