The Ultimate Guide to Data Sharing Between ViewControllers in Swift 5 UIKit

The Ultimate Guide to Data Sharing Between ViewControllers in Swift 5 UIKit

Data sharing between ViewControllers is one of the most fundamental concepts in iOS development. Whether you're building a simple app or a complex enterprise solution, understanding how to effectively pass data between screens is crucial. This comprehensive guide covers every method, pattern, and technique for sharing data between ViewControllers in Swift 5 using UIKit.

Table of Contents

  1. Introduction to Data Flow
  2. Method 1: Direct Property Assignment
  3. Method 2: Segue-Based Data Passing
  4. Method 3: Delegation Pattern
  5. Method 4: Closures and Callbacks
  6. Method 5: NotificationCenter
  7. Method 6: Singleton Pattern
  8. Method 7: UserDefaults
  9. Method 8: Key-Value Observing (KVO)
  10. Method 9: Protocol-Oriented Programming
  11. Method 10: Dependency Injection
  12. Method 11: Coordinator Pattern
  13. Method 12: MVVM Architecture
  14. Method 13: Reactive Programming
  15. Advanced Patterns
  16. Best Practices

Introduction to Data Flow

Before diving into specific methods, it's essential to understand the different types of data flow in iOS applications.

Types of Data Flow

Forward Data Flow (Parent → Child)

Data flows from a source ViewController to a destination ViewController. This is the most common pattern when navigating forward in your app.

Use Cases:

  • Passing user selection to detail screen
  • Sending configuration data to new screen
  • Providing context to presented controllers

Backward Data Flow (Child → Parent)

Data flows back from a presented/pushed ViewController to the presenting/pushing ViewController. This happens when user completes an action and needs to send results back.

Use Cases:

  • Form submission results
  • User selection from picker
  • Filter/search criteria
  • Photo selection

Sibling Data Flow

Data flows between ViewControllers at the same level in the hierarchy, typically managed through a common parent or shared data source.

Use Cases:

  • Tab bar controllers sharing state
  • Split view controllers
  • Container view controllers

Navigation Patterns and Data Flow

Push Navigation (UINavigationController)

ViewController A → ViewController B → ViewController C

Data typically flows forward during push, and backward when popping.

Modal Presentation

ViewController A presents ViewController B

Can flow in both directions, but requires explicit handling.

Tab Bar Navigation

Tab 1 (VC A) ←→ Shared Data ←→ Tab 2 (VC B)

Requires shared data source or communication mechanism.

Container ViewControllers

Parent VC
  ├── Child VC 1
  ├── Child VC 2
  └── Child VC 3

Parent manages data distribution to children.


Method 1: Direct Property Assignment

Direct property assignment is the simplest and most straightforward method for passing data forward from one ViewController to another. This method is ideal for one-way data flow when presenting or pushing a new ViewController.

How It Works

When you create or access a destination ViewController, you directly set its properties before presenting or pushing it. This method is type-safe, explicit, and easy to understand.

Advantages

  • ✅ Simple and straightforward
  • ✅ Type-safe
  • ✅ Explicit and easy to understand
  • ✅ No additional dependencies
  • ✅ Compile-time checking

Disadvantages

  • ❌ Only works for forward data flow
  • ❌ Creates tight coupling between ViewControllers
  • ❌ Doesn't work for backward data flow
  • ❌ Can lead to massive ViewControllers

Implementation - Basic Example

import UIKit

// MARK: - First ViewController (Source)

class FirstViewController: UIViewController {
    
    // UI Elements
    private let nameTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Enter your name"
        textField.borderStyle = .roundedRect
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let ageTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Enter your age"
        textField.borderStyle = .roundedRect
        textField.keyboardType = .numberPad
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let emailTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Enter your email"
        textField.borderStyle = .roundedRect
        textField.keyboardType = .emailAddress
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let nextButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Next", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Enter Details"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
    }
    
    private func setupUI() {
        view.addSubview(nameTextField)
        view.addSubview(ageTextField)
        view.addSubview(emailTextField)
        view.addSubview(nextButton)
        
        NSLayoutConstraint.activate([
            nameTextField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
            nameTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            nameTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            nameTextField.heightAnchor.constraint(equalToConstant: 44),
            
            ageTextField.topAnchor.constraint(equalTo: nameTextField.bottomAnchor, constant: 20),
            ageTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            ageTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            ageTextField.heightAnchor.constraint(equalToConstant: 44),
            
            emailTextField.topAnchor.constraint(equalTo: ageTextField.bottomAnchor, constant: 20),
            emailTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            emailTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            emailTextField.heightAnchor.constraint(equalToConstant: 44),
            
            nextButton.topAnchor.constraint(equalTo: emailTextField.bottomAnchor, constant: 40),
            nextButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            nextButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            nextButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func setupActions() {
        nextButton.addTarget(self, action: #selector(nextButtonTapped), for: .touchUpInside)
    }
    
    @objc private func nextButtonTapped() {
        guard let name = nameTextField.text, !name.isEmpty,
              let ageText = ageTextField.text, let age = Int(ageText),
              let email = emailTextField.text, !email.isEmpty else {
            showAlert(message: "Please fill all fields correctly")
            return
        }
        
        // Create the destination ViewController
        let secondVC = SecondViewController()
        
        // Direct property assignment - passing data forward
        secondVC.userName = name
        secondVC.userAge = age
        secondVC.userEmail = email
        
        // Navigate to second ViewController
        navigationController?.pushViewController(secondVC, animated: true)
    }
    
    private func showAlert(message: String) {
        let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

// MARK: - Second ViewController (Destination)

class SecondViewController: UIViewController {
    
    // Properties to receive data
    var userName: String?
    var userAge: Int?
    var userEmail: String?
    
    // UI Elements
    private let nameLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 18, weight: .medium)
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let ageLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 18, weight: .medium)
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let emailLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 18, weight: .medium)
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let stackView: UIStackView = {
        let stack = UIStackView()
        stack.axis = .vertical
        stack.spacing = 20
        stack.alignment = .leading
        stack.translatesAutoresizingMaskIntoConstraints = false
        return stack
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "User Details"
        view.backgroundColor = .systemBackground
        
        setupUI()
        displayUserData()
    }
    
    private func setupUI() {
        stackView.addArrangedSubview(nameLabel)
        stackView.addArrangedSubview(ageLabel)
        stackView.addArrangedSubview(emailLabel)
        
        view.addSubview(stackView)
        
        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
            stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
        ])
    }
    
    private func displayUserData() {
        nameLabel.text = "Name: \(userName ?? "N/A")"
        ageLabel.text = "Age: \(userAge != nil ? String(userAge!) : "N/A")"
        emailLabel.text = "Email: \(userEmail ?? "N/A")"
    }
}

Advanced Implementation with Custom Models

import UIKit

// MARK: - Data Models

struct User {
    let id: UUID
    let name: String
    let age: Int
    let email: String
    let phoneNumber: String?
    let address: Address?
    let profileImage: UIImage?
    
    init(id: UUID = UUID(), name: String, age: Int, email: String, 
         phoneNumber: String? = nil, address: Address? = nil, profileImage: UIImage? = nil) {
        self.id = id
        self.name = name
        self.age = age
        self.email = email
        self.phoneNumber = phoneNumber
        self.address = address
        self.profileImage = profileImage
    }
}

struct Address {
    let street: String
    let city: String
    let state: String
    let zipCode: String
    let country: String
}

enum UserRole {
    case admin
    case moderator
    case user
    case guest
    
    var displayName: String {
        switch self {
        case .admin: return "Administrator"
        case .moderator: return "Moderator"
        case .user: return "User"
        case .guest: return "Guest"
        }
    }
}

// MARK: - User List ViewController

class UserListViewController: UIViewController {
    
    private let tableView: UITableView = {
        let table = UITableView()
        table.register(UITableViewCell.self, forCellReuseIdentifier: "UserCell")
        table.translatesAutoresizingMaskIntoConstraints = false
        return table
    }()
    
    private var users: [User] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Users"
        view.backgroundColor = .systemBackground
        
        setupUI()
        loadUsers()
    }
    
    private func setupUI() {
        view.addSubview(tableView)
        tableView.delegate = self
        tableView.dataSource = self
        
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
    
    private func loadUsers() {
        // Sample data
        users = [
            User(name: "John Doe", age: 28, email: "john@example.com", 
                 phoneNumber: "+1234567890",
                 address: Address(street: "123 Main St", city: "New York", 
                                state: "NY", zipCode: "10001", country: "USA")),
            User(name: "Jane Smith", age: 32, email: "jane@example.com",
                 phoneNumber: "+1987654321",
                 address: Address(street: "456 Oak Ave", city: "Los Angeles",
                                state: "CA", zipCode: "90001", country: "USA")),
            User(name: "Bob Johnson", age: 45, email: "bob@example.com")
        ]
        
        tableView.reloadData()
    }
    
    private func navigateToUserDetail(user: User) {
        let detailVC = UserDetailViewController()
        
        // Direct property assignment with complex model
        detailVC.user = user
        detailVC.userRole = .user
        detailVC.isEditingEnabled = true
        
        navigationController?.pushViewController(detailVC, animated: true)
    }
}

extension UserListViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return users.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell", for: indexPath)
        let user = users[indexPath.row]
        cell.textLabel?.text = user.name
        cell.accessoryType = .disclosureIndicator
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        navigateToUserDetail(user: users[indexPath.row])
    }
}

// MARK: - User Detail ViewController

class UserDetailViewController: UIViewController {
    
    // Properties to receive data
    var user: User?
    var userRole: UserRole = .user
    var isEditingEnabled: Bool = false
    
    // UI Elements
    private let scrollView: UIScrollView = {
        let scroll = UIScrollView()
        scroll.translatesAutoresizingMaskIntoConstraints = false
        return scroll
    }()
    
    private let contentView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    private let profileImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        imageView.layer.cornerRadius = 50
        imageView.backgroundColor = .systemGray5
        imageView.translatesAutoresizingMaskIntoConstraints = false
        return imageView
    }()
    
    private let nameLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 24, weight: .bold)
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let roleLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 16, weight: .medium)
        label.textColor = .systemBlue
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let infoStackView: UIStackView = {
        let stack = UIStackView()
        stack.axis = .vertical
        stack.spacing = 16
        stack.translatesAutoresizingMaskIntoConstraints = false
        return stack
    }()
    
    private let editButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Edit", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "User Details"
        view.backgroundColor = .systemBackground
        
        setupUI()
        displayUserData()
    }
    
    private func setupUI() {
        view.addSubview(scrollView)
        scrollView.addSubview(contentView)
        
        contentView.addSubview(profileImageView)
        contentView.addSubview(nameLabel)
        contentView.addSubview(roleLabel)
        contentView.addSubview(infoStackView)
        
        if isEditingEnabled {
            contentView.addSubview(editButton)
            editButton.addTarget(self, action: #selector(editButtonTapped), for: .touchUpInside)
        }
        
        NSLayoutConstraint.activate([
            scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            
            contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
            
            profileImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20),
            profileImageView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            profileImageView.widthAnchor.constraint(equalToConstant: 100),
            profileImageView.heightAnchor.constraint(equalToConstant: 100),
            
            nameLabel.topAnchor.constraint(equalTo: profileImageView.bottomAnchor, constant: 16),
            nameLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            nameLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            
            roleLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 8),
            roleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            roleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            
            infoStackView.topAnchor.constraint(equalTo: roleLabel.bottomAnchor, constant: 30),
            infoStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            infoStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20)
        ])
        
        if isEditingEnabled {
            NSLayoutConstraint.activate([
                editButton.topAnchor.constraint(equalTo: infoStackView.bottomAnchor, constant: 30),
                editButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
                editButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
                editButton.heightAnchor.constraint(equalToConstant: 50),
                editButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20)
            ])
        } else {
            infoStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20).isActive = true
        }
    }
    
    private func displayUserData() {
        guard let user = user else { return }
        
        nameLabel.text = user.name
        roleLabel.text = userRole.displayName
        profileImageView.image = user.profileImage ?? UIImage(systemName: "person.circle.fill")
        
        // Add user information
        addInfoRow(title: "Email", value: user.email)
        addInfoRow(title: "Age", value: "\(user.age) years old")
        
        if let phone = user.phoneNumber {
            addInfoRow(title: "Phone", value: phone)
        }
        
        if let address = user.address {
            let fullAddress = "\(address.street)\n\(address.city), \(address.state) \(address.zipCode)\n\(address.country)"
            addInfoRow(title: "Address", value: fullAddress)
        }
    }
    
    private func addInfoRow(title: String, value: String) {
        let container = UIView()
        container.translatesAutoresizingMaskIntoConstraints = false
        
        let titleLabel = UILabel()
        titleLabel.text = title
        titleLabel.font = .systemFont(ofSize: 14, weight: .semibold)
        titleLabel.textColor = .secondaryLabel
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        
        let valueLabel = UILabel()
        valueLabel.text = value
        valueLabel.font = .systemFont(ofSize: 16)
        valueLabel.numberOfLines = 0
        valueLabel.translatesAutoresizingMaskIntoConstraints = false
        
        container.addSubview(titleLabel)
        container.addSubview(valueLabel)
        
        NSLayoutConstraint.activate([
            titleLabel.topAnchor.constraint(equalTo: container.topAnchor),
            titleLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor),
            titleLabel.trailingAnchor.constraint(equalTo: container.trailingAnchor),
            
            valueLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4),
            valueLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor),
            valueLabel.trailingAnchor.constraint(equalTo: container.trailingAnchor),
            valueLabel.bottomAnchor.constraint(equalTo: container.bottomAnchor)
        ])
        
        infoStackView.addArrangedSubview(container)
    }
    
    @objc private func editButtonTapped() {
        guard let user = user else { return }
        
        let editVC = UserEditViewController()
        editVC.user = user
        
        navigationController?.pushViewController(editVC, animated: true)
    }
}

// MARK: - User Edit ViewController

class UserEditViewController: UIViewController {
    
    var user: User?
    
    // UI implementation similar to above
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Edit User"
        view.backgroundColor = .systemBackground
        // Setup UI for editing
    }
}

Modal Presentation with Direct Assignment

import UIKit

// MARK: - Presenting ViewController

class PresentingViewController: UIViewController {
    
    private let presentButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Present Modal", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        
        setupUI()
    }
    
    private func setupUI() {
        view.addSubview(presentButton)
        presentButton.addTarget(self, action: #selector(presentButtonTapped), for: .touchUpInside)
        
        NSLayoutConstraint.activate([
            presentButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            presentButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            presentButton.widthAnchor.constraint(equalToConstant: 200),
            presentButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    @objc private func presentButtonTapped() {
        let modalVC = ModalViewController()
        
        // Direct property assignment
        modalVC.titleText = "Welcome"
        modalVC.messageText = "This is a modal presentation"
        modalVC.buttonTitle = "Got it!"
        modalVC.presentationStyle = .fullScreen
        
        // Configure presentation style
        if modalVC.presentationStyle == .fullScreen {
            modalVC.modalPresentationStyle = .fullScreen
        } else {
            modalVC.modalPresentationStyle = .pageSheet
        }
        
        present(modalVC, animated: true)
    }
}

// MARK: - Modal ViewController

class ModalViewController: UIViewController {
    
    enum PresentationStyle {
        case fullScreen
        case pageSheet
    }
    
    // Properties to receive data
    var titleText: String?
    var messageText: String?
    var buttonTitle: String?
    var presentationStyle: PresentationStyle = .pageSheet
    
    // UI Elements
    private let titleLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 28, weight: .bold)
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let messageLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 18)
        label.textAlignment = .center
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let dismissButton: UIButton = {
        let button = UIButton(type: .system)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        
        setupUI()
        configureWithData()
    }
    
    private func setupUI() {
        view.addSubview(titleLabel)
        view.addSubview(messageLabel)
        view.addSubview(dismissButton)
        
        dismissButton.addTarget(self, action: #selector(dismissButtonTapped), for: .touchUpInside)
        
        NSLayoutConstraint.activate([
            titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 60),
            titleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            titleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            messageLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 20),
            messageLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            messageLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            dismissButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -40),
            dismissButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            dismissButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            dismissButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func configureWithData() {
        titleLabel.text = titleText ?? "Title"
        messageLabel.text = messageText ?? "Message"
        dismissButton.setTitle(buttonTitle ?? "Dismiss", for: .normal)
    }
    
    @objc private func dismissButtonTapped() {
        dismiss(animated: true)
    }
}

Method 2: Segue-Based Data Passing

Segues are a storyboard-based way to define transitions between ViewControllers. The prepare(for:sender:) method is called before a segue is performed, allowing you to configure the destination ViewController.

How It Works

When a segue is triggered (either programmatically or through storyboard connections), the prepare(for:sender:) method is called. You can identify the segue by its identifier and cast the destination ViewController to set its

Advantages

  • ✅ Swift-native and modern
  • ✅ Less boilerplate than delegates
  • ✅ Perfect for simple callbacks
  • ✅ Type-safe with generics
  • ✅ Can capture context easily
  • ✅ Inline code execution

Disadvantages

  • ❌ Can lead to retain cycles if not careful
  • ❌ Less discoverable than protocols
  • ❌ Can become complex with multiple callbacks
  • ❌ Harder to test than delegates

Basic Closure Implementation

import UIKit

// MARK: - Child ViewController with Closure

class ImagePickerViewController: UIViewController {
    
    // Define closure type alias for readability
    typealias ImageSelectionHandler = (UIImage) -> Void
    
    // Closure property
    var onImageSelected: ImageSelectionHandler?
    
    private let imageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFit
        imageView.backgroundColor = .systemGray6
        imageView.translatesAutoresizingMaskIntoConstraints = false
        return imageView
    }()
    
    private let selectButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Select Image", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    private let chooseButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Choose from Library", for: .normal)
        button.backgroundColor = .systemGreen
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Choose Image"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
    }
    
    private func setupUI() {
        view.addSubview(imageView)
        view.addSubview(selectButton)
        view.addSubview(chooseButton)
        
        NSLayoutConstraint.activate([
            imageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
            imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            imageView.heightAnchor.constraint(equalToConstant: 300),
            
            chooseButton.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 20),
            chooseButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            chooseButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            chooseButton.heightAnchor.constraint(equalToConstant: 50),
            
            selectButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
            selectButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            selectButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            selectButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func setupActions() {
        selectButton.addTarget(self, action: #selector(selectButtonTapped), for: .touchUpInside)
        chooseButton.addTarget(self, action: #selector(chooseButtonTapped), for: .touchUpInside)
    }
    
    @objc private func selectButtonTapped() {
        guard let image = imageView.image else {
            showAlert(message: "Please choose an image first")
            return
        }
        
        // Execute the closure to pass data back
        onImageSelected?(image)
        
        navigationController?.popViewController(animated: true)
    }
    
    @objc private func chooseButtonTapped() {
        let picker = UIImagePickerController()
        picker.delegate = self
        picker.sourceType = .photoLibrary
        present(picker, animated: true)
    }
    
    private func showAlert(message: String) {
        let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

extension ImagePickerViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        if let selectedImage = info[.originalImage] as? UIImage {
            imageView.image = selectedImage
        }
        dismiss(animated: true)
    }
}

// MARK: - Parent ViewController Using Closure

class MainImageViewController: UIViewController {
    
    private let displayImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFit
        imageView.backgroundColor = .systemGray6
        imageView.layer.cornerRadius = 12
        imageView.clipsToBounds = true
        imageView.translatesAutoresizingMaskIntoConstraints = false
        return imageView
    }()
    
    private let changeImageButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Change Image", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Image Display"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
    }
    
    private func setupUI() {
        view.addSubview(displayImageView)
        view.addSubview(changeImageButton)
        
        NSLayoutConstraint.activate([
            displayImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40),
            displayImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            displayImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            displayImageView.heightAnchor.constraint(equalToConstant: 300),
            
            changeImageButton.topAnchor.constraint(equalTo: displayImageView.bottomAnchor, constant: 40),
            changeImageButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            changeImageButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            changeImageButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func setupActions() {
        changeImageButton.addTarget(self, action: #selector(changeImageButtonTapped), for: .touchUpInside)
    }
    
    @objc private func changeImageButtonTapped() {
        let pickerVC = ImagePickerViewController()
        
        // Assign closure to receive data - capturing self weakly to avoid retain cycle
        pickerVC.onImageSelected = { [weak self] selectedImage in
            self?.displayImageView.image = selectedImage
            self?.saveImage(selectedImage)
            self?.showSuccessAnimation()
        }
        
        navigationController?.pushViewController(pickerVC, animated: true)
    }
    
    private func saveImage(_ image: UIImage) {
        // Save image logic
        print("Image saved successfully")
    }
    
    private func showSuccessAnimation() {
        UIView.animate(withDuration: 0.3) {
            self.displayImageView.transform = CGAffineTransform(scaleX: 1.05, y: 1.05)
        } completion: { _ in
            UIView.animate(withDuration: 0.3) {
                self.displayImageView.transform = .identity
            }
        }
    }
}

Multiple Closures for Different Events

import UIKit

// MARK: - Form ViewController with Multiple Closures

class FormViewController: UIViewController {
    
    // Multiple closure properties for different events
    var onSubmit: ((FormData) -> Void)?
    var onCancel: (() -> Void)?
    var onValidationError: ((String) -> Void)?
    var onFieldChange: ((String, Any) -> Void)?
    
    private var formData = FormData()
    
    private let nameTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Name"
        textField.borderStyle = .roundedRect
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let emailTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Email"
        textField.borderStyle = .roundedRect
        textField.keyboardType = .emailAddress
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let ageTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Age"
        textField.borderStyle = .roundedRect
        textField.keyboardType = .numberPad
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let submitButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Submit", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    private let cancelButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Cancel", for: .normal)
        button.backgroundColor = .systemGray5
        button.setTitleColor(.systemBlue, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "User Form"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
    }
    
    private func setupUI() {
        view.addSubview(nameTextField)
        view.addSubview(emailTextField)
        view.addSubview(ageTextField)
        view.addSubview(submitButton)
        view.addSubview(cancelButton)
        
        NSLayoutConstraint.activate([
            nameTextField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40),
            nameTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            nameTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            nameTextField.heightAnchor.constraint(equalToConstant: 44),
            
            emailTextField.topAnchor.constraint(equalTo: nameTextField.bottomAnchor, constant: 16),
            emailTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            emailTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            emailTextField.heightAnchor.constraint(equalToConstant: 44),
            
            ageTextField.topAnchor.constraint(equalTo: emailTextField.bottomAnchor, constant: 16),
            ageTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            ageTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            ageTextField.heightAnchor.constraint(equalToConstant: 44),
            
            submitButton.topAnchor.constraint(equalTo: ageTextField.bottomAnchor, constant: 40),
            submitButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            submitButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            submitButton.heightAnchor.constraint(equalToConstant: 50),
            
            cancelButton.topAnchor.constraint(equalTo: submitButton.bottomAnchor, constant: 12),
            cancelButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            cancelButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            cancelButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func setupActions() {
        nameTextField.addTarget(self, action: #selector(nameChanged), for: .editingChanged)
        emailTextField.addTarget(self, action: #selector(emailChanged), for: .editingChanged)
        ageTextField.addTarget(self, action: #selector(ageChanged), for: .editingChanged)
        
        submitButton.addTarget(self, action: #selector(submitButtonTapped), for: .touchUpInside)
        cancelButton.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside)
    }
    
    @objc private func nameChanged() {
        formData.name = nameTextField.text ?? ""
        onFieldChange?("name", formData.name)
    }
    
    @objc private func emailChanged() {
        formData.email = emailTextField.text ?? ""
        onFieldChange?("email", formData.email)
    }
    
    @objc private func ageChanged() {
        if let ageText = ageTextField.text, let age = Int(ageText) {
            formData.age = age
            onFieldChange?("age", age)
        }
    }
    
    @objc private func submitButtonTapped() {
        // Validate form
        if !validateForm() {
            return
        }
        
        // Execute submit closure
        onSubmit?(formData)
        
        navigationController?.popViewController(animated: true)
    }
    
    @objc private func cancelButtonTapped() {
        // Execute cancel closure
        onCancel?()
        
        navigationController?.popViewController(animated: true)
    }
    
    private func validateForm() -> Bool {
        if formData.name.isEmpty {
            onValidationError?("Name is required")
            return false
        }
        
        if formData.email.isEmpty {
            onValidationError?("Email is required")
            return false
        }
        
        if !isValidEmail(formData.email) {
            onValidationError?("Invalid email format")
            return false
        }
        
        if formData.age <= 0 {
            onValidationError?("Please enter a valid age")
            return false
        }
        
        return true
    }
    
    private func isValidEmail(_ email: String) -> Bool {
        let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
        let emailPredicate = NSPredicate(format: "SELF MATCHES %@", emailRegex)
        return emailPredicate.evaluate(with: email)
    }
}

// MARK: - Form Data Model

struct FormData {
    var name: String = ""
    var email: String = ""
    var age: Int = 0
}

// MARK: - Parent ViewController Using Multiple Closures

class FormContainerViewController: UIViewController {
    
    private let resultLabel: UILabel = {
        let label = UILabel()
        label.text = "No form submitted yet"
        label.textAlignment = .center
        label.numberOfLines = 0
        label.font = .systemFont(ofSize: 16)
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let showFormButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Show Form", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Form Manager"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
    }
    
    private func setupUI() {
        view.addSubview(resultLabel)
        view.addSubview(showFormButton)
        
        NSLayoutConstraint.activate([
            resultLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            resultLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            resultLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            resultLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            showFormButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -40),
            showFormButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            showFormButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            showFormButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func setupActions() {
        showFormButton.addTarget(self, action: #selector(showFormButtonTapped), for: .touchUpInside)
    }
    
    @objc private func showFormButtonTapped() {
        let formVC = FormViewController()
        
        // Setup all closures
        formVC.onSubmit = { [weak self] formData in
            self?.handleFormSubmission(formData)
        }
        
        formVC.onCancel = { [weak self] in
            self?.handleFormCancellation()
        }
        
        formVC.onValidationError = { [weak self] errorMessage in
            self?.handleValidationError(errorMessage)
        }
        
        formVC.onFieldChange = { [weak self] fieldName, value in
            self?.handleFieldChange(fieldName: fieldName, value: value)
        }
        
        navigationController?.pushViewController(formVC, animated: true)
    }
    
    private func handleFormSubmission(_ formData: FormData) {
        resultLabel.text = """
        Form Submitted!
        Name: \(formData.name)
        Email: \(formData.email)
        Age: \(formData.age)
        """
        
        // Save data to database
        saveFormData(formData)
        
        // Show success alert
        showSuccessAlert()
    }
    
    private func handleFormCancellation() {
        resultLabel.text = "Form submission cancelled"
        print("User cancelled the form")
    }
    
    private func handleValidationError(_ errorMessage: String) {
        // Show error alert
        let alert = UIAlertController(title: "Validation Error", message: errorMessage, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
    
    private func handleFieldChange(fieldName: String, value: Any) {
        print("Field '\(fieldName)' changed to: \(value)")
        // Could update UI or perform real-time validation
    }
    
    private func saveFormData(_ formData: FormData) {
        // Save to UserDefaults, CoreData, or send to API
        print("Saving form data: \(formData)")
    }
    
    private func showSuccessAlert() {
        let alert = UIAlertController(title: "Success", message: "Form submitted successfully!", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

Escaping and Non-Escaping Closures

import UIKit

// MARK: - Understanding Escaping Closures

class DataFetchViewController: UIViewController {
    
    // Escaping closure - executes after function returns
    var onDataFetched: ((Result<[String], Error>) -> Void)?
    
    private let activityIndicator: UIActivityIndicatorView = {
        let indicator = UIActivityIndicatorView(style: .large)
        indicator.translatesAutoresizingMaskIntoConstraints = false
        return indicator
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Fetch Data"
        view.backgroundColor = .systemBackground
        
        setupUI()
        fetchData()
    }
    
    private func setupUI() {
        view.addSubview(activityIndicator)
        
        NSLayoutConstraint.activate([
            activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
        
        activityIndicator.startAnimating()
    }
    
    private func fetchData() {
        // Simulate API call with escaping closure
        performAsyncOperation { [weak self] result in
            DispatchQueue.main.async {
                self?.activityIndicator.stopAnimating()
                
                switch result {
                case .success(let data):
                    self?.onDataFetched?(.success(data))
                case .failure(let error):
                    self?.onDataFetched?(.failure(error))
                }
                
                self?.navigationController?.popViewController(animated: true)
            }
        }
    }
    
    // Escaping closure marked with @escaping
    private func performAsyncOperation(completion: @escaping (Result<[String], Error>) -> Void) {
        DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) {
            let mockData = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"]
            completion(.success(mockData))
        }
    }
}

// MARK: - Parent Using Escaping Closure

class DataListViewController: UIViewController {
    
    private var dataItems: [String] = []
    
    private let tableView: UITableView = {
        let table = UITableView()
        table.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        table.translatesAutoresizingMaskIntoConstraints = false
        return table
    }()
    
    private let fetchButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Fetch Data", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Data List"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
    }
    
    private func setupUI() {
        view.addSubview(tableView)
        view.addSubview(fetchButton)
        
        tableView.delegate = self
        tableView.dataSource = self
        
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: fetchButton.topAnchor, constant: -16),
            
            fetchButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            fetchButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            fetchButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
            fetchButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func setupActions() {
        fetchButton.addTarget(self, action: #selector(fetchButtonTapped), for: .touchUpInside)
    }
    
    @objc private func fetchButtonTapped() {
        let fetchVC = DataFetchViewController()
        
        // Assign escaping closure
        fetchVC.onDataFetched = { [weak self] result in
            switch result {
            case .success(let data):
                self?.dataItems = data
                self?.tableView.reloadData()
                self?.showSuccessMessage()
            case .failure(let error):
                self?.showErrorAlert(error: error)
            }
        }
        
        navigationController?.pushViewController(fetchVC, animated: true)
    }
    
    private func showSuccessMessage() {
        let alert = UIAlertController(title: "Success", message: "Data fetched successfully!", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
    
    private func showErrorAlert(error: Error) {
        let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

extension DataListViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataItems.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = dataItems[indexPath.row]
        return cell
    }
}

Generic Closures for Reusability

import UIKit

// MARK: - Generic Picker ViewController

class GenericPickerViewController<T>: UIViewController where T: CustomStringConvertible {
    
    // Generic closure
    var onItemSelected: ((T) -> Void)?
    var items: [T] = []
    
    private let tableView: UITableView = {
        let table = UITableView()
        table.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        table.translatesAutoresizingMaskIntoConstraints = false
        return table
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Select Item"
        view.backgroundColor = .systemBackground
        
        setupUI()
    }
    
    private func setupUI() {
        view.addSubview(tableView)
        tableView.delegate = self
        tableView.dataSource = self
        
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
}

extension GenericPickerViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = items[indexPath.row].description
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let selectedItem = items[indexPath.row]
        onItemSelected?(selectedItem)
        navigationController?.popViewController(animated: true)
    }
}

// MARK: - Using Generic Picker

enum Priority: String, CustomStringConvertible {
    case low = "Low"
    case medium = "Medium"
    case high = "High"
    case critical = "Critical"
    
    var description: String {
        return self.rawValue
    }
}

struct Category: CustomStringConvertible {
    let id: Int
    let name: String
    let icon: String
    
    var description: String {
        return "\(icon) \(name)"
    }
}

class TaskCreatorViewController: UIViewController {
    
    private var selectedPriority: Priority = .medium
    private var selectedCategory: Category?
    
    private let priorityButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Priority: Medium", for: .normal)
        button.backgroundColor = .systemGray6
        button.setTitleColor(.label, for: .normal)
        button.layer.cornerRadius = 8
        button.contentHorizontalAlignment = .left
        button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    private let categoryButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Category: None", for: .normal)
        button.backgroundColor = .systemGray6
        button.setTitleColor(.label, for: .normal)
        button.layer.cornerRadius = 8
        button.contentHorizontalAlignment = .left
        button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Create Task"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
    }
    
    private func setupUI() {
        view.addSubview(priorityButton)
        view.addSubview(categoryButton)
        
        NSLayoutConstraint.activate([
            priorityButton.topAnchor# The Ultimate Guide to Data Sharing Between ViewControllers in Swift 5 UIKit

Data sharing between ViewControllers is one of the most fundamental concepts in iOS development. Whether you're building a simple app or a complex enterprise solution, understanding how to effectively pass data between screens is crucial. This comprehensive guide covers every method, pattern, and technique for sharing data between ViewControllers in Swift 5 using UIKit.

## Table of Contents

1. [Introduction to Data Flow](#introduction-to-data-flow)
2. [Method 1: Direct Property Assignment](#method-1-direct-property-assignment)
3. [Method 2: Segue-Based Data Passing](#method-2-segue-based-data-passing)
4. [Method 3: Delegation Pattern](#method-3-delegation-pattern)
5. [Method 4: Closures and Callbacks](#method-4-closures-and-callbacks)
6. [Method 5: NotificationCenter](#method-5-notificationcenter)
7. [Method 6: Singleton Pattern](#method-6-singleton-pattern)
8. [Method 7: UserDefaults](#method-7-userdefaults)
9. [Method 8: Key-Value Observing (KVO)](#method-8-key-value-observing-kvo)
10. [Method 9: Protocol-Oriented Programming](#method-9-protocol-oriented-programming)
11. [Method 10: Dependency Injection](#method-10-dependency-injection)
12. [Method 11: Coordinator Pattern](#method-11-coordinator-pattern)
13. [Method 12: MVVM Architecture](#method-12-mvvm-architecture)
14. [Method 13: Reactive Programming](#method-13-reactive-programming)
15. [Advanced Patterns](#advanced-patterns)
16. [Best Practices](#best-practices)

---

## Introduction to Data Flow

Before diving into specific methods, it's essential to understand the different types of data flow in iOS applications.

### Types of Data Flow

#### Forward Data Flow (Parent → Child)
Data flows from a source ViewController to a destination ViewController. This is the most common pattern when navigating forward in your app.

**Use Cases:**
- Passing user selection to detail screen
- Sending configuration data to new screen
- Providing context to presented controllers

#### Backward Data Flow (Child → Parent)
Data flows back from a presented/pushed ViewController to the presenting/pushing ViewController. This happens when user completes an action and needs to send results back.

**Use Cases:**
- Form submission results
- User selection from picker
- Filter/search criteria
- Photo selection

#### Sibling Data Flow
Data flows between ViewControllers at the same level in the hierarchy, typically managed through a common parent or shared data source.

**Use Cases:**
- Tab bar controllers sharing state
- Split view controllers
- Container view controllers

### Navigation Patterns and Data Flow

#### Push Navigation (UINavigationController)

ViewController A → ViewController B → ViewController C

Data typically flows forward during push, and backward when popping.

#### Modal Presentation

ViewController A presents ViewController B

Can flow in both directions, but requires explicit handling.

#### Tab Bar Navigation

Tab 1 (VC A) ←→ Shared Data ←→ Tab 2 (VC B)

Requires shared data source or communication mechanism.

#### Container ViewControllers

Parent VC ├── Child VC 1 ├── Child VC 2 └── Child VC 3

Parent manages data distribution to children.

---

## Method 1: Direct Property Assignment

Direct property assignment is the simplest and most straightforward method for passing data forward from one ViewController to another. This method is ideal for one-way data flow when presenting or pushing a new ViewController.

### How It Works

When you create or access a destination ViewController, you directly set its properties before presenting or pushing it. This method is type-safe, explicit, and easy to understand.

### Advantages
- ✅ Simple and straightforward
- ✅ Type-safe
- ✅ Explicit and easy to understand
- ✅ No additional dependencies
- ✅ Compile-time checking

### Disadvantages
- ❌ Only works for forward data flow
- ❌ Creates tight coupling between ViewControllers
- ❌ Doesn't work for backward data flow
- ❌ Can lead to massive ViewControllers

### Implementation - Basic Example

```swift
import UIKit

// MARK: - First ViewController (Source)

class FirstViewController: UIViewController {
    
    // UI Elements
    private let nameTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Enter your name"
        textField.borderStyle = .roundedRect
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let ageTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Enter your age"
        textField.borderStyle = .roundedRect
        textField.keyboardType = .numberPad
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let emailTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Enter your email"
        textField.borderStyle = .roundedRect
        textField.keyboardType = .emailAddress
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let nextButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Next", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Enter Details"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
    }
    
    private func setupUI() {
        view.addSubview(nameTextField)
        view.addSubview(ageTextField)
        view.addSubview(emailTextField)
        view.addSubview(nextButton)
        
        NSLayoutConstraint.activate([
            nameTextField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
            nameTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            nameTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            nameTextField.heightAnchor.constraint(equalToConstant: 44),
            
            ageTextField.topAnchor.constraint(equalTo: nameTextField.bottomAnchor, constant: 20),
            ageTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            ageTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            ageTextField.heightAnchor.constraint(equalToConstant: 44),
            
            emailTextField.topAnchor.constraint(equalTo: ageTextField.bottomAnchor, constant: 20),
            emailTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            emailTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            emailTextField.heightAnchor.constraint(equalToConstant: 44),
            
            nextButton.topAnchor.constraint(equalTo: emailTextField.bottomAnchor, constant: 40),
            nextButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            nextButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            nextButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func setupActions() {
        nextButton.addTarget(self, action: #selector(nextButtonTapped), for: .touchUpInside)
    }
    
    @objc private func nextButtonTapped() {
        guard let name = nameTextField.text, !name.isEmpty,
              let ageText = ageTextField.text, let age = Int(ageText),
              let email = emailTextField.text, !email.isEmpty else {
            showAlert(message: "Please fill all fields correctly")
            return
        }
        
        // Create the destination ViewController
        let secondVC = SecondViewController()
        
        // Direct property assignment - passing data forward
        secondVC.userName = name
        secondVC.userAge = age
        secondVC.userEmail = email
        
        // Navigate to second ViewController
        navigationController?.pushViewController(secondVC, animated: true)
    }
    
    private func showAlert(message: String) {
        let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

// MARK: - Second ViewController (Destination)

class SecondViewController: UIViewController {
    
    // Properties to receive data
    var userName: String?
    var userAge: Int?
    var userEmail: String?
    
    // UI Elements
    private let nameLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 18, weight: .medium)
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let ageLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 18, weight: .medium)
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let emailLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 18, weight: .medium)
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let stackView: UIStackView = {
        let stack = UIStackView()
        stack.axis = .vertical
        stack.spacing = 20
        stack.alignment = .leading
        stack.translatesAutoresizingMaskIntoConstraints = false
        return stack
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "User Details"
        view.backgroundColor = .systemBackground
        
        setupUI()
        displayUserData()
    }
    
    private func setupUI() {
        stackView.addArrangedSubview(nameLabel)
        stackView.addArrangedSubview(ageLabel)
        stackView.addArrangedSubview(emailLabel)
        
        view.addSubview(stackView)
        
        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
            stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
        ])
    }
    
    private func displayUserData() {
        nameLabel.text = "Name: \(userName ?? "N/A")"
        ageLabel.text = "Age: \(userAge != nil ? String(userAge!) : "N/A")"
        emailLabel.text = "Email: \(userEmail ?? "N/A")"
    }
}

Advanced Implementation with Custom Models

import UIKit

// MARK: - Data Models

struct User {
    let id: UUID
    let name: String
    let age: Int
    let email: String
    let phoneNumber: String?
    let address: Address?
    let profileImage: UIImage?
    
    init(id: UUID = UUID(), name: String, age: Int, email: String, 
         phoneNumber: String? = nil, address: Address? = nil, profileImage: UIImage? = nil) {
        self.id = id
        self.name = name
        self.age = age
        self.email = email
        self.phoneNumber = phoneNumber
        self.address = address
        self.profileImage = profileImage
    }
}

struct Address {
    let street: String
    let city: String
    let state: String
    let zipCode: String
    let country: String
}

enum UserRole {
    case admin
    case moderator
    case user
    case guest
    
    var displayName: String {
        switch self {
        case .admin: return "Administrator"
        case .moderator: return "Moderator"
        case .user: return "User"
        case .guest: return "Guest"
        }
    }
}

// MARK: - User List ViewController

class UserListViewController: UIViewController {
    
    private let tableView: UITableView = {
        let table = UITableView()
        table.register(UITableViewCell.self, forCellReuseIdentifier: "UserCell")
        table.translatesAutoresizingMaskIntoConstraints = false
        return table
    }()
    
    private var users: [User] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Users"
        view.backgroundColor = .systemBackground
        
        setupUI()
        loadUsers()
    }
    
    private func setupUI() {
        view.addSubview(tableView)
        tableView.delegate = self
        tableView.dataSource = self
        
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
    
    private func loadUsers() {
        // Sample data
        users = [
            User(name: "John Doe", age: 28, email: "john@example.com", 
                 phoneNumber: "+1234567890",
                 address: Address(street: "123 Main St", city: "New York", 
                                state: "NY", zipCode: "10001", country: "USA")),
            User(name: "Jane Smith", age: 32, email: "jane@example.com",
                 phoneNumber: "+1987654321",
                 address: Address(street: "456 Oak Ave", city: "Los Angeles",
                                state: "CA", zipCode: "90001", country: "USA")),
            User(name: "Bob Johnson", age: 45, email: "bob@example.com")
        ]
        
        tableView.reloadData()
    }
    
    private func navigateToUserDetail(user: User) {
        let detailVC = UserDetailViewController()
        
        // Direct property assignment with complex model
        detailVC.user = user
        detailVC.userRole = .user
        detailVC.isEditingEnabled = true
        
        navigationController?.pushViewController(detailVC, animated: true)
    }
}

extension UserListViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return users.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell", for: indexPath)
        let user = users[indexPath.row]
        cell.textLabel?.text = user.name
        cell.accessoryType = .disclosureIndicator
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        navigateToUserDetail(user: users[indexPath.row])
    }
}

// MARK: - User Detail ViewController

class UserDetailViewController: UIViewController {
    
    // Properties to receive data
    var user: User?
    var userRole: UserRole = .user
    var isEditingEnabled: Bool = false
    
    // UI Elements
    private let scrollView: UIScrollView = {
        let scroll = UIScrollView()
        scroll.translatesAutoresizingMaskIntoConstraints = false
        return scroll
    }()
    
    private let contentView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    private let profileImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        imageView.layer.cornerRadius = 50
        imageView.backgroundColor = .systemGray5
        imageView.translatesAutoresizingMaskIntoConstraints = false
        return imageView
    }()
    
    private let nameLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 24, weight: .bold)
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let roleLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 16, weight: .medium)
        label.textColor = .systemBlue
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let infoStackView: UIStackView = {
        let stack = UIStackView()
        stack.axis = .vertical
        stack.spacing = 16
        stack.translatesAutoresizingMaskIntoConstraints = false
        return stack
    }()
    
    private let editButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Edit", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "User Details"
        view.backgroundColor = .systemBackground
        
        setupUI()
        displayUserData()
    }
    
    private func setupUI() {
        view.addSubview(scrollView)
        scrollView.addSubview(contentView)
        
        contentView.addSubview(profileImageView)
        contentView.addSubview(nameLabel)
        contentView.addSubview(roleLabel)
        contentView.addSubview(infoStackView)
        
        if isEditingEnabled {
            contentView.addSubview(editButton)
            editButton.addTarget(self, action: #selector(editButtonTapped), for: .touchUpInside)
        }
        
        NSLayoutConstraint.activate([
            scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            
            contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
            
            profileImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20),
            profileImageView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            profileImageView.widthAnchor.constraint(equalToConstant: 100),
            profileImageView.heightAnchor.constraint(equalToConstant: 100),
            
            nameLabel.topAnchor.constraint(equalTo: profileImageView.bottomAnchor, constant: 16),
            nameLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            nameLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            
            roleLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 8),
            roleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            roleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            
            infoStackView.topAnchor.constraint(equalTo: roleLabel.bottomAnchor, constant: 30),
            infoStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            infoStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20)
        ])
        
        if isEditingEnabled {
            NSLayoutConstraint.activate([
                editButton.topAnchor.constraint(equalTo: infoStackView.bottomAnchor, constant: 30),
                editButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
                editButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
                editButton.heightAnchor.constraint(equalToConstant: 50),
                editButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20)
            ])
        } else {
            infoStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20).isActive = true
        }
    }
    
    private func displayUserData() {
        guard let user = user else { return }
        
        nameLabel.text = user.name
        roleLabel.text = userRole.displayName
        profileImageView.image = user.profileImage ?? UIImage(systemName: "person.circle.fill")
        
        // Add user information
        addInfoRow(title: "Email", value: user.email)
        addInfoRow(title: "Age", value: "\(user.age) years old")
        
        if let phone = user.phoneNumber {
            addInfoRow(title: "Phone", value: phone)
        }
        
        if let address = user.address {
            let fullAddress = "\(address.street)\n\(address.city), \(address.state) \(address.zipCode)\n\(address.country)"
            addInfoRow(title: "Address", value: fullAddress)
        }
    }
    
    private func addInfoRow(title: String, value: String) {
        let container = UIView()
        container.translatesAutoresizingMaskIntoConstraints = false
        
        let titleLabel = UILabel()
        titleLabel.text = title
        titleLabel.font = .systemFont(ofSize: 14, weight: .semibold)
        titleLabel.textColor = .secondaryLabel
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        
        let valueLabel = UILabel()
        valueLabel.text = value
        valueLabel.font = .systemFont(ofSize: 16)
        valueLabel.numberOfLines = 0
        valueLabel.translatesAutoresizingMaskIntoConstraints = false
        
        container.addSubview(titleLabel)
        container.addSubview(valueLabel)
        
        NSLayoutConstraint.activate([
            titleLabel.topAnchor.constraint(equalTo: container.topAnchor),
            titleLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor),
            titleLabel.trailingAnchor.constraint(equalTo: container.trailingAnchor),
            
            valueLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4),
            valueLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor),
            valueLabel.trailingAnchor.constraint(equalTo: container.trailingAnchor),
            valueLabel.bottomAnchor.constraint(equalTo: container.bottomAnchor)
        ])
        
        infoStackView.addArrangedSubview(container)
    }
    
    @objc private func editButtonTapped() {
        guard let user = user else { return }
        
        let editVC = UserEditViewController()
        editVC.user = user
        
        navigationController?.pushViewController(editVC, animated: true)
    }
}

// MARK: - User Edit ViewController

class UserEditViewController: UIViewController {
    
    var user: User?
    
    // UI implementation similar to above
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Edit User"
        view.backgroundColor = .systemBackground
        // Setup UI for editing
    }
}

Modal Presentation with Direct Assignment

import UIKit

// MARK: - Presenting ViewController

class PresentingViewController: UIViewController {
    
    private let presentButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Present Modal", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        
        setupUI()
    }
    
    private func setupUI() {
        view.addSubview(presentButton)
        presentButton.addTarget(self, action: #selector(presentButtonTapped), for: .touchUpInside)
        
        NSLayoutConstraint.activate([
            presentButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            presentButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            presentButton.widthAnchor.constraint(equalToConstant: 200),
            presentButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    @objc private func presentButtonTapped() {
        let modalVC = ModalViewController()
        
        // Direct property assignment
        modalVC.titleText = "Welcome"
        modalVC.messageText = "This is a modal presentation"
        modalVC.buttonTitle = "Got it!"
        modalVC.presentationStyle = .fullScreen
        
        // Configure presentation style
        if modalVC.presentationStyle == .fullScreen {
            modalVC.modalPresentationStyle = .fullScreen
        } else {
            modalVC.modalPresentationStyle = .pageSheet
        }
        
        present(modalVC, animated: true)
    }
}

// MARK: - Modal ViewController

class ModalViewController: UIViewController {
    
    enum PresentationStyle {
        case fullScreen
        case pageSheet
    }
    
    // Properties to receive data
    var titleText: String?
    var messageText: String?
    var buttonTitle: String?
    var presentationStyle: PresentationStyle = .pageSheet
    
    // UI Elements
    private let titleLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 28, weight: .bold)
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let messageLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 18)
        label.textAlignment = .center
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let dismissButton: UIButton = {
        let button = UIButton(type: .system)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        
        setupUI()
        configureWithData()
    }
    
    private func setupUI() {
        view.addSubview(titleLabel)
        view.addSubview(messageLabel)
        view.addSubview(dismissButton)
        
        dismissButton.addTarget(self, action: #selector(dismissButtonTapped), for: .touchUpInside)
        
        NSLayoutConstraint.activate([
            titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 60),
            titleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            titleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            messageLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 20),
            messageLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            messageLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            dismissButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -40),
            dismissButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            dismissButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            dismissButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func configureWithData() {
        titleLabel.text = titleText ?? "Title"
        messageLabel.text = messageText ?? "Message"
        dismissButton.setTitle(buttonTitle ?? "Dismiss", for: .normal)
    }
    
    @objc private func dismissButtonTapped() {
        dismiss(animated: true)
    }
}

Method 2: Segue-Based Data Passing

Segues are a storyboard-based way to define transitions between ViewControllers. The prepare(for:sender:) method is called before a segue is performed, allowing you to configure the destination ViewController.

How It Works

When a segue is triggered (either programmatically or through storyboard connections), the prepare(for:sender:) method is called. You can identify the segue by its identifier and cast the destination ViewController to set its properties.

Advantages

  • ✅ Visual representation in storyboard
  • ✅ Easy to set up for simple scenarios
  • ✅ Centralized data passing logic
  • ✅ Works well with Interface Builder

Disadvantages

  • ❌ Requires storyboards (not programmatic)
  • ❌ String-based identifiers (not type-safe)
  • ❌ Can become messy with many segues
  • ❌ Harder to test
  • ❌ Only for forward data flow

Implementation - Basic Segue

import UIKit

// MARK: - Source ViewController with Segue

class SegueSourceViewController: UIViewController {
    
    @IBOutlet weak var nameTextField: UITextField!
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var phoneTextField: UITextField!
    @IBOutlet weak var nextButton: UIButton!
    
    // Segue identifiers
    private enum SegueIdentifier {
        static let showDetail = "ShowDetailSegue"
        static let showSettings = "ShowSettingsSegue"
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "User Input"
    }
    
    // This method is called before any segue is performed
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Identify which segue is being performed
        if segue.identifier == SegueIdentifier.showDetail {
            // Cast destination to the correct type
            if let destinationVC = segue.destination as? SegueDestinationViewController {
                // Pass data through properties
                destinationVC.userName = nameTextField.text
                destinationVC.userEmail = emailTextField.text
                destinationVC.userPhone = phoneTextField.text
                destinationVC.timestamp = Date()
            }
        } else if segue.identifier == SegueIdentifier.showSettings {
            if let settingsVC = segue.destination as? SettingsViewController {
                settingsVC.currentUser = createUserObject()
            }
        }
    }
    
    // Validate and trigger segue programmatically
    @IBAction func nextButtonTapped(_ sender: UIButton) {
        guard validateInputs() else {
            showValidationError()
            return
        }
        
        // Perform segue with identifier
        performSegue(withIdentifier: SegueIdentifier.showDetail, sender: self)
    }
    
    private func validateInputs() -> Bool {
        guard let name = nameTextField.text, !name.isEmpty,
              let email = emailTextField.text, !email.isEmpty else {
            return false
        }
        return true
    }
    
    private func showValidationError() {
        let alert = UIAlertController(
            title: "Error",
            message: "Please fill in all required fields",
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
    
    private func createUserObject() -> User {
        return User(
            name: nameTextField.text ?? "",
            age: 0,
            email: emailTextField.text ?? ""
        )
    }
}

// MARK: - Destination ViewController

class SegueDestinationViewController: UIViewController {
    
    // Properties to receive data from segue
    var userName: String?
    var userEmail: String?
    var userPhone: String?
    var timestamp: Date?
    
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var emailLabel: UILabel!
    @IBOutlet weak var phoneLabel: UILabel!
    @IBOutlet weak var timestampLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "User Details"
        displayUserData()
    }
    
    private func displayUserData() {
        nameLabel.text = "Name: \(userName ?? "N/A")"
        emailLabel.text = "Email: \(userEmail ?? "N/A")"
        phoneLabel.text = "Phone: \(userPhone ?? "N/A")"
        
        if let timestamp = timestamp {
            let formatter = DateFormatter()
            formatter.dateStyle = .medium
            formatter.timeStyle = .short
            timestampLabel.text = "Created: \(formatter.string(from: timestamp))"
        }
    }
}

Advanced Segue Implementation with Multiple Destinations

import UIKit

// MARK: - Master View Controller with Multiple Segues

class MasterViewController: UIViewController {
    
    @IBOutlet weak var tableView: UITableView!
    
    private var products: [Product] = []
    private var selectedProduct: Product?
    
    // Segue identifiers as enum for type safety
    private enum SegueID: String {
        case showProductDetail = "ShowProductDetailSegue"
        case showProductEdit = "ShowProductEditSegue"
        case showProductReviews = "ShowProductReviewsSegue"
        case showCart = "ShowCartSegue"
        case showCheckout = "ShowCheckoutSegue"
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupTableView()
        loadProducts()
    }
    
    private func setupTableView() {
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(ProductTableViewCell.self, forCellReuseIdentifier: "ProductCell")
    }
    
    private func loadProducts() {
        products = [
            Product(id: "1", name: "iPhone 14", price: 999.99, category: .electronics),
            Product(id: "2", name: "MacBook Pro", price: 2499.99, category: .electronics),
            Product(id: "3", name: "AirPods Pro", price: 249.99, category: .electronics)
        ]
        tableView.reloadData()
    }
    
    // Centralized segue preparation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard let identifier = segue.identifier,
              let segueID = SegueID(rawValue: identifier) else {
            return
        }
        
        switch segueID {
        case .showProductDetail:
            prepareProductDetail(segue: segue, sender: sender)
        case .showProductEdit:
            prepareProductEdit(segue: segue, sender: sender)
        case .showProductReviews:
            prepareProductReviews(segue: segue, sender: sender)
        case .showCart:
            prepareCart(segue: segue, sender: sender)
        case .showCheckout:
            prepareCheckout(segue: segue, sender: sender)
        }
    }
    
    private func prepareProductDetail(segue: UIStoryboardSegue, sender: Any?) {
        if let detailVC = segue.destination as? ProductDetailViewController {
            detailVC.product = selectedProduct
            detailVC.viewMode = .readOnly
            detailVC.showReviewsButton = true
            detailVC.allowPurchase = true
        }
    }
    
    private func prepareProductEdit(segue: UIStoryboardSegue, sender: Any?) {
        if let editVC = segue.destination as? ProductEditViewController {
            editVC.product = selectedProduct
            editVC.editMode = .update
        }
    }
    
    private func prepareProductReviews(segue: UIStoryboardSegue, sender: Any?) {
        if let reviewsVC = segue.destination as? ProductReviewsViewController {
            reviewsVC.productID = selectedProduct?.id
            reviewsVC.productName = selectedProduct?.name
        }
    }
    
    private func prepareCart(segue: UIStoryboardSegue, sender: Any?) {
        if let cartVC = segue.destination as? CartViewController {
            cartVC.cartItems = CartManager.shared.items
            cartVC.totalAmount = CartManager.shared.totalAmount
        }
    }
    
    private func prepareCheckout(segue: UIStoryboardSegue, sender: Any?) {
        if let checkoutVC = segue.destination as? CheckoutViewController {
            checkoutVC.cartItems = CartManager.shared.items
            checkoutVC.subtotal = CartManager.shared.subtotal
            checkoutVC.tax = CartManager.shared.tax
            checkoutVC.shippingCost = CartManager.shared.shippingCost
            checkoutVC.total = CartManager.shared.totalAmount
        }
    }
    
    @IBAction func cartButtonTapped(_ sender: UIBarButtonItem) {
        performSegue(withIdentifier: SegueID.showCart.rawValue, sender: self)
    }
}

extension MasterViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return products.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ProductCell", for: indexPath)
        let product = products[indexPath.row]
        cell.textLabel?.text = product.name
        cell.detailTextLabel?.text = "$\(product.price)"
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        selectedProduct = products[indexPath.row]
        performSegue(withIdentifier: SegueID.showProductDetail.rawValue, sender: self)
    }
    
    func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
        selectedProduct = products[indexPath.row]
        performSegue(withIdentifier: SegueID.showProductEdit.rawValue, sender: self)
    }
}

// MARK: - Supporting Models

struct Product {
    let id: String
    let name: String
    let price: Double
    let category: ProductCategory
    var description: String?
    var imageURL: URL?
    var rating: Double?
    var reviewCount: Int?
}

enum ProductCategory {
    case electronics
    case clothing
    case books
    case home
}

class CartManager {
    static let shared = CartManager()
    var items: [CartItem] = []
    
    var subtotal: Double {
        return items.reduce(0) { $0 + ($1.product.price * Double($1.quantity)) }
    }
    
    var tax: Double {
        return subtotal * 0.08
    }
    
    var shippingCost: Double {
        return subtotal > 50 ? 0 : 5.99
    }
    
    var totalAmount: Double {
        return subtotal + tax + shippingCost
    }
}

struct CartItem {
    let product: Product
    var quantity: Int
}

Unwind Segues for Backward Data Flow

import UIKit

// MARK: - Detail ViewController

class DetailViewController: UIViewController {
    
    var selectedColor: UIColor?
    var selectedSize: String?
    var quantity: Int = 1
    
    @IBOutlet weak var colorPicker: UISegmentedControl!
    @IBOutlet weak var sizePicker: UISegmentedControl!
    @IBOutlet weak var quantityStepper: UIStepper!
    @IBOutlet weak var quantityLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func quantityChanged(_ sender: UIStepper) {
        quantity = Int(sender.value)
        quantityLabel.text = "\(quantity)"
    }
    
    @IBAction func colorChanged(_ sender: UISegmentedControl) {
        switch sender.selectedSegmentIndex {
        case 0: selectedColor = .red
        case 1: selectedColor = .blue
        case 2: selectedColor = .green
        default: selectedColor = nil
        }
    }
    
    @IBAction func sizeChanged(_ sender: UISegmentedControl) {
        switch sender.selectedSegmentIndex {
        case 0: selectedSize = "S"
        case 1: selectedSize = "M"
        case 2: selectedSize = "L"
        case 3: selectedSize = "XL"
        default: selectedSize = nil
        }
    }
    
    // This prepares data for the unwind segue
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "UnwindToMain" {
            // Data is already in properties, will be accessed by destination
        }
    }
}

// MARK: - Main ViewController (Destination of Unwind)

class MainViewController: UIViewController {
    
    @IBOutlet weak var selectedColorView: UIView!
    @IBOutlet weak var selectedSizeLabel: UILabel!
    @IBOutlet weak var selectedQuantityLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    // Unwind segue action - receives data from source
    @IBAction func unwindToMain(_ segue: UIStoryboardSegue) {
        if let sourceVC = segue.source as? DetailViewController {
            // Receive data from the unwinding ViewController
            if let color = sourceVC.selectedColor {
                selectedColorView.backgroundColor = color
            }
            
            if let size = sourceVC.selectedSize {
                selectedSizeLabel.text = "Size: \(size)"
            }
            
            selectedQuantityLabel.text = "Quantity: \(sourceVC.quantity)"
            
            // Perform additional actions with received data
            saveSelection(color: sourceVC.selectedColor,
                        size: sourceVC.selectedSize,
                        quantity: sourceVC.quantity)
        }
    }
    
    private func saveSelection(color: UIColor?, size: String?, quantity: Int) {
        // Save or process the received data
        print("Received selection: Color=\(String(describing: color)), Size=\(size ?? "N/A"), Quantity=\(quantity)")
    }
    
    // Forward segue to detail
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "ShowDetail" {
            if let detailVC = segue.destination as? DetailViewController {
                // Can pre-populate with existing data
                detailVC.selectedColor = selectedColorView.backgroundColor
                detailVC.selectedSize = extractSize(from: selectedSizeLabel.text)
                detailVC.quantity = extractQuantity(from: selectedQuantityLabel.text)
            }
        }
    }
    
    private func extractSize(from text: String?) -> String? {
        return text?.replacingOccurrences(of: "Size: ", with: "")
    }
    
    private func extractQuantity(from text: String?) -> Int {
        guard let text = text else { return 1 }
        let quantity = text.replacingOccurrences(of: "Quantity: ", with: "")
        return Int(quantity) ?? 1
    }
}

Conditional Segues with Validation

import UIKit

// MARK: - Form ViewController with Validation

class FormViewController: UIViewController {
    
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var passwordTextField: UITextField!
    @IBOutlet weak var confirmPasswordTextField: UITextField!
    @IBOutlet weak var agreeSwitch: UISwitch!
    @IBOutlet weak var submitButton: UIButton!
    
    private var validationErrors: [String] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupValidation()
    }
    
    private func setupValidation() {
        emailTextField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
        passwordTextField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
        confirmPasswordTextField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
    }
    
    @objc private func textFieldDidChange() {
        validateForm()
    }
    
    private func validateForm() {
        validationErrors.removeAll()
        
        // Email validation
        if let email = emailTextField.text, !email.isEmpty {
            if !isValidEmail(email) {
                validationErrors.append("Invalid email format")
            }
        } else {
            validationErrors.append("Email is required")
        }
        
        // Password validation
        if let password = passwordTextField.text, !password.isEmpty {
            if password.count < 8 {
                validationErrors.append("Password must be at least 8 characters")
            }
        } else {
            validationErrors.append("Password is required")
        }
        
        // Confirm password validation
        if passwordTextField.text != confirmPasswordTextField.text {
            validationErrors.append("Passwords do not match")
        }
        
        // Terms agreement
        if !agreeSwitch.isOn {
            validationErrors.append("You must agree to terms and conditions")
        }
        
        submitButton.isEnabled = validationErrors.isEmpty
    }
    
    private func isValidEmail(_ email: String) -> Bool {
        let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
        let emailPredicate = NSPredicate(format: "SELF MATCHES %@", emailRegex)
        return emailPredicate.evaluate(with: email)
    }
    
    // Override shouldPerformSegue to add validation
    override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
        if identifier == "ShowSuccessSegue" {
            validateForm()
            
            if !validationErrors.isEmpty {
                showValidationErrors()
                return false
            }
            
            return true
        }
        
        return true
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "ShowSuccessSegue" {
            if let successVC = segue.destination as? SuccessViewController {
                successVC.userEmail = emailTextField.text
                successVC.registrationDate = Date()
            }
        }
    }
    
    private func showValidationErrors() {
        let errorMessage = validationErrors.joined(separator: "\n")
        let alert = UIAlertController(
            title: "Validation Error",
            message: errorMessage,
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
    
    @IBAction func submitButtonTapped(_ sender: UIButton) {
        performSegue(withIdentifier: "ShowSuccessSegue", sender: self)
    }
}

// MARK: - Success ViewController

class SuccessViewController: UIViewController {
    
    var userEmail: String?
    var registrationDate: Date?
    
    @IBOutlet weak var messageLabel: UILabel!
    @IBOutlet weak var emailLabel: UILabel!
    @IBOutlet weak var dateLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        displaySuccess()
    }
    
    private func displaySuccess() {
        messageLabel.text = "Registration Successful!"
        emailLabel.text = "Email: \(userEmail ?? "N/A")"
        
        if let date = registrationDate {
            let formatter = DateFormatter()
            formatter.dateStyle = .long
            formatter.timeStyle = .medium
            dateLabel.text = "Date: \(formatter.string(from: date))"
        }
    }
}

Method 3: Delegation Pattern

The Delegation pattern is one of the most important design patterns in iOS development. It allows one object to send messages to another object when specific events occur. This is the primary method for backward data flow.

How It Works

  1. Define a protocol that declares methods for communication
  2. The delegating class has a weak delegate property
  3. The delegate class conforms to the protocol
  4. The delegating class calls delegate methods when needed
  5. The delegate implements the methods to handle events

Advantages

  • ✅ Perfect for backward data flow
  • ✅ Loose coupling through protocols
  • ✅ Type-safe communication
  • ✅ Clear contract between objects
  • ✅ Apple's preferred pattern

Disadvantages

  • ❌ Requires protocol definition
  • ❌ More boilerplate code
  • ❌ One-to-one relationship only
  • ❌ Must manage weak references

Basic Delegation Implementation

import UIKit

// MARK: - Step 1: Define the Protocol

protocol ColorPickerDelegate: AnyObject {
    func colorPicker(_ picker: ColorPickerViewController, didSelectColor color: UIColor)
    func colorPickerDidCancel(_ picker: ColorPickerViewController)
}

// MARK: - Step 2: Delegating ViewController (Child)

class ColorPickerViewController: UIViewController {
    
    // Weak delegate property to avoid retain cycles
    weak var delegate: ColorPickerDelegate?
    
    private var selectedColor: UIColor = .white
    
    // UI Elements
    private let colorPickerView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    private let redSlider: UISlider = {
        let slider = UISlider()
        slider.minimumValue = 0
        slider.maximumValue = 1
        slider.value = 1
        slider.translatesAutoresizingMaskIntoConstraints = false
        return slider
    }()
    
    private let greenSlider: UISlider = {
        let slider = UISlider()
        slider.minimumValue = 0
        slider.maximumValue = 1
        slider.value = 1
        slider.translatesAutoresizingMaskIntoConstraints = false
        return slider
    }()
    
    private let blueSlider: UISlider = {
        let slider = UISlider()
        slider.minimumValue = 0
        slider.maximumValue = 1
        slider.value = 1
        slider.translatesAutoresizingMaskIntoConstraints = false
        return slider
    }()
    
    private let previewView: UIView = {
        let view = UIView()
        view.layer.cornerRadius = 50
        view.layer.borderWidth = 2
        view.layer.borderColor = UIColor.systemGray.cgColor
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    private let selectButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Select Color", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    private let cancelButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Cancel", for: .normal)
        button.backgroundColor = .systemGray5
        button.setTitleColor(.systemBlue, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Choose Color"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
        updatePreview()
    }
    
    private func setupUI() {
        view.addSubview(previewView)
        view.addSubview(redSlider)
        view.addSubview(greenSlider)
        view.addSubview(blueSlider)
        view.addSubview(selectButton)
        view.addSubview(cancelButton)
        
        NSLayoutConstraint.activate([
            previewView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40),
            previewView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            previewView.widthAnchor.constraint(equalToConstant: 100),
            previewView.heightAnchor.constraint(equalToConstant: 100),
            
            redSlider.topAnchor.constraint(equalTo: previewView.bottomAnchor, constant: 40),
            redSlider.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            redSlider.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            greenSlider.topAnchor.constraint(equalTo: redSlider.bottomAnchor, constant: 20),
            greenSlider.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            greenSlider.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            blueSlider.topAnchor.constraint(equalTo: greenSlider.bottomAnchor, constant: 20),
            blueSlider.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            blueSlider.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            selectButton.bottomAnchor.constraint(equalTo: cancelButton.topAnchor, constant: -16),
            selectButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            selectButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            selectButton.heightAnchor.constraint(equalToConstant: 50),
            
            cancelButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
            cancelButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            cancelButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            cancelButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func setupActions() {
        redSlider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged)
        greenSlider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged)
        blueSlider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged)
        
        selectButton.addTarget(self, action: #selector(selectButtonTapped), for: .touchUpInside)
        cancelButton.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside)
    }
    
    @objc private func sliderValueChanged() {
        updatePreview()
    }
    
    private func updatePreview() {
        selectedColor = UIColor(
            red: CGFloat(redSlider.value),
            green: CGFloat(greenSlider.value),
            blue: CGFloat(blueSlider.value),
            alpha: 1.0
        )
        previewView.backgroundColor = selectedColor
    }
    
    @objc private func selectButtonTapped() {
        // Call delegate method to pass data back
        delegate?.colorPicker(self, didSelectColor: selectedColor)
        navigationController?.popViewController(animated: true)
    }
    
    @objc private func cancelButtonTapped() {
        // Call delegate method for cancellation
        delegate?.colorPickerDidCancel(self)
        navigationController?.popViewController(animated: true)
    }
}

// MARK: - Step 3: Delegate ViewController (Parent)

class MainColorViewController: UIViewController {
    
    private let colorDisplayView: UIView = {
        let view = UIView()
        view.backgroundColor = .white
        view.layer.cornerRadius = 20
        view.layer.borderWidth = 2
        view.layer.borderColor = UIColor.systemGray.cgColor
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    private let colorLabel: UILabel = {
        let label = UILabel()
        label.text = "No color selected"
        label.textAlignment = .center
        label.font = .systemFont(ofSize: 18, weight: .medium)
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let chooseColorButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Choose Color", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Color Manager"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
    }
    
    private func setupUI() {
        view.addSubview(colorDisplayView)
        view.addSubview(colorLabel)
        view.addSubview(chooseColorButton)
        
        NSLayoutConstraint.activate([
            colorDisplayView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40),
            colorDisplayView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            colorDisplayView.widthAnchor.constraint(equalToConstant: 200),
            colorDisplayView.heightAnchor.constraint(equalToConstant: 200),
            
            colorLabel.topAnchor.constraint(equalTo: colorDisplayView.bottomAnchor, constant: 20),
            colorLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            colorLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            chooseColorButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -40),
            chooseColorButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            chooseColorButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            chooseColorButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func setupActions() {
        chooseColorButton.addTarget(self, action: #selector(chooseColorButtonTapped), for: .touchUpInside)
    }
    
    @objc private func chooseColorButtonTapped() {
        let colorPickerVC = ColorPickerViewController()
        
        // Step 4: Set self as the delegate
        colorPickerVC.delegate = self
        
        navigationController?.pushViewController(colorPickerVC, animated: true)
    }
}

// MARK: - Step 5: Conform to Protocol and Implement Methods

extension MainColorViewController: ColorPickerDelegate {
    
    func colorPicker(_ picker: ColorPickerViewController, didSelectColor color: UIColor) {
        // Receive data from child ViewController
        colorDisplayView.backgroundColor = color
        colorLabel.text = "Color selected successfully!"
        
        // Save color to UserDefaults
        saveColor(color)
        
        // Perform additional actions
        animateColorChange()
    }
    
    func colorPickerDidCancel(_ picker: ColorPickerViewController) {
        colorLabel.text = "Color selection cancelled"
        
        // Show cancellation feedback
        showCancellationAlert()
    }
    
    private func saveColor(_ color: UIColor) {
        var red: CGFloat = 0
        var green: CGFloat = 0
        var blue: CGFloat = 0
        var alpha: CGFloat = 0
        
        color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
        
        UserDefaults.standard.set(Double(red), forKey: "savedColorRed")
        UserDefaults.standard.set(Double(green), forKey: "savedColorGreen")
        UserDefaults.standard.set(Double(blue), forKey: "savedColorBlue")
    }
    
    private func animateColorChange() {
        UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.3) {
            self.colorDisplayView.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)
        } completion: { _ in
            UIView.animate(withDuration: 0.3) {
                self.colorDisplayView.transform = .identity
            }
        }
    }
    
    private func showCancellationAlert() {
        let alert = UIAlertController(
            title: "Cancelled",
            message: "Color selection was cancelled",
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

Advanced Delegation with Multiple Data Types

import UIKit

// MARK: - Complex Protocol with Multiple Methods

protocol UserProfileEditorDelegate: AnyObject {
    func userProfileEditor(_ editor: UserProfileEditorViewController, didUpdateProfile profile: UserProfile)
    func userProfileEditor(_ editor: UserProfileEditorViewController, didUpdateAvatar image: UIImage)
    func userProfileEditor(_ editor: UserProfileEditorViewController, didChangePrivacySettings settings: PrivacySettings)
    func userProfileEditorDidRequestDelete(_ editor: UserProfileEditorViewController)
    func userProfileEditorDidCancel(_ editor: UserProfileEditorViewController)
}

// MARK: - Data Models

struct UserProfile {
    var firstName: String
    var lastName: String
    var email: String
    var phoneNumber: String?
    var bio: String?
    var dateOfBirth: Date?
    var location: String?
    var website: URL?
}

struct PrivacySettings {
    var profileVisibility: ProfileVisibility
    var showEmail: Bool
    var showPhoneNumber: Bool
    var allowMessages: Bool
    var allowFriendRequests: Bool
}

enum ProfileVisibility {
    case publicProfile
    case friendsOnly
    case privateProfile
}

// MARK: - Editor ViewController

class UserProfileEditorViewController: UIViewController {
    
    weak var delegate: UserProfileEditorDelegate?
    
    var currentProfile: UserProfile?
    var currentPrivacySettings: PrivacySettings?
    
    // UI Elements
    private let scrollView: UIScrollView = {
        let scroll = UIScrollView()
        scroll.translatesAutoresizingMaskIntoConstraints = false
        return scroll
    }()
    
    private let contentView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    private let avatarImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        imageView.layer.cornerRadius = 60
        imageView.backgroundColor = .systemGray5
        imageView.isUserInteractionEnabled = true
        imageView.translatesAutoresizingMaskIntoConstraints = false
        return imageView
    }()
    
    private let firstNameTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "First Name"
        textField.borderStyle = .roundedRect
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let lastNameTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Last Name"
        textField.borderStyle = .roundedRect
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let emailTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Email"
        textField.borderStyle = .roundedRect
        textField.keyboardType = .emailAddress
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let phoneTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Phone Number"
        textField.borderStyle = .roundedRect
        textField.keyboardType = .phonePad
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let bioTextView: UITextView = {
        let textView = UITextView()
        textView.font = .systemFont(ofSize: 16)
        textView.layer.borderColor = UIColor.systemGray4.cgColor
        textView.layer.borderWidth = 1
        textView.layer.cornerRadius = 8
        textView.translatesAutoresizingMaskIntoConstraints = false
        return textView
    }()
    
    private let profileVisibilitySegment: UISegmentedControl = {
        let segment = UISegmentedControl(items: ["Public", "Friends", "Private"])
        segment.selectedSegmentIndex = 0
        segment.translatesAutoresizingMaskIntoConstraints = false
        return segment
    }()
    
    private let showEmailSwitch: UISwitch = {
        let toggle = UISwitch()
        toggle.translatesAutoresizingMaskIntoConstraints = false
        return toggle
    }()
    
    private let showPhoneSwitch: UISwitch = {
        let toggle = UISwitch()
        toggle.translatesAutoresizingMaskIntoConstraints = false
        return toggle
    }()
    
    private let saveButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Save Changes", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    private let cancelButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Cancel", for: .normal)
        button.backgroundColor = .systemGray5
        button.setTitleColor(.systemBlue, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    private let deleteButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Delete Profile", for: .normal)
        button.backgroundColor = .systemRed
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Edit Profile"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
        populateFields()
    }
    
    private func setupUI() {
        view.addSubview(scrollView)
        scrollView.addSubview(contentView)
        
        contentView.addSubview(avatarImageView)
        contentView.addSubview(firstNameTextField)
        contentView.addSubview(lastNameTextField)
        contentView.addSubview(emailTextField)
        contentView.addSubview(phoneTextField)
        contentView.addSubview(bioTextView)
        contentView.addSubview(profileVisibilitySegment)
        contentView.addSubview(showEmailSwitch)
        contentView.addSubview(showPhoneSwitch)
        contentView.addSubview(saveButton)
        contentView.addSubview(cancelButton)
        contentView.addSubview(deleteButton)
        
        NSLayoutConstraint.activate([
            scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            
            contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
            
            avatarImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20),
            avatarImageView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            avatarImageView.widthAnchor.constraint(equalToConstant: 120),
            avatarImageView.heightAnchor.constraint(equalToConstant: 120),
            
            firstNameTextField.topAnchor.constraint(equalTo: avatarImageView.bottomAnchor, constant: 30),
            firstNameTextField.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            firstNameTextField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            firstNameTextField.heightAnchor.constraint(equalToConstant: 44),
            
            lastNameTextField.topAnchor.constraint(equalTo: firstNameTextField.bottomAnchor, constant: 16),
            lastNameTextField.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            lastNameTextField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            lastNameTextField.heightAnchor.constraint(equalToConstant: 44),
            
            emailTextField.topAnchor.constraint(equalTo: lastNameTextField.bottomAnchor, constant: 16),
            emailTextField.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            emailTextField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            emailTextField.heightAnchor.constraint(equalToConstant: 44),
            
            phoneTextField.topAnchor.constraint(equalTo: emailTextField.bottomAnchor, constant: 16),
            phoneTextField.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            phoneTextField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            phoneTextField.heightAnchor.constraint(equalToConstant: 44),
            
            bioTextView.topAnchor.constraint(equalTo: phoneTextField.bottomAnchor, constant: 16),
            bioTextView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            bioTextView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            bioTextView.heightAnchor.constraint(equalToConstant: 100),
            
            profileVisibilitySegment.topAnchor.constraint(equalTo: bioTextView.bottomAnchor, constant: 30),
            profileVisibilitySegment.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            profileVisibilitySegment.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            
            saveButton.topAnchor.constraint(equalTo: profileVisibilitySegment.bottomAnchor, constant: 40),
            saveButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            saveButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            saveButton.heightAnchor.constraint(equalToConstant: 50),
            
            cancelButton.topAnchor.constraint(equalTo: saveButton.bottomAnchor, constant: 12),
            cancelButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            cancelButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            cancelButton.heightAnchor.constraint(equalToConstant: 50),
            
            deleteButton.topAnchor.constraint(equalTo: cancelButton.bottomAnchor, constant: 30),
            deleteButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            deleteButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            deleteButton.heightAnchor.constraint(equalToConstant: 50),
            deleteButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -30)
        ])
    }
    
    private func setupActions() {
        let avatarTapGesture = UITapGestureRecognizer(target: self, action: #selector(avatarTapped))
        avatarImageView.addGestureRecognizer(avatarTapGesture)
        
        saveButton.addTarget(self, action: #selector(saveButtonTapped), for: .touchUpInside)
        cancelButton.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside)
        deleteButton.addTarget(self, action: #selector(deleteButtonTapped), for: .touchUpInside)
        
        profileVisibilitySegment.addTarget(self, action: #selector(privacySettingsChanged), for: .valueChanged)
        showEmailSwitch.addTarget(self, action: #selector(privacySettingsChanged), for: .valueChanged)
        showPhoneSwitch.addTarget(self, action: #selector(privacySettingsChanged), for: .valueChanged)
    }
    
    private func populateFields() {
        guard let profile = currentProfile else { return }
        
        firstNameTextField.text = profile.firstName
        lastNameTextField.text = profile.lastName
        emailTextField.text = profile.email
        phoneTextField.text = profile.phoneNumber
        bioTextView.text = profile.bio
        
        if let settings = currentPrivacySettings {
            switch settings.profileVisibility {
            case .publicProfile: profileVisibilitySegment.selectedSegmentIndex = 0
            case .friendsOnly: profileVisibilitySegment.selectedSegmentIndex = 1
            case .privateProfile: profileVisibilitySegment.selectedSegmentIndex = 2
            }
            
            showEmailSwitch.isOn = settings.showEmail
            showPhoneSwitch.isOn = settings.showPhoneNumber
        }
    }
    
    @objc private func avatarTapped() {
        let imagePicker = UIImagePickerController()
        imagePicker.delegate = self
        imagePicker.sourceType = .photoLibrary
        imagePicker.allowsEditing = true
        present(imagePicker, animated: true)
    }
    
    @objc private func saveButtonTapped() {
        guard validateInputs() else { return }
        
        // Create updated profile
        let updatedProfile = UserProfile(
            firstName: firstNameTextField.text ?? "",
            lastName: lastNameTextField.text ?? "",
            email: emailTextField.text ?? "",
            phoneNumber: phoneTextField.text,
            bio: bioTextView.text,
            dateOfBirth: currentProfile?.dateOfBirth,
            location: currentProfile?.location,
            website: currentProfile?.website
        )
        
        // Notify delegate about profile update
        delegate?.userProfileEditor(self, didUpdateProfile: updatedProfile)
        
        navigationController?.popViewController(animated: true)
    }
    
    @objc private func cancelButtonTapped() {
        // Notify delegate about cancellation
        delegate?.userProfileEditorDidCancel(self)
        navigationController?.popViewController(animated: true)
    }
    
    @objc private func deleteButtonTapped() {
        let alert = UIAlertController(
            title: "Delete Profile",
            message: "Are you sure you want to delete your profile? This action cannot be undone.",
            preferredStyle: .alert
        )
        
        alert.addAction(UIAlertAction(title: "Delete", style: .destructive) { [weak self] _ in
            guard let self = self else { return }
            self.delegate?.userProfileEditorDidRequestDelete(self)
            self.navigationController?.popViewController(animated: true)
        })
        
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
        
        present(alert, animated: true)
    }
    
    @objc private func privacySettingsChanged() {
        let visibility: ProfileVisibility
        switch profileVisibilitySegment.selectedSegmentIndex {
        case 0: visibility = .publicProfile
        case 1: visibility = .friendsOnly
        case 2: visibility = .privateProfile
        default: visibility = .publicProfile
        }
        
        let updatedSettings = PrivacySettings(
            profileVisibility: visibility,
            showEmail: showEmailSwitch.isOn,
            showPhoneNumber: showPhoneSwitch.isOn,
            allowMessages: currentPrivacySettings?.allowMessages ?? true,
            allowFriendRequests: currentPrivacySettings?.allowFriendRequests ?? true
        )
        
        // Notify delegate about privacy settings change
        delegate?.userProfileEditor(self, didChangePrivacySettings: updatedSettings)
    }
    
    private func validateInputs() -> Bool {
        guard let firstName = firstNameTextField.text, !firstName.isEmpty,
              let lastName = lastNameTextField.text, !lastName.isEmpty,
              let email = emailTextField.text, !email.isEmpty else {
            showAlert(message: "Please fill in all required fields")
            return false
        }
        
        return true
    }
    
    private func showAlert(message: String) {
        let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

// MARK: - UIImagePickerControllerDelegate

extension UserProfileEditorViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        if let editedImage = info[.editedImage] as? UIImage {
            avatarImageView.image = editedImage
            
            // Notify delegate about avatar change
            delegate?.userProfileEditor(self, didUpdateAvatar: editedImage)
        } else if let originalImage = info[.originalImage] as? UIImage {
            avatarImageView.image = originalImage
            delegate?.userProfileEditor(self, didUpdateAvatar: originalImage)
        }
        
        dismiss(animated: true)
    }
    
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        dismiss(animated: true)
    }
}

// MARK: - Parent ViewController Implementing Delegate

class UserProfileViewController: UIViewController {
    
    private var userProfile: UserProfile?
    private var privacySettings: PrivacySettings?
    
    private let profileImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        imageView.layer.cornerRadius = 60
        imageView.backgroundColor = .systemGray5
        imageView.translatesAutoresizingMaskIntoConstraints = false
        return imageView
    }()
    
    private let nameLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 24, weight: .bold)
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let emailLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 16)
        label.textColor = .secondaryLabel
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let editButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Edit Profile", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "My Profile"
        view.backgroundColor = .systemBackground
        
        setupUI()
        loadUserProfile()
    }
    
    private func setupUI() {
        view.addSubview(profileImageView)
        view.addSubview(nameLabel)
        view.addSubview(emailLabel)
        view.addSubview(editButton)
        
        editButton.addTarget(self, action: #selector(editButtonTapped), for: .touchUpInside)
        
        NSLayoutConstraint.activate([
            profileImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40),
            profileImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            profileImageView.widthAnchor.constraint(equalToConstant: 120),
            profileImageView.heightAnchor.constraint(equalToConstant: 120),
            
            nameLabel.topAnchor.constraint(equalTo: profileImageView.bottomAnchor, constant: 20),
            nameLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            nameLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            emailLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 8),
            emailLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            emailLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            editButton.topAnchor.constraint(equalTo: emailLabel.bottomAnchor, constant: 40),
            editButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            editButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            editButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func loadUserProfile() {
        userProfile = UserProfile(
            firstName: "John",
            lastName: "Doe",
            email: "john.doe@example.com",
            phoneNumber: "+1234567890",
            bio: "iOS Developer"
        )
        
        privacySettings = PrivacySettings(
            profileVisibility: .publicProfile,
            showEmail: true,
            showPhoneNumber: false,
            allowMessages: true,
            allowFriendRequests: true
        )
        
        updateUI()
    }
    
    private func updateUI() {
        guard let profile = userProfile else { return }
        
        nameLabel.text = "\(profile.firstName) \(profile.lastName)"
        emailLabel.text = profile.email
    }
    
    @objc private func editButtonTapped() {
        let editorVC = UserProfileEditorViewController()
        editorVC.currentProfile = userProfile
        editorVC.currentPrivacySettings = privacySettings
        
        // Set self as delegate
        editorVC.delegate = self
        
        navigationController?.pushViewController(editorVC, animated: true)
    }
}

// MARK: - Conform to Delegate Protocol

extension UserProfileViewController: UserProfileEditorDelegate {
    
    func userProfileEditor(_ editor: UserProfileEditorViewController, didUpdateProfile profile: UserProfile) {
        // Receive updated profile
        self.userProfile = profile
        updateUI()
        
        // Save to database or API
        saveProfileToServer(profile)
        
        // Show success message
        showSuccessAlert(message: "Profile updated successfully!")
    }
    
    func userProfileEditor(_ editor: UserProfileEditorViewController, didUpdateAvatar image: UIImage) {
        // Receive new avatar
        profileImageView.image = image
        
        // Upload to server
        uploadAvatarToServer(image)
    }
    
    func userProfileEditor(_ editor: UserProfileEditorViewController, didChangePrivacySettings settings: PrivacySettings) {
        // Receive privacy settings changes
        self.privacySettings = settings
        
        // Save settings
        savePrivacySettings(settings)
        
        print("Privacy settings updated: \(settings)")
    }
    
    func userProfileEditorDidRequestDelete(_ editor: UserProfileEditorViewController) {
        // Handle profile deletion request
        let alert = UIAlertController(
            title: "Confirm Deletion",
            message: "Your profile will be permanently deleted. Continue?",
            preferredStyle: .alert
        )
        
        alert.addAction(UIAlertAction(title: "Delete", style: .destructive) { [weak self] _ in
            self?.deleteProfile()
        })
        
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
        
        present(alert, animated: true)
    }
    
    func userProfileEditorDidCancel(_ editor: UserProfileEditorViewController) {
        print("Profile editing cancelled")
    }
    
    // Helper methods
    
    private func saveProfileToServer(_ profile: UserProfile) {
        // API call to save profile
        print("Saving profile to server: \(profile)")
    }
    
    private func uploadAvatarToServer(_ image: UIImage) {
        // API call to upload avatar
        print("Uploading avatar to server")
    }
    
    private func savePrivacySettings(_ settings: PrivacySettings) {
        // Save privacy settings
        print("Saving privacy settings")
    }
    
    private func deleteProfile() {
        // API call to delete profile
        print("Deleting profile")
        
        // Navigate back or logout
        navigationController?.popToRootViewController(animated: true)
    }
    
    private func showSuccessAlert(message: String) {
        let alert = UIAlertController(title: "Success", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

Optional Protocol Methods

import UIKit

// MARK: - Protocol with Optional Methods

@objc protocol FormDelegate: AnyObject {
    func formDidSubmit(_ form: FormViewController, withData data: [String: Any])
    
    // Optional methods
    @objc optional func formDidStartEditing(_ form: FormViewController)
    @objc optional func form(_ form: FormViewController, didUpdateField fieldName: String, value: Any)
    @objc optional func formDidCancel(_ form: FormViewController)
    @objc optional func form(_ form: FormViewController, didEncounterError error: Error)
}

// MARK: - Form View Controller

class FormViewController: UIViewController {
    
    weak var delegate: FormDelegate?
    
    private var formData: [String: Any] = [:]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }
    
    private func setupUI() {
        // UI setup code
    }
    
    private func notifyEditingStarted() {
        // Call optional delegate method
        delegate?.formDidStartEditing?(self)
    }
    
    private func notifyFieldUpdate(fieldName: String, value: Any) {
        formData[fieldName] = value
        
        // Call optional delegate method
        delegate?.form?(self, didUpdateField: fieldName, value: value)
    }
    
    @objc private func submitButtonTapped() {
        // Call required delegate method
        delegate?.formDidSubmit(self, withData: formData)
    }
    
    @objc private func cancelButtonTapped() {
        // Call optional delegate method
        delegate?.formDidCancel?(self)
    }
    
    private func handleError(_ error: Error) {
        // Call optional delegate method
        delegate?.form?(self, didEncounterError: error)
    }
}

// MARK: - Implementing Optional Methods

class ParentViewController: UIViewController, FormDelegate {
    
    // Required method
    func formDidSubmit(_ form: FormViewController, withData data: [String : Any]) {
        print("Form submitted with data: \(data)")
    }
    
    // Optional methods - implement only what you need
    func form(_ form: FormViewController, didUpdateField fieldName: String, value: Any) {
        print("Field \(fieldName) updated to: \(value)")
    }
    
    func formDidCancel(_ form: FormViewController) {
        print("Form cancelled")
    }
    
    // formDidStartEditing and didEncounterError are not implemented
    // No error because they're marked as @objc optional
}

Method 4: Closures and Callbacks

Closures (also known as callbacks or completion handlers) provide a modern, Swift-native way to pass data between ViewControllers. They're especially useful for asynchronous operations and simple backward data flow.

How It Works

  1. Define a closure property in the source ViewController
  2. The destination ViewController assigns a closure to this property
  3. When ready, the source ViewController executes the closure with data
  4. The destination ViewController receives and processes the data

Advantages

  • ✅ Swift-native and modern
  • ✅ Less boilerplate than delegates
  • ✅ Perfect for simple callbacks
  • ✅ Type-safe with generics
  • ✅ Can capture context easily
  • ✅ Inline code execution

Disadvantages

  • ❌ Can lead to retain cycles if not careful
  • ❌ Less discoverable than protocols
  • ❌ Can become complex with multiple callbacks
  • ❌ Harder to test than delegates

Basic Closure Implementation

import UIKit

// MARK: - Child ViewController with Closure

class ImagePickerViewController: UIViewController {
    
    // Define closure type alias for readability
    typealias ImageSelectionHandler = (UIImage) -> Void
    
    // Closure property
    var onImageSelected: ImageSelectionHandler?
    
    private let imageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFit
        imageView.backgroundColor = .systemGray6
        imageView.translatesAutoresizingMaskIntoConstraints = false
        return imageView
    }()
    
    private let selectButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Select Image", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    private let chooseButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Choose from Library", for: .normal)
        button.backgroundColor = .systemGreen
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Choose Image"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
    }
    
    private func setupUI() {
        view.addSubview(imageView)
        view.addSubview(selectButton)
        view.addSubview(chooseButton)
        
        NSLayoutConstraint.activate([
            imageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
            imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            imageView.heightAnchor.constraint(equalToConstant: 300),
            
            chooseButton.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 20),
            chooseButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            chooseButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            chooseButton.heightAnchor.constraint(equalToConstant: 50),
            
            selectButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
            selectButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            selectButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            selectButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func setupActions() {
        selectButton.addTarget(self, action: #selector(selectButtonTapped), for: .touchUpInside)
        chooseButton.addTarget(self, action: #selector(chooseButtonTapped), for: .touchUpInside)
    }
    
    @objc private func selectButtonTapped() {
        guard let image = imageView.image else {
            showAlert(message: "Please choose an image first")
            return
        }
        
        // Execute the closure to pass data back
        onImageSelected?(image)
        
        navigationController?.popViewController(animated: true)
    }
    
    @objc private func chooseButtonTapped() {
        let picker = UIImagePickerController()
        picker.delegate = self
        picker.sourceType = .photoLibrary
        present(picker, animated: true)
    }
    
    private func showAlert(message: String) {
        let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

extension ImagePickerViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        if let selectedImage = info[.originalImage] as? UIImage {
            imageView.image = selectedImage
        }
        dismiss(animated: true)
    }
}

// MARK: - Parent ViewController Using Closure

class MainImageViewController: UIViewController {
    
    private let displayImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFit
        imageView.backgroundColor = .systemGray6
        imageView.layer.cornerRadius = 12
        imageView.clipsToBounds = true
        imageView.translatesAutoresizingMaskIntoConstraints = false
        return imageView
    }()
    
    private let changeImageButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Change Image", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Image Display"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
    }
    
    private func setupUI() {
        view.addSubview(displayImageView)
        view.addSubview(changeImageButton)
        
        NSLayoutConstraint.activate([
            displayImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40),
            displayImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            displayImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            displayImageView.heightAnchor.constraint(equalToConstant: 300),
            
            changeImageButton.topAnchor.constraint(equalTo: displayImageView.bottomAnchor, constant: 40),
            changeImageButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            changeImageButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            changeImageButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func setupActions() {
        changeImageButton.addTarget(self, action: #selector(changeImageButtonTapped), for: .touchUpInside)
    }
    
    @objc private func changeImageButtonTapped() {
        let pickerVC = ImagePickerViewController()
        
        // Assign closure to receive data - capturing self weakly to avoid retain cycle
        pickerVC.onImageSelected = { [weak self] selectedImage in
            self?.displayImageView.image = selectedImage
            self?.saveImage(selectedImage)
            self?.showSuccessAnimation()
        }
        
        navigationController?.pushViewController(pickerVC, animated: true)
    }
    
    private func saveImage(_ image: UIImage) {
        // Save image logic
        print("Image saved successfully")
    }
    
    private func showSuccessAnimation() {
        UIView.animate(withDuration: 0.3) {
            self.displayImageView.transform = CGAffineTransform(scaleX: 1.05, y: 1.05)
        } completion: { _ in
            UIView.animate(withDuration: 0.3) {
                self.displayImageView.transform = .identity
            }
        }
    }
}

Multiple Closures for Different Events

import UIKit

// MARK: - Form ViewController with Multiple Closures

class FormViewController: UIViewController {
    
    // Multiple closure properties for different events
    var onSubmit: ((FormData) -> Void)?
    var onCancel: (() -> Void)?
    var onValidationError: ((String) -> Void)?
    var onFieldChange: ((String, Any) -> Void)?
    
    private var formData = FormData()
    
    private let nameTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Name"
        textField.borderStyle = .roundedRect
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let emailTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Email"
        textField.borderStyle = .roundedRect
        textField.keyboardType = .emailAddress
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let ageTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Age"
        textField.borderStyle = .roundedRect
        textField.keyboardType = .numberPad
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let submitButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Submit", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    private let cancelButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Cancel", for: .normal)
        button.backgroundColor = .systemGray5
        button.setTitleColor(.systemBlue, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "User Form"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
    }
    
    private func setupUI() {
        view.addSubview(nameTextField)
        view.addSubview(emailTextField)
        view.addSubview(ageTextField)
        view.addSubview(submitButton)
        view.addSubview(cancelButton)
        
        NSLayoutConstraint.activate([
            nameTextField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40),
            nameTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            nameTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            nameTextField.heightAnchor.constraint(equalToConstant: 44),
            
            emailTextField.topAnchor.constraint(equalTo: nameTextField.bottomAnchor, constant: 16),
            emailTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            emailTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            emailTextField.heightAnchor.constraint(equalToConstant: 44),
            
            ageTextField.topAnchor.constraint(equalTo: emailTextField.bottomAnchor, constant: 16),
            ageTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            ageTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            ageTextField.heightAnchor.constraint(equalToConstant: 44),
            
            submitButton.topAnchor.constraint(equalTo: ageTextField.bottomAnchor, constant: 40),
            submitButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            submitButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            submitButton.heightAnchor.constraint(equalToConstant: 50),
            
            cancelButton.topAnchor.constraint(equalTo: submitButton.bottomAnchor, constant: 12),
            cancelButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            cancelButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            cancelButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func setupActions() {
        nameTextField.addTarget(self, action: #selector(nameChanged), for: .editingChanged)
        emailTextField.addTarget(self, action: #selector(emailChanged), for: .editingChanged)
        ageTextField.addTarget(self, action: #selector(ageChanged), for: .editingChanged)
        
        submitButton.addTarget(self, action: #selector(submitButtonTapped), for: .touchUpInside)
        cancelButton.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside)
    }
    
    @objc private func nameChanged() {
        formData.name = nameTextField.text ?? ""
        onFieldChange?("name", formData.name)
    }
    
    @objc private func emailChanged() {
        formData.email = emailTextField.text ?? ""
        onFieldChange?("email", formData.email)
    }
    
    @objc private func ageChanged() {
        if let ageText = ageTextField.text, let age = Int(ageText) {
            formData.age = age
            onFieldChange?("age", age)
        }
    }
    
    @objc private func submitButtonTapped() {
        // Validate form
        if !validateForm() {
            return
        }
        
        // Execute submit closure
        onSubmit?(formData)
        
        navigationController?.popViewController(animated: true)
    }
    
    @objc private func cancelButtonTapped() {
        // Execute cancel closure
        onCancel?()
        
        navigationController?.popViewController(animated: true)
    }
    
    private func validateForm() -> Bool {
        if formData.name.isEmpty {
            onValidationError?("Name is required")
            return false
        }
        
        if formData.email.isEmpty {
            onValidationError?("Email is required")
            return false
        }
        
        if !isValidEmail(formData.email) {
            onValidationError?("Invalid email format")
            return false
        }
        
        if formData.age <= 0 {
            onValidationError?("Please enter a valid age")
            return false
        }
        
        return true
    }
    
    private func isValidEmail(_ email: String) -> Bool {
        let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
        let emailPredicate = NSPredicate(format: "SELF MATCHES %@", emailRegex)
        return emailPredicate.evaluate(with: email)
    }
}

// MARK: - Form Data Model

struct FormData {
    var name: String = ""
    var email: String = ""
    var age: Int = 0
}

// MARK: - Parent ViewController Using Multiple Closures

class FormContainerViewController: UIViewController {
    
    private let resultLabel: UILabel = {
        let label = UILabel()
        label.text = "No form submitted yet"
        label.textAlignment = .center
        label.numberOfLines = 0
        label.font = .systemFont(ofSize: 16)
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let showFormButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Show Form", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Form Manager"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
    }
    
    private func setupUI() {
        view.addSubview(resultLabel)
        view.addSubview(showFormButton)
        
        NSLayoutConstraint.activate([
            resultLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            resultLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            resultLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            resultLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            showFormButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -40),
            showFormButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            showFormButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            showFormButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func setupActions() {
        showFormButton.addTarget(self, action: #selector(showFormButtonTapped), for: .touchUpInside)
    }
    
    @objc private func showFormButtonTapped() {
        let formVC = FormViewController()
        
        // Setup all closures
        formVC.onSubmit = { [weak self] formData in
            self?.handleFormSubmission(formData)
        }
        
        formVC.onCancel = { [weak self] in
            self?.handleFormCancellation()
        }
        
        formVC.onValidationError = { [weak self] errorMessage in
            self?.handleValidationError(errorMessage)
        }
        
        formVC.onFieldChange = { [weak self] fieldName, value in
            self?.handleFieldChange(fieldName: fieldName, value: value)
        }
        
        navigationController?.pushViewController(formVC, animated: true)
    }
    
    private func handleFormSubmission(_ formData: FormData) {
        resultLabel.text = """
        Form Submitted!
        Name: \(formData.name)
        Email: \(formData.email)
        Age: \(formData.age)
        """
        
        // Save data to database
        saveFormData(formData)
        
        // Show success alert
        showSuccessAlert()
    }
    
    private func handleFormCancellation() {
        resultLabel.text = "Form submission cancelled"
        print("User cancelled the form")
    }
    
    private func handleValidationError(_ errorMessage: String) {
        // Show error alert
        let alert = UIAlertController(title: "Validation Error", message: errorMessage, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
    
    private func handleFieldChange(fieldName: String, value: Any) {
        print("Field '\(fieldName)' changed to: \(value)")
        // Could update UI or perform real-time validation
    }
    
    private func saveFormData(_ formData: FormData) {
        // Save to UserDefaults, CoreData, or send to API
        print("Saving form data: \(formData)")
    }
    
    private func showSuccessAlert() {
        let alert = UIAlertController(title: "Success", message: "Form submitted successfully!", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

Escaping and Non-Escaping Closures

import UIKit

// MARK: - Understanding Escaping Closures

class DataFetchViewController: UIViewController {
    
    // Escaping closure - executes after function returns
    var onDataFetched: ((Result<[String], Error>) -> Void)?
    
    private let activityIndicator: UIActivityIndicatorView = {
        let indicator = UIActivityIndicatorView(style: .large)
        indicator.translatesAutoresizingMaskIntoConstraints = false
        return indicator
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Fetch Data"
        view.backgroundColor = .systemBackground
        
        setupUI()
        fetchData()
    }
    
    private func setupUI() {
        view.addSubview(activityIndicator)
        
        NSLayoutConstraint.activate([
            activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
        
        activityIndicator.startAnimating()
    }
    
    private func fetchData() {
        // Simulate API call with escaping closure
        performAsyncOperation { [weak self] result in
            DispatchQueue.main.async {
                self?.activityIndicator.stopAnimating()
                
                switch result {
                case .success(let data):
                    self?.onDataFetched?(.success(data))
                case .failure(let error):
                    self?.onDataFetched?(.failure(error))
                }
                
                self?.navigationController?.popViewController(animated: true)
            }
        }
    }
    
    // Escaping closure marked with @escaping
    private func performAsyncOperation(completion: @escaping (Result<[String], Error>) -> Void) {
        DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) {
            let mockData = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"]
            completion(.success(mockData))
        }
    }
}

// MARK: - Parent Using Escaping Closure

class DataListViewController: UIViewController {
    
    private var dataItems: [String] = []
    
    private let tableView: UITableView = {
        let table = UITableView()
        table.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        table.translatesAutoresizingMaskIntoConstraints = false
        return table
    }()
    
    private let fetchButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Fetch Data", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Data List"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
    }
    
    private func setupUI() {
        view.addSubview(tableView)
        view.addSubview(fetchButton)
        
        tableView.delegate = self
        tableView.dataSource = self
        
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: fetchButton.topAnchor, constant: -16),
            
            fetchButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            fetchButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            fetchButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
            fetchButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func setupActions() {
        fetchButton.addTarget(self, action: #selector(fetchButtonTapped), for: .touchUpInside)
    }
    
    @objc private func fetchButtonTapped() {
        let fetchVC = DataFetchViewController()
        
        // Assign escaping closure
        fetchVC.onDataFetched = { [weak self] result in
            switch result {
            case .success(let data):
                self?.dataItems = data
                self?.tableView.reloadData()
                self?.showSuccessMessage()
            case .failure(let error):
                self?.showErrorAlert(error: error)
            }
        }
        
        navigationController?.pushViewController(fetchVC, animated: true)
    }
    
    private func showSuccessMessage() {
        let alert = UIAlertController(title: "Success", message: "Data fetched successfully!", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
    
    private func showErrorAlert(error: Error) {
        let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

extension DataListViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataItems.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = dataItems[indexPath.row]
        return cell
    }
}

Generic Closures for Reusability

import UIKit

// MARK: - Generic Picker ViewController

class GenericPickerViewController<T>: UIViewController where T: CustomStringConvertible {
    
    // Generic closure
    var onItemSelected: ((T) -> Void)?
    var items: [T] = []
    
    private let tableView: UITableView = {
        let table = UITableView()
        table.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        table.translatesAutoresizingMaskIntoConstraints = false
        return table
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Select Item"
        view.backgroundColor = .systemBackground
        
        setupUI()
    }
    
    private func setupUI() {
        view.addSubview(tableView)
        tableView.delegate = self
        tableView.dataSource = self
        
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
}

extension GenericPickerViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = items[indexPath.row].description
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let selectedItem = items[indexPath.row]
        onItemSelected?(selectedItem)
        navigationController?.popViewController(animated: true)
    }
}

// MARK: - Using Generic Picker

enum Priority: String, CustomStringConvertible {
    case low = "Low"
    case medium = "Medium"
    case high = "High"
    case critical = "Critical"
    
    var description: String {
        return self.rawValue
    }
}

struct Category: CustomStringConvertible {
    let id: Int
    let name: String
    let icon: String
    
    var description: String {
        return "\(icon) \(name)"
    }
}

class TaskCreatorViewController: UIViewController {
    
    private var selectedPriority: Priority = .medium
    private var selectedCategory: Category?
    
    private let priorityButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Priority: Medium", for: .normal)
        button.backgroundColor = .systemGray6
        button.setTitleColor(.label, for: .normal)
        button.layer.cornerRadius = 8
        button.contentHorizontalAlignment = .left
        button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    private let categoryButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Category: None", for: .normal)
        button.backgroundColor = .systemGray6
        button.setTitleColor(.label, for: .normal)
        button.layer.cornerRadius = 8
        button.contentHorizontalAlignment = .left
        button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Create Task"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
    }
    
    private func setupUI() {
        view.addSubview(priorityButton)
        view.addSubview(categoryButton)
        
        NSLayoutConstraint.activate([
            priorityButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40),
            priorityButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            priorityButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            priorityButton.heightAnchor.constraint(equalToConstant: 50),
            
            categoryButton.topAnchor.constraint(equalTo: priorityButton.bottomAnchor, constant: 16),
            categoryButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            categoryButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            categoryButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func setupActions() {
        priorityButton.addTarget(self, action: #selector(priorityButtonTapped), for: .touchUpInside)
        categoryButton.addTarget(self, action: #selector(categoryButtonTapped), for: .touchUpInside)
    }
    
    @objc private func priorityButtonTapped() {
        let pickerVC = GenericPickerViewController<Priority>()
        pickerVC.items = [.low, .medium, .high, .critical]
        
        // Use generic closure for Priority type
        pickerVC.onItemSelected = { [weak self] priority in
            self?.selectedPriority = priority
            self?.priorityButton.setTitle("Priority: \(priority.rawValue)", for: .normal)
        }
        
        navigationController?.pushViewController(pickerVC, animated: true)
    }
    
    @objc private func categoryButtonTapped() {
        let pickerVC = GenericPickerViewController<Category>()
        pickerVC.items = [
            Category(id: 1, name: "Work", icon: "💼"),
            Category(id: 2, name: "Personal", icon: "🏠"),
            Category(id: 3, name: "Shopping", icon: "🛒"),
            Category(id: 4, name: "Health", icon: "⚕️")
        ]
        
        // Use generic closure for Category type
        pickerVC.onItemSelected = { [weak self] category in
            self?.selectedCategory = category
            self?.categoryButton.setTitle("Category: \(category.name)", for: .normal)
        }
        
        navigationController?.pushViewController(pickerVC, animated: true)
    }
}

Method 5: NotificationCenter

NotificationCenter provides a broadcast mechanism for sending information between objects in your application. It's particularly useful for one-to-many communication where multiple observers need to react to the same event.

How It Works

  1. Define a notification name
  2. Observers register to receive notifications
  3. Senders post notifications with optional data
  4. All registered observers receive the notification
  5. Observers process the notification data

Advantages

  • ✅ One-to-many communication
  • ✅ Decoupled components
  • ✅ System-wide broadcasting
  • ✅ Can pass data with userInfo
  • ✅ Built into Foundation

Disadvantages

  • ❌ No compile-time checking
  • ❌ String-based notification names
  • ❌ Hard to track data flow
  • ❌ Must remember to remove observers
  • ❌ Can lead to difficult debugging

Basic NotificationCenter Implementation

import UIKit

// MARK: - Notification Name Extension

extension Notification.Name {
    static let userDidLogin = Notification.Name("userDidLogin")
    static let userDidLogout = Notification.Name("userDidLogout")
    static let themeDidChange = Notification.Name("themeDidChange")
    static let dataDidRefresh = Notification.Name("dataDidRefresh")
}

// MARK: - Login ViewController (Sender)

class LoginViewController: UIViewController {
    
    private let usernameTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Username"
        textField.borderStyle = .roundedRect
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let passwordTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Password"
        textField.borderStyle = .roundedRect
        textField.isSecureTextEntry = true
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let loginButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Login", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Login"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
    }
    
    private func setupUI() {
        view.addSubview(usernameTextField)
        view.addSubview(passwordTextField)
        view.addSubview(loginButton)
        
        NSLayoutConstraint.activate([
            usernameTextField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 100),
            usernameTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            usernameTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            usernameTextField.heightAnchor.constraint(equalToConstant: 44),
            
            passwordTextField.topAnchor.constraint(equalTo: usernameTextField.bottomAnchor, constant: 16),
            passwordTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            passwordTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            passwordTextField.heightAnchor.constraint(equalToConstant: 44),
            
            loginButton.topAnchor.constraint(equalTo: passwordTextField.bottomAnchor, constant: 40),
            loginButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            loginButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            loginButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func setupActions() {
        loginButton.addTarget(self, action: #selector(loginButtonTapped), for: .touchUpInside)
    }
    
    @objc private func loginButtonTapped() {
        guard let username = usernameTextField.text, !username.isEmpty,
              let password = passwordTextField.text, !password.isEmpty else {
            showAlert(message: "Please fill in all fields")
            return
        }
        
        // Simulate login
        performLogin(username: username, password: password)
    }
    
    private func performLogin(username: String, password: String) {
        // Simulate API call
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
            let userInfo: [String: Any] = [
                "username": username,
                "userId": UUID().uuidString,
                "loginTime": Date(),
                "accessToken": "sample_token_\(Int.random(in: 1000...9999))"
            ]
            
            // Post notification with user data
            NotificationCenter.default.post(
                name: .userDidLogin,
                object: self,
                userInfo: userInfo
            )
            
            self?.dismiss(animated: true)
        }
    }
    
    private func showAlert(message: String) {
        let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

// MARK: - Dashboard ViewController (Observer)

class DashboardViewController: UIViewController {
    
    private var currentUser: String = "Not logged in"
    
    private let welcomeLabel: UILabel = {
        let label = UILabel()
        label.text = "Welcome!"
        label.font = .systemFont(ofSize: 28, weight: .bold)
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let userStatusLabel: UILabel = {
        let label = UILabel()
        label.text = "Please log in"
        label.font = .systemFont(ofSize: 16)
        label.textColor = .secondaryLabel
        label.textAlignment = .center
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let loginButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Login", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    private let logoutButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Logout", for: .normal)
        button.backgroundColor = .systemRed
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.isHidden = true
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Dashboard"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
        registerForNotifications()
    }
    
    private func setupUI() {
        view.addSubview(welcomeLabel)
        view.addSubview(userStatusLabel)
        view.addSubview(loginButton)
        view.addSubview(logoutButton)
        
        NSLayoutConstraint.activate([
            welcomeLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 80),
            welcomeLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            welcomeLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            userStatusLabel.topAnchor.constraint(equalTo: welcomeLabel.bottomAnchor, constant: 16),
            userStatusLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            userStatusLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            loginButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -40),
            loginButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            loginButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            loginButton.heightAnchor.constraint(equalToConstant: 50),
            
            logoutButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -40),
            logoutButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            logoutButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            logoutButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func setupActions() {
        loginButton.addTarget(self, action: #selector(loginButtonTapped), for: .touchUpInside)
        logoutButton.addTarget(self, action: #selector(logoutButtonTapped), for: .touchUpInside)
    }
    
    private func registerForNotifications() {
        // Register for login notification
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleUserDidLogin(_:)),
            name: .userDidLogin,
            object: nil
        )
        
        // Register for logout notification
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleUserDidLogout(_:)),
            name: .userDidLogout,
            object: nil
        )
    }
    
    @objc private func handleUserDidLogin(_ notification: Notification) {
        // Extract data from notification
        guard let userInfo = notification.userInfo,
              let username = userInfo["username"] as? String,
              let userId = userInfo["userId"] as? String,
              let loginTime = userInfo["loginTime"] as? Date else {
            return
        }
        
        currentUser = username
        
        // Update UI
        welcomeLabel.text = "Welcome, \(username)!"
        
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        formatter.timeStyle = .short
        userStatusLabel.text = "Logged in at \(formatter.string(from: loginTime))\nUser ID: \(userId)"
        
        loginButton.isHidden = true
        logoutButton.isHidden = false
        
        // Animate transition
        animateLoginSuccess()
    }
    
    @objc private func handleUserDidLogout(_ notification: Notification) {
        currentUser = "Not logged in"
        
        welcomeLabel.text = "Welcome!"
        userStatusLabel.text = "Please log in"
        
        loginButton.isHidden = false
        logoutButton.isHidden = true
    }
    
    @objc private func loginButtonTapped() {
        let loginVC = LoginViewController()
        let navController = UINavigationController(rootViewController: loginVC)
        navController.modalPresentationStyle = .fullScreen
        present(navController, animated: true)
    }
    
    @objc private func logoutButtonTapped() {
        // Post logout notification
        NotificationCenter.default.post(name: .userDidLogout, object: self)
    }
    
    private func animateLoginSuccess() {
        welcomeLabel.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
        welcomeLabel.alpha = 0
        
        UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.3) {
            self.welcomeLabel.transform = .identity
            self.welcomeLabel.alpha = 1
        }
    }
    
    deinit {
        // Remove observers when view controller is deallocated
        NotificationCenter.default.removeObserver(self)
    }
}

// MARK: - Profile ViewController (Another Observer)

class ProfileViewController: UIViewController {
    
    private let userInfoLabel: UILabel = {
        let label = UILabel()
        label.text = "No user logged in"
        label.font = .systemFont(ofSize: 18)
        label.textAlignment = .center
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Profile"
        view.backgroundColor = .systemBackground
        
        setupUI()
        registerForNotifications()
    }
    
    private func setupUI() {
        view.addSubview(userInfoLabel)
        
        NSLayoutConstraint.activate([
            userInfoLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            userInfoLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            userInfoLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            userInfoLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
        ])
    }
    
    private func registerForNotifications() {
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleUserDidLogin(_:)),
            name: .userDidLogin,
            object: nil
        )
        
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleUserDidLogout(_:)),
            name: .userDidLogout,
            object: nil
        )
    }
    
    @objc private func handleUserDidLogin(_ notification: Notification) {
        guard let userInfo = notification.userInfo,
              let username = userInfo["username"] as? String,
              let userId = userInfo["userId"] as? String else {
            return
        }
        
        userInfoLabel.text = """
        User: \(username)
        ID: \(userId)
        Status: Active
        """
    }
    
    @objc private func handleUserDidLogout(_ notification: Notification) {
        userInfoLabel.text = "No user logged in"
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

Advanced NotificationCenter with Custom Objects

import UIKit

// MARK: - Custom Notification Object

class DataUpdateNotification {
    let updateType: UpdateType
    let affectedItems: [String]
    let timestamp: Date
    let source: String
    
    enum UpdateType {
        case created
        case updated
        case deleted
        case synced
    }
    
    init(updateType: UpdateType, affectedItems: [String], source: String) {
        self.updateType = updateType
        self.affectedItems = affectedItems
        self.timestamp = Date()
        self.source = source
    }
}

// MARK: - Notification Names

extension Notification.Name {
    static let dataDidUpdate = Notification.Name("dataDidUpdate")
    static let syncDidComplete = Notification.Name("syncDidComplete")
    static let connectionStatusChanged = Notification.Name("connectionStatusChanged")
}

// MARK: - Data Manager (Notification Sender)

class DataManager {
    static let shared = DataManager()
    
    private init() {}
    
    func createItem(name: String) {
        // Simulate item creation
        print("Creating item: \(name)")
        
        let updateNotification = DataUpdateNotification(
            updateType: .created,
            affectedItems: [name],
            source: "DataManager"
        )
        
        // Post notification with custom object
        NotificationCenter.default.post(
            name: .dataDidUpdate,
            object: updateNotification
        )
    }
    
    func updateItems(names: [String]) {
        print("Updating items: \(names)")
        
        let updateNotification = DataUpdateNotification(
            updateType: .updated,
            affectedItems: names,
            source: "DataManager"
        )
        
        NotificationCenter.default.post(
            name: .dataDidUpdate,
            object: updateNotification
        )
    }
    
    func deleteItem(name: String) {
        print("Deleting item: \(name)")
        
        let updateNotification = DataUpdateNotification(
            updateType: .deleted,
            affectedItems: [name],
            source: "DataManager"
        )
        
        NotificationCenter.default.post(
            name: .dataDidUpdate,
            object: updateNotification
        )
    }
    
    func syncData() {
        print("Syncing data...")
        
        DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) {
            let syncInfo: [String: Any] = [
                "itemCount": 150,
                "syncDuration": 2.0,
                "success": true,
                "timestamp": Date()
            ]
            
            DispatchQueue.main.async {
                NotificationCenter.default.post(
                    name: .syncDidComplete,
                    object: nil,
                    userInfo: syncInfo
                )
            }
        }
    }
}

// MARK: - List ViewController (Observer)

class ListViewController: UIViewController {
    
    private var items: [String] = []
    
    private let tableView: UITableView = {
        let table = UITableView()
        table.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        table.translatesAutoresizingMaskIntoConstraints = false
        return table
    }()
    
    private let statusLabel: UILabel = {
        let label = UILabel()
        label.text = "Ready"
        label.font = .systemFont(ofSize: 14)
        label.textColor = .secondaryLabel
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let addButton: UIBarButtonItem = {
        return UIBarButtonItem(barButtonSystemItem: .add, target: nil, action: nil)
    }()
    
    private let syncButton: UIBarButtonItem = {
        return UIBarButtonItem(barButtonSystemItem: .refresh, target: nil, action: nil)
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Items"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupNavigationBar()
        registerForNotifications()
        loadInitialData()
    }
    
    private func setupUI() {
        view.addSubview(tableView)
        view.addSubview(statusLabel)
        
        tableView.delegate = self
        tableView.dataSource = self
        
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: statusLabel.topAnchor),
            
            statusLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            statusLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            statusLabel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -8),
            statusLabel.heightAnchor.constraint(equalToConstant: 30)
        ])
    }
    
    private func setupNavigationBar() {
        addButton.target = self
        addButton.action = #selector(addButtonTapped)
        
        syncButton.target = self
        syncButton.action = #selector(syncButtonTapped)
        
        navigationItem.rightBarButtonItems = [addButton, syncButton]
    }
    
    private func registerForNotifications() {
        // Register for data updates
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleDataUpdate(_:)),
            name: .dataDidUpdate,
            object: nil
        )
        
        // Register for sync completion
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleSyncComplete(_:)),
            name: .syncDidComplete,
            object: nil
        )
    }
    
    @objc private func handleDataUpdate(_ notification: Notification) {
        guard let updateInfo = notification.object as? DataUpdateNotification else {
            return
        }
        
        switch updateInfo.updateType {
        case .created:
            items.append(contentsOf: updateInfo.affectedItems)
            statusLabel.text = "Added \(updateInfo.affectedItems.count) item(s)"
            
        case .updated:
            statusLabel.text = "Updated \(updateInfo.affectedItems.count) item(s)"
            
        case .deleted:
            items.removeAll { updateInfo.affectedItems.contains($0) }
            statusLabel.text = "Deleted \(updateInfo.affectedItems.count) item(s)"
            
        case .synced:
            statusLabel.text = "Data synced successfully"
        }
        
        tableView.reloadData()
        
        // Reset status after delay
        DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { [weak self] in
            self?.statusLabel.text = "Ready"
        }
    }
    
    @objc private func handleSyncComplete(_ notification: Notification) {
        guard let userInfo = notification.userInfo,
              let itemCount = userInfo["itemCount"] as? Int,
              let duration = userInfo["syncDuration"] as? Double,
              let success = userInfo["success"] as? Bool else {
            return
        }
        
        if success {
            statusLabel.text = "Synced \(itemCount) items in \(String(format: "%.1f", duration))s"
        } else {
            statusLabel.text = "Sync failed"
        }
    }
    
    @objc private func addButtonTapped() {
        let alert = UIAlertController(title: "Add Item", message: nil, preferredStyle: .alert)
        
        alert.addTextField { textField in
            textField.placeholder = "Item name"
        }
        
        alert.addAction(UIAlertAction(title: "Add", style: .default) { [weak self] _ in
            guard let name = alert.textFields?.first?.text, !name.isEmpty else { return }
            DataManager.shared.createItem(name: name)
        })
        
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
        
        present(alert, animated: true)
    }
    
    @objc private func syncButtonTapped() {
        statusLabel.text = "Syncing..."
        DataManager.shared.syncData()
    }
    
    private func loadInitialData() {
        items = ["Item 1", "Item 2", "Item 3"]
        tableView.reloadData()
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

extension ListViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = items[indexPath.row]
        return cell
    }
    
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            let itemName = items[indexPath.row]
            DataManager.shared.deleteItem(name: itemName)
        }
    }
}

NotificationCenter with Queue and Modern API

import UIKit

// MARK: - Modern NotificationCenter Usage (iOS 13+)

class ModernNotificationViewController: UIViewController {
    
    private var observations = [NSObjectProtocol]()
    
    private let statusLabel: UILabel = {
        let label = UILabel()
        label.text = "Waiting for events..."
        label.numberOfLines = 0
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Modern Notifications"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupModernObservers()
    }
    
    private func setupUI() {
        view.addSubview(statusLabel)
        
        NSLayoutConstraint.activate([
            statusLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            statusLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            statusLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            statusLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
        ])
    }
    
    private func setupModernObservers() {
        // Modern block-based API
        let dataObserver = NotificationCenter.default.addObserver(
            forName: .dataDidUpdate,
            object: nil,
            queue: .main
        ) { [weak self] notification in
            self?.handleDataUpdate(notification)
        }
        observations.append(dataObserver)
        
        // Observer with custom queue
        let backgroundQueue = OperationQueue()
        backgroundQueue.name = "com.app.notification.background"
        backgroundQueue.qualityOfService = .background
        
        let syncObserver = NotificationCenter.default.addObserver(
            forName: .syncDidComplete,
            object: nil,
            queue: backgroundQueue
        ) { [weak self] notification in
            // Process on background queue
            self?.processSyncOnBackground(notification)
            
            // Update UI on main queue
            DispatchQueue.main.async {
                self?.updateUIAfterSync(notification)
            }
        }
        observations.append(syncObserver)
    }
    
    private func handleDataUpdate(_ notification: Notification) {
        if let updateInfo = notification.object as? DataUpdateNotification {
            statusLabel.text = "Data updated: \(updateInfo.affectedItems.joined(separator: ", "))"
        }
    }
    
    private func processSyncOnBackground(_ notification: Notification) {
        // Heavy processing on background thread
        print("Processing sync on background queue")
        Thread.sleep(forTimeInterval: 1.0)
    }
    
    private func updateUIAfterSync(_ notification: Notification) {
        statusLabel.text = "Sync completed successfully!"
    }
    
    deinit {
        // Remove all observers
        observations.forEach { NotificationCenter.default.removeObserver($0) }
    }
}

Thank you for reading!
@hiren_syl


Continuation in Next Section...

Due to the extensive nature of this guide, we've covered Methods 1-5 in detail. The complete blog continues with:

  • Method 6: Singleton Pattern
  • Method 7: UserDefaults
  • Method 8: Key-Value Observing (KVO)
  • Method 9: Protocol-Oriented Programming
  • Method 10: Dependency Injection
  • Method 11: Coordinator Pattern
  • Method 12: MVVM Architecture
  • Method 13: Reactive Programming (Combine)
  • Advanced Patterns and Best Practices

Each remaining method includes:

  • Detailed explanations
  • Complete working code examples
  • Advantages and disadvantages
  • Real-world use cases
  • Performance considerations
  • Memory management tips

This comprehensive guide provides iOS developers with every tool needed for effective data sharing between ViewControllers in Swift 5 with UIKit.# The Ultimate Guide to Data Sharing Between ViewControllers in Swift 5 UIKit

Data sharing between ViewControllers is one of the most fundamental concepts in iOS development. Whether you're building a simple app or a complex enterprise solution, understanding how to effectively pass data between screens is crucial. This comprehensive guide covers every method, pattern, and technique for sharing data between ViewControllers in Swift 5 using UIKit.

Table of Contents

  1. Introduction to Data Flow
  2. Method 1: Direct Property Assignment
  3. Method 2: Segue-Based Data Passing
  4. Method 3: Delegation Pattern
  5. Method 4: Closures and Callbacks
  6. Method 5: NotificationCenter
  7. Method 6: Singleton Pattern
  8. Method 7: UserDefaults
  9. Method 8: Key-Value Observing (KVO)
  10. Method 9: Protocol-Oriented Programming
  11. Method 10: Dependency Injection
  12. Method 11: Coordinator Pattern
  13. Method 12: MVVM Architecture
  14. Method 13: Reactive Programming
  15. Advanced Patterns
  16. Best Practices

Introduction to Data Flow

Before diving into specific methods, it's essential to understand the different types of data flow in iOS applications.

Types of Data Flow

Forward Data Flow (Parent → Child)

Data flows from a source ViewController to a destination ViewController. This is the most common pattern when navigating forward in your app.

Use Cases:

  • Passing user selection to detail screen
  • Sending configuration data to new screen
  • Providing context to presented controllers

Backward Data Flow (Child → Parent)

Data flows back from a presented/pushed ViewController to the presenting/pushing ViewController. This happens when user completes an action and needs to send results back.

Use Cases:

  • Form submission results
  • User selection from picker
  • Filter/search criteria
  • Photo selection

Sibling Data Flow

Data flows between ViewControllers at the same level in the hierarchy, typically managed through a common parent or shared data source.

Use Cases:

  • Tab bar controllers sharing state
  • Split view controllers
  • Container view controllers

Navigation Patterns and Data Flow

Push Navigation (UINavigationController)

ViewController A → ViewController B → ViewController C

Data typically flows forward during push, and backward when popping.

Modal Presentation

ViewController A presents ViewController B

Can flow in both directions, but requires explicit handling.

Tab Bar Navigation

Tab 1 (VC A) ←→ Shared Data ←→ Tab 2 (VC B)

Requires shared data source or communication mechanism.

Container ViewControllers

Parent VC
  ├── Child VC 1
  ├── Child VC 2
  └── Child VC 3

Parent manages data distribution to children.


Method 1: Direct Property Assignment

Direct property assignment is the simplest and most straightforward method for passing data forward from one ViewController to another. This method is ideal for one-way data flow when presenting or pushing a new ViewController.

How It Works

When you create or access a destination ViewController, you directly set its properties before presenting or pushing it. This method is type-safe, explicit, and easy to understand.

Advantages

  • ✅ Simple and straightforward
  • ✅ Type-safe
  • ✅ Explicit and easy to understand
  • ✅ No additional dependencies
  • ✅ Compile-time checking

Disadvantages

  • ❌ Only works for forward data flow
  • ❌ Creates tight coupling between ViewControllers
  • ❌ Doesn't work for backward data flow
  • ❌ Can lead to massive ViewControllers

Implementation - Basic Example

import UIKit

// MARK: - First ViewController (Source)

class FirstViewController: UIViewController {
    
    // UI Elements
    private let nameTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Enter your name"
        textField.borderStyle = .roundedRect
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let ageTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Enter your age"
        textField.borderStyle = .roundedRect
        textField.keyboardType = .numberPad
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let emailTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Enter your email"
        textField.borderStyle = .roundedRect
        textField.keyboardType = .emailAddress
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let nextButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Next", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Enter Details"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
    }
    
    private func setupUI() {
        view.addSubview(nameTextField)
        view.addSubview(ageTextField)
        view.addSubview(emailTextField)
        view.addSubview(nextButton)
        
        NSLayoutConstraint.activate([
            nameTextField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
            nameTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            nameTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            nameTextField.heightAnchor.constraint(equalToConstant: 44),
            
            ageTextField.topAnchor.constraint(equalTo: nameTextField.bottomAnchor, constant: 20),
            ageTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            ageTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            ageTextField.heightAnchor.constraint(equalToConstant: 44),
            
            emailTextField.topAnchor.constraint(equalTo: ageTextField.bottomAnchor, constant: 20),
            emailTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            emailTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            emailTextField.heightAnchor.constraint(equalToConstant: 44),
            
            nextButton.topAnchor.constraint(equalTo: emailTextField.bottomAnchor, constant: 40),
            nextButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            nextButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            nextButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func setupActions() {
        nextButton.addTarget(self, action: #selector(nextButtonTapped), for: .touchUpInside)
    }
    
    @objc private func nextButtonTapped() {
        guard let name = nameTextField.text, !name.isEmpty,
              let ageText = ageTextField.text, let age = Int(ageText),
              let email = emailTextField.text, !email.isEmpty else {
            showAlert(message: "Please fill all fields correctly")
            return
        }
        
        // Create the destination ViewController
        let secondVC = SecondViewController()
        
        // Direct property assignment - passing data forward
        secondVC.userName = name
        secondVC.userAge = age
        secondVC.userEmail = email
        
        // Navigate to second ViewController
        navigationController?.pushViewController(secondVC, animated: true)
    }
    
    private func showAlert(message: String) {
        let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

// MARK: - Second ViewController (Destination)

class SecondViewController: UIViewController {
    
    // Properties to receive data
    var userName: String?
    var userAge: Int?
    var userEmail: String?
    
    // UI Elements
    private let nameLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 18, weight: .medium)
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let ageLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 18, weight: .medium)
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let emailLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 18, weight: .medium)
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let stackView: UIStackView = {
        let stack = UIStackView()
        stack.axis = .vertical
        stack.spacing = 20
        stack.alignment = .leading
        stack.translatesAutoresizingMaskIntoConstraints = false
        return stack
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "User Details"
        view.backgroundColor = .systemBackground
        
        setupUI()
        displayUserData()
    }
    
    private func setupUI() {
        stackView.addArrangedSubview(nameLabel)
        stackView.addArrangedSubview(ageLabel)
        stackView.addArrangedSubview(emailLabel)
        
        view.addSubview(stackView)
        
        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
            stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
        ])
    }
    
    private func displayUserData() {
        nameLabel.text = "Name: \(userName ?? "N/A")"
        ageLabel.text = "Age: \(userAge != nil ? String(userAge!) : "N/A")"
        emailLabel.text = "Email: \(userEmail ?? "N/A")"
    }
}

Advanced Implementation with Custom Models

import UIKit

// MARK: - Data Models

struct User {
    let id: UUID
    let name: String
    let age: Int
    let email: String
    let phoneNumber: String?
    let address: Address?
    let profileImage: UIImage?
    
    init(id: UUID = UUID(), name: String, age: Int, email: String, 
         phoneNumber: String? = nil, address: Address? = nil, profileImage: UIImage? = nil) {
        self.id = id
        self.name = name
        self.age = age
        self.email = email
        self.phoneNumber = phoneNumber
        self.address = address
        self.profileImage = profileImage
    }
}

struct Address {
    let street: String
    let city: String
    let state: String
    let zipCode: String
    let country: String
}

enum UserRole {
    case admin
    case moderator
    case user
    case guest
    
    var displayName: String {
        switch self {
        case .admin: return "Administrator"
        case .moderator: return "Moderator"
        case .user: return "User"
        case .guest: return "Guest"
        }
    }
}

// MARK: - User List ViewController

class UserListViewController: UIViewController {
    
    private let tableView: UITableView = {
        let table = UITableView()
        table.register(UITableViewCell.self, forCellReuseIdentifier: "UserCell")
        table.translatesAutoresizingMaskIntoConstraints = false
        return table
    }()
    
    private var users: [User] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Users"
        view.backgroundColor = .systemBackground
        
        setupUI()
        loadUsers()
    }
    
    private func setupUI() {
        view.addSubview(tableView)
        tableView.delegate = self
        tableView.dataSource = self
        
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
    
    private func loadUsers() {
        // Sample data
        users = [
            User(name: "John Doe", age: 28, email: "john@example.com", 
                 phoneNumber: "+1234567890",
                 address: Address(street: "123 Main St", city: "New York", 
                                state: "NY", zipCode: "10001", country: "USA")),
            User(name: "Jane Smith", age: 32, email: "jane@example.com",
                 phoneNumber: "+1987654321",
                 address: Address(street: "456 Oak Ave", city: "Los Angeles",
                                state: "CA", zipCode: "90001", country: "USA")),
            User(name: "Bob Johnson", age: 45, email: "bob@example.com")
        ]
        
        tableView.reloadData()
    }
    
    private func navigateToUserDetail(user: User) {
        let detailVC = UserDetailViewController()
        
        // Direct property assignment with complex model
        detailVC.user = user
        detailVC.userRole = .user
        detailVC.isEditingEnabled = true
        
        navigationController?.pushViewController(detailVC, animated: true)
    }
}

extension UserListViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return users.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell", for: indexPath)
        let user = users[indexPath.row]
        cell.textLabel?.text = user.name
        cell.accessoryType = .disclosureIndicator
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        navigateToUserDetail(user: users[indexPath.row])
    }
}

// MARK: - User Detail ViewController

class UserDetailViewController: UIViewController {
    
    // Properties to receive data
    var user: User?
    var userRole: UserRole = .user
    var isEditingEnabled: Bool = false
    
    // UI Elements
    private let scrollView: UIScrollView = {
        let scroll = UIScrollView()
        scroll.translatesAutoresizingMaskIntoConstraints = false
        return scroll
    }()
    
    private let contentView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    private let profileImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        imageView.layer.cornerRadius = 50
        imageView.backgroundColor = .systemGray5
        imageView.translatesAutoresizingMaskIntoConstraints = false
        return imageView
    }()
    
    private let nameLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 24, weight: .bold)
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let roleLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 16, weight: .medium)
        label.textColor = .systemBlue
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let infoStackView: UIStackView = {
        let stack = UIStackView()
        stack.axis = .vertical
        stack.spacing = 16
        stack.translatesAutoresizingMaskIntoConstraints = false
        return stack
    }()
    
    private let editButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Edit", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "User Details"
        view.backgroundColor = .systemBackground
        
        setupUI()
        displayUserData()
    }
    
    private func setupUI() {
        view.addSubview(scrollView)
        scrollView.addSubview(contentView)
        
        contentView.addSubview(profileImageView)
        contentView.addSubview(nameLabel)
        contentView.addSubview(roleLabel)
        contentView.addSubview(infoStackView)
        
        if isEditingEnabled {
            contentView.addSubview(editButton)
            editButton.addTarget(self, action: #selector(editButtonTapped), for: .touchUpInside)
        }
        
        NSLayoutConstraint.activate([
            scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            
            contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
            
            profileImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20),
            profileImageView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            profileImageView.widthAnchor.constraint(equalToConstant: 100),
            profileImageView.heightAnchor.constraint(equalToConstant: 100),
            
            nameLabel.topAnchor.constraint(equalTo: profileImageView.bottomAnchor, constant: 16),
            nameLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            nameLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            
            roleLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 8),
            roleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            roleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            
            infoStackView.topAnchor.constraint(equalTo: roleLabel.bottomAnchor, constant: 30),
            infoStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            infoStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20)
        ])
        
        if isEditingEnabled {
            NSLayoutConstraint.activate([
                editButton.topAnchor.constraint(equalTo: infoStackView.bottomAnchor, constant: 30),
                editButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
                editButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
                editButton.heightAnchor.constraint(equalToConstant: 50),
                editButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20)
            ])
        } else {
            infoStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20).isActive = true
        }
    }
    
    private func displayUserData() {
        guard let user = user else { return }
        
        nameLabel.text = user.name
        roleLabel.text = userRole.displayName
        profileImageView.image = user.profileImage ?? UIImage(systemName: "person.circle.fill")
        
        // Add user information
        addInfoRow(title: "Email", value: user.email)
        addInfoRow(title: "Age", value: "\(user.age) years old")
        
        if let phone = user.phoneNumber {
            addInfoRow(title: "Phone", value: phone)
        }
        
        if let address = user.address {
            let fullAddress = "\(address.street)\n\(address.city), \(address.state) \(address.zipCode)\n\(address.country)"
            addInfoRow(title: "Address", value: fullAddress)
        }
    }
    
    private func addInfoRow(title: String, value: String) {
        let container = UIView()
        container.translatesAutoresizingMaskIntoConstraints = false
        
        let titleLabel = UILabel()
        titleLabel.text = title
        titleLabel.font = .systemFont(ofSize: 14, weight: .semibold)
        titleLabel.textColor = .secondaryLabel
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        
        let valueLabel = UILabel()
        valueLabel.text = value
        valueLabel.font = .systemFont(ofSize: 16)
        valueLabel.numberOfLines = 0
        valueLabel.translatesAutoresizingMaskIntoConstraints = false
        
        container.addSubview(titleLabel)
        container.addSubview(valueLabel)
        
        NSLayoutConstraint.activate([
            titleLabel.topAnchor.constraint(equalTo: container.topAnchor),
            titleLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor),
            titleLabel.trailingAnchor.constraint(equalTo: container.trailingAnchor),
            
            valueLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4),
            valueLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor),
            valueLabel.trailingAnchor.constraint(equalTo: container.trailingAnchor),
            valueLabel.bottomAnchor.constraint(equalTo: container.bottomAnchor)
        ])
        
        infoStackView.addArrangedSubview(container)
    }
    
    @objc private func editButtonTapped() {
        guard let user = user else { return }
        
        let editVC = UserEditViewController()
        editVC.user = user
        
        navigationController?.pushViewController(editVC, animated: true)
    }
}

// MARK: - User Edit ViewController

class UserEditViewController: UIViewController {
    
    var user: User?
    
    // UI implementation similar to above
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Edit User"
        view.backgroundColor = .systemBackground
        // Setup UI for editing
    }
}

Modal Presentation with Direct Assignment

import UIKit

// MARK: - Presenting ViewController

class PresentingViewController: UIViewController {
    
    private let presentButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Present Modal", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        
        setupUI()
    }
    
    private func setupUI() {
        view.addSubview(presentButton)
        presentButton.addTarget(self, action: #selector(presentButtonTapped), for: .touchUpInside)
        
        NSLayoutConstraint.activate([
            presentButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            presentButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            presentButton.widthAnchor.constraint(equalToConstant: 200),
            presentButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    @objc private func presentButtonTapped() {
        let modalVC = ModalViewController()
        
        // Direct property assignment
        modalVC.titleText = "Welcome"
        modalVC.messageText = "This is a modal presentation"
        modalVC.buttonTitle = "Got it!"
        modalVC.presentationStyle = .fullScreen
        
        // Configure presentation style
        if modalVC.presentationStyle == .fullScreen {
            modalVC.modalPresentationStyle = .fullScreen
        } else {
            modalVC.modalPresentationStyle = .pageSheet
        }
        
        present(modalVC, animated: true)
    }
}

// MARK: - Modal ViewController

class ModalViewController: UIViewController {
    
    enum PresentationStyle {
        case fullScreen
        case pageSheet
    }
    
    // Properties to receive data
    var titleText: String?
    var messageText: String?
    var buttonTitle: String?
    var presentationStyle: PresentationStyle = .pageSheet
    
    // UI Elements
    private let titleLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 28, weight: .bold)
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let messageLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 18)
        label.textAlignment = .center
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let dismissButton: UIButton = {
        let button = UIButton(type: .system)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        
        setupUI()
        configureWithData()
    }
    
    private func setupUI() {
        view.addSubview(titleLabel)
        view.addSubview(messageLabel)
        view.addSubview(dismissButton)
        
        dismissButton.addTarget(self, action: #selector(dismissButtonTapped), for: .touchUpInside)
        
        NSLayoutConstraint.activate([
            titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 60),
            titleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            titleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            messageLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 20),
            messageLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            messageLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            dismissButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -40),
            dismissButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            dismissButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            dismissButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func configureWithData() {
        titleLabel.text = titleText ?? "Title"
        messageLabel.text = messageText ?? "Message"
        dismissButton.setTitle(buttonTitle ?? "Dismiss", for: .normal)
    }
    
    @objc private func dismissButtonTapped() {
        dismiss(animated: true)
    }
}

Method 2: Segue-Based Data Passing

Segues are a storyboard-based way to define transitions between ViewControllers. The prepare(for:sender:) method is called before a segue is performed, allowing you to configure the destination ViewController.

How It Works

When a segue is triggered (either programmatically or through storyboard connections), the prepare(for:sender:) method is called. You can identify the segue by its identifier and cast the destination ViewController to set its properties.

Advantages

  • ✅ Visual representation in storyboard
  • ✅ Easy to set up for simple scenarios
  • ✅ Centralized data passing logic
  • ✅ Works well with Interface Builder

Disadvantages

  • ❌ Requires storyboards (not programmatic)
  • ❌ String-based identifiers (not type-safe)
  • ❌ Can become messy with many segues
  • ❌ Harder to test
  • ❌ Only for forward data flow

Implementation - Basic Segue

import UIKit

// MARK: - Source ViewController with Segue

class SegueSourceViewController: UIViewController {
    
    @IBOutlet weak var nameTextField: UITextField!
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var phoneTextField: UITextField!
    @IBOutlet weak var nextButton: UIButton!
    
    // Segue identifiers
    private enum SegueIdentifier {
        static let showDetail = "ShowDetailSegue"
        static let showSettings = "ShowSettingsSegue"
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "User Input"
    }
    
    // This method is called before any segue is performed
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Identify which segue is being performed
        if segue.identifier == SegueIdentifier.showDetail {
            // Cast destination to the correct type
            if let destinationVC = segue.destination as? SegueDestinationViewController {
                // Pass data through properties
                destinationVC.userName = nameTextField.text
                destinationVC.userEmail = emailTextField.text
                destinationVC.userPhone = phoneTextField.text
                destinationVC.timestamp = Date()
            }
        } else if segue.identifier == SegueIdentifier.showSettings {
            if let settingsVC = segue.destination as? SettingsViewController {
                settingsVC.currentUser = createUserObject()
            }
        }
    }
    
    // Validate and trigger segue programmatically
    @IBAction func nextButtonTapped(_ sender: UIButton) {
        guard validateInputs() else {
            showValidationError()
            return
        }
        
        // Perform segue with identifier
        performSegue(withIdentifier: SegueIdentifier.showDetail, sender: self)
    }
    
    private func validateInputs() -> Bool {
        guard let name = nameTextField.text, !name.isEmpty,
              let email = emailTextField.text, !email.isEmpty else {
            return false
        }
        return true
    }
    
    private func showValidationError() {
        let alert = UIAlertController(
            title: "Error",
            message: "Please fill in all required fields",
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
    
    private func createUserObject() -> User {
        return User(
            name: nameTextField.text ?? "",
            age: 0,
            email: emailTextField.text ?? ""
        )
    }
}

// MARK: - Destination ViewController

class SegueDestinationViewController: UIViewController {
    
    // Properties to receive data from segue
    var userName: String?
    var userEmail: String?
    var userPhone: String?
    var timestamp: Date?
    
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var emailLabel: UILabel!
    @IBOutlet weak var phoneLabel: UILabel!
    @IBOutlet weak var timestampLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "User Details"
        displayUserData()
    }
    
    private func displayUserData() {
        nameLabel.text = "Name: \(userName ?? "N/A")"
        emailLabel.text = "Email: \(userEmail ?? "N/A")"
        phoneLabel.text = "Phone: \(userPhone ?? "N/A")"
        
        if let timestamp = timestamp {
            let formatter = DateFormatter()
            formatter.dateStyle = .medium
            formatter.timeStyle = .short
            timestampLabel.text = "Created: \(formatter.string(from: timestamp))"
        }
    }
}

Advanced Segue Implementation with Multiple Destinations

import UIKit

// MARK: - Master View Controller with Multiple Segues

class MasterViewController: UIViewController {
    
    @IBOutlet weak var tableView: UITableView!
    
    private var products: [Product] = []
    private var selectedProduct: Product?
    
    // Segue identifiers as enum for type safety
    private enum SegueID: String {
        case showProductDetail = "ShowProductDetailSegue"
        case showProductEdit = "ShowProductEditSegue"
        case showProductReviews = "ShowProductReviewsSegue"
        case showCart = "ShowCartSegue"
        case showCheckout = "ShowCheckoutSegue"
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupTableView()
        loadProducts()
    }
    
    private func setupTableView() {
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(ProductTableViewCell.self, forCellReuseIdentifier: "ProductCell")
    }
    
    private func loadProducts() {
        products = [
            Product(id: "1", name: "iPhone 14", price: 999.99, category: .electronics),
            Product(id: "2", name: "MacBook Pro", price: 2499.99, category: .electronics),
            Product(id: "3", name: "AirPods Pro", price: 249.99, category: .electronics)
        ]
        tableView.reloadData()
    }
    
    // Centralized segue preparation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard let identifier = segue.identifier,
              let segueID = SegueID(rawValue: identifier) else {
            return
        }
        
        switch segueID {
        case .showProductDetail:
            prepareProductDetail(segue: segue, sender: sender)
        case .showProductEdit:
            prepareProductEdit(segue: segue, sender: sender)
        case .showProductReviews:
            prepareProductReviews(segue: segue, sender: sender)
        case .showCart:
            prepareCart(segue: segue, sender: sender)
        case .showCheckout:
            prepareCheckout(segue: segue, sender: sender)
        }
    }
    
    private func prepareProductDetail(segue: UIStoryboardSegue, sender: Any?) {
        if let detailVC = segue.destination as? ProductDetailViewController {
            detailVC.product = selectedProduct
            detailVC.viewMode = .readOnly
            detailVC.showReviewsButton = true
            detailVC.allowPurchase = true
        }
    }
    
    private func prepareProductEdit(segue: UIStoryboardSegue, sender: Any?) {
        if let editVC = segue.destination as? ProductEditViewController {
            editVC.product = selectedProduct
            editVC.editMode = .update
        }
    }
    
    private func prepareProductReviews(segue: UIStoryboardSegue, sender: Any?) {
        if let reviewsVC = segue.destination as? ProductReviewsViewController {
            reviewsVC.productID = selectedProduct?.id
            reviewsVC.productName = selectedProduct?.name
        }
    }
    
    private func prepareCart(segue: UIStoryboardSegue, sender: Any?) {
        if let cartVC = segue.destination as? CartViewController {
            cartVC.cartItems = CartManager.shared.items
            cartVC.totalAmount = CartManager.shared.totalAmount
        }
    }
    
    private func prepareCheckout(segue: UIStoryboardSegue, sender: Any?) {
        if let checkoutVC = segue.destination as? CheckoutViewController {
            checkoutVC.cartItems = CartManager.shared.items
            checkoutVC.subtotal = CartManager.shared.subtotal
            checkoutVC.tax = CartManager.shared.tax
            checkoutVC.shippingCost = CartManager.shared.shippingCost
            checkoutVC.total = CartManager.shared.totalAmount
        }
    }
    
    @IBAction func cartButtonTapped(_ sender: UIBarButtonItem) {
        performSegue(withIdentifier: SegueID.showCart.rawValue, sender: self)
    }
}

extension MasterViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return products.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ProductCell", for: indexPath)
        let product = products[indexPath.row]
        cell.textLabel?.text = product.name
        cell.detailTextLabel?.text = "$\(product.price)"
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        selectedProduct = products[indexPath.row]
        performSegue(withIdentifier: SegueID.showProductDetail.rawValue, sender: self)
    }
    
    func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
        selectedProduct = products[indexPath.row]
        performSegue(withIdentifier: SegueID.showProductEdit.rawValue, sender: self)
    }
}

// MARK: - Supporting Models

struct Product {
    let id: String
    let name: String
    let price: Double
    let category: ProductCategory
    var description: String?
    var imageURL: URL?
    var rating: Double?
    var reviewCount: Int?
}

enum ProductCategory {
    case electronics
    case clothing
    case books
    case home
}

class CartManager {
    static let shared = CartManager()
    var items: [CartItem] = []
    
    var subtotal: Double {
        return items.reduce(0) { $0 + ($1.product.price * Double($1.quantity)) }
    }
    
    var tax: Double {
        return subtotal * 0.08
    }
    
    var shippingCost: Double {
        return subtotal > 50 ? 0 : 5.99
    }
    
    var totalAmount: Double {
        return subtotal + tax + shippingCost
    }
}

struct CartItem {
    let product: Product
    var quantity: Int
}

Unwind Segues for Backward Data Flow

import UIKit

// MARK: - Detail ViewController

class DetailViewController: UIViewController {
    
    var selectedColor: UIColor?
    var selectedSize: String?
    var quantity: Int = 1
    
    @IBOutlet weak var colorPicker: UISegmentedControl!
    @IBOutlet weak var sizePicker: UISegmentedControl!
    @IBOutlet weak var quantityStepper: UIStepper!
    @IBOutlet weak var quantityLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func quantityChanged(_ sender: UIStepper) {
        quantity = Int(sender.value)
        quantityLabel.text = "\(quantity)"
    }
    
    @IBAction func colorChanged(_ sender: UISegmentedControl) {
        switch sender.selectedSegmentIndex {
        case 0: selectedColor = .red
        case 1: selectedColor = .blue
        case 2: selectedColor = .green
        default: selectedColor = nil
        }
    }
    
    @IBAction func sizeChanged(_ sender: UISegmentedControl) {
        switch sender.selectedSegmentIndex {
        case 0: selectedSize = "S"
        case 1: selectedSize = "M"
        case 2: selectedSize = "L"
        case 3: selectedSize = "XL"
        default: selectedSize = nil
        }
    }
    
    // This prepares data for the unwind segue
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "UnwindToMain" {
            // Data is already in properties, will be accessed by destination
        }
    }
}

// MARK: - Main ViewController (Destination of Unwind)

class MainViewController: UIViewController {
    
    @IBOutlet weak var selectedColorView: UIView!
    @IBOutlet weak var selectedSizeLabel: UILabel!
    @IBOutlet weak var selectedQuantityLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    // Unwind segue action - receives data from source
    @IBAction func unwindToMain(_ segue: UIStoryboardSegue) {
        if let sourceVC = segue.source as? DetailViewController {
            // Receive data from the unwinding ViewController
            if let color = sourceVC.selectedColor {
                selectedColorView.backgroundColor = color
            }
            
            if let size = sourceVC.selectedSize {
                selectedSizeLabel.text = "Size: \(size)"
            }
            
            selectedQuantityLabel.text = "Quantity: \(sourceVC.quantity)"
            
            // Perform additional actions with received data
            saveSelection(color: sourceVC.selectedColor,
                        size: sourceVC.selectedSize,
                        quantity: sourceVC.quantity)
        }
    }
    
    private func saveSelection(color: UIColor?, size: String?, quantity: Int) {
        // Save or process the received data
        print("Received selection: Color=\(String(describing: color)), Size=\(size ?? "N/A"), Quantity=\(quantity)")
    }
    
    // Forward segue to detail
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "ShowDetail" {
            if let detailVC = segue.destination as? DetailViewController {
                // Can pre-populate with existing data
                detailVC.selectedColor = selectedColorView.backgroundColor
                detailVC.selectedSize = extractSize(from: selectedSizeLabel.text)
                detailVC.quantity = extractQuantity(from: selectedQuantityLabel.text)
            }
        }
    }
    
    private func extractSize(from text: String?) -> String? {
        return text?.replacingOccurrences(of: "Size: ", with: "")
    }
    
    private func extractQuantity(from text: String?) -> Int {
        guard let text = text else { return 1 }
        let quantity = text.replacingOccurrences(of: "Quantity: ", with: "")
        return Int(quantity) ?? 1
    }
}

Conditional Segues with Validation

import UIKit

// MARK: - Form ViewController with Validation

class FormViewController: UIViewController {
    
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var passwordTextField: UITextField!
    @IBOutlet weak var confirmPasswordTextField: UITextField!
    @IBOutlet weak var agreeSwitch: UISwitch!
    @IBOutlet weak var submitButton: UIButton!
    
    private var validationErrors: [String] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupValidation()
    }
    
    private func setupValidation() {
        emailTextField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
        passwordTextField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
        confirmPasswordTextField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
    }
    
    @objc private func textFieldDidChange() {
        validateForm()
    }
    
    private func validateForm() {
        validationErrors.removeAll()
        
        // Email validation
        if let email = emailTextField.text, !email.isEmpty {
            if !isValidEmail(email) {
                validationErrors.append("Invalid email format")
            }
        } else {
            validationErrors.append("Email is required")
        }
        
        // Password validation
        if let password = passwordTextField.text, !password.isEmpty {
            if password.count < 8 {
                validationErrors.append("Password must be at least 8 characters")
            }
        } else {
            validationErrors.append("Password is required")
        }
        
        // Confirm password validation
        if passwordTextField.text != confirmPasswordTextField.text {
            validationErrors.append("Passwords do not match")
        }
        
        // Terms agreement
        if !agreeSwitch.isOn {
            validationErrors.append("You must agree to terms and conditions")
        }
        
        submitButton.isEnabled = validationErrors.isEmpty
    }
    
    private func isValidEmail(_ email: String) -> Bool {
        let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
        let emailPredicate = NSPredicate(format: "SELF MATCHES %@", emailRegex)
        return emailPredicate.evaluate(with: email)
    }
    
    // Override shouldPerformSegue to add validation
    override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
        if identifier == "ShowSuccessSegue" {
            validateForm()
            
            if !validationErrors.isEmpty {
                showValidationErrors()
                return false
            }
            
            return true
        }
        
        return true
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "ShowSuccessSegue" {
            if let successVC = segue.destination as? SuccessViewController {
                successVC.userEmail = emailTextField.text
                successVC.registrationDate = Date()
            }
        }
    }
    
    private func showValidationErrors() {
        let errorMessage = validationErrors.joined(separator: "\n")
        let alert = UIAlertController(
            title: "Validation Error",
            message: errorMessage,
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
    
    @IBAction func submitButtonTapped(_ sender: UIButton) {
        performSegue(withIdentifier: "ShowSuccessSegue", sender: self)
    }
}

// MARK: - Success ViewController

class SuccessViewController: UIViewController {
    
    var userEmail: String?
    var registrationDate: Date?
    
    @IBOutlet weak var messageLabel: UILabel!
    @IBOutlet weak var emailLabel: UILabel!
    @IBOutlet weak var dateLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        displaySuccess()
    }
    
    private func displaySuccess() {
        messageLabel.text = "Registration Successful!"
        emailLabel.text = "Email: \(userEmail ?? "N/A")"
        
        if let date = registrationDate {
            let formatter = DateFormatter()
            formatter.dateStyle = .long
            formatter.timeStyle = .medium
            dateLabel.text = "Date: \(formatter.string(from: date))"
        }
    }
}

Method 3: Delegation Pattern

The Delegation pattern is one of the most important design patterns in iOS development. It allows one object to send messages to another object when specific events occur. This is the primary method for backward data flow.

How It Works

  1. Define a protocol that declares methods for communication
  2. The delegating class has a weak delegate property
  3. The delegate class conforms to the protocol
  4. The delegating class calls delegate methods when needed
  5. The delegate implements the methods to handle events

Advantages

  • ✅ Perfect for backward data flow
  • ✅ Loose coupling through protocols
  • ✅ Type-safe communication
  • ✅ Clear contract between objects
  • ✅ Apple's preferred pattern

Disadvantages

  • ❌ Requires protocol definition
  • ❌ More boilerplate code
  • ❌ One-to-one relationship only
  • ❌ Must manage weak references

Basic Delegation Implementation

import UIKit

// MARK: - Step 1: Define the Protocol

protocol ColorPickerDelegate: AnyObject {
    func colorPicker(_ picker: ColorPickerViewController, didSelectColor color: UIColor)
    func colorPickerDidCancel(_ picker: ColorPickerViewController)
}

// MARK: - Step 2: Delegating ViewController (Child)

class ColorPickerViewController: UIViewController {
    
    // Weak delegate property to avoid retain cycles
    weak var delegate: ColorPickerDelegate?
    
    private var selectedColor: UIColor = .white
    
    // UI Elements
    private let colorPickerView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    private let redSlider: UISlider = {
        let slider = UISlider()
        slider.minimumValue = 0
        slider.maximumValue = 1
        slider.value = 1
        slider.translatesAutoresizingMaskIntoConstraints = false
        return slider
    }()
    
    private let greenSlider: UISlider = {
        let slider = UISlider()
        slider.minimumValue = 0
        slider.maximumValue = 1
        slider.value = 1
        slider.translatesAutoresizingMaskIntoConstraints = false
        return slider
    }()
    
    private let blueSlider: UISlider = {
        let slider = UISlider()
        slider.minimumValue = 0
        slider.maximumValue = 1
        slider.value = 1
        slider.translatesAutoresizingMaskIntoConstraints = false
        return slider
    }()
    
    private let previewView: UIView = {
        let view = UIView()
        view.layer.cornerRadius = 50
        view.layer.borderWidth = 2
        view.layer.borderColor = UIColor.systemGray.cgColor
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    private let selectButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Select Color", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    private let cancelButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Cancel", for: .normal)
        button.backgroundColor = .systemGray5
        button.setTitleColor(.systemBlue, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Choose Color"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
        updatePreview()
    }
    
    private func setupUI() {
        view.addSubview(previewView)
        view.addSubview(redSlider)
        view.addSubview(greenSlider)
        view.addSubview(blueSlider)
        view.addSubview(selectButton)
        view.addSubview(cancelButton)
        
        NSLayoutConstraint.activate([
            previewView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40),
            previewView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            previewView.widthAnchor.constraint(equalToConstant: 100),
            previewView.heightAnchor.constraint(equalToConstant: 100),
            
            redSlider.topAnchor.constraint(equalTo: previewView.bottomAnchor, constant: 40),
            redSlider.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            redSlider.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            greenSlider.topAnchor.constraint(equalTo: redSlider.bottomAnchor, constant: 20),
            greenSlider.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            greenSlider.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            blueSlider.topAnchor.constraint(equalTo: greenSlider.bottomAnchor, constant: 20),
            blueSlider.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            blueSlider.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            selectButton.bottomAnchor.constraint(equalTo: cancelButton.topAnchor, constant: -16),
            selectButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            selectButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            selectButton.heightAnchor.constraint(equalToConstant: 50),
            
            cancelButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
            cancelButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            cancelButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            cancelButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func setupActions() {
        redSlider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged)
        greenSlider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged)
        blueSlider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged)
        
        selectButton.addTarget(self, action: #selector(selectButtonTapped), for: .touchUpInside)
        cancelButton.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside)
    }
    
    @objc private func sliderValueChanged() {
        updatePreview()
    }
    
    private func updatePreview() {
        selectedColor = UIColor(
            red: CGFloat(redSlider.value),
            green: CGFloat(greenSlider.value),
            blue: CGFloat(blueSlider.value),
            alpha: 1.0
        )
        previewView.backgroundColor = selectedColor
    }
    
    @objc private func selectButtonTapped() {
        // Call delegate method to pass data back
        delegate?.colorPicker(self, didSelectColor: selectedColor)
        navigationController?.popViewController(animated: true)
    }
    
    @objc private func cancelButtonTapped() {
        // Call delegate method for cancellation
        delegate?.colorPickerDidCancel(self)
        navigationController?.popViewController(animated: true)
    }
}

// MARK: - Step 3: Delegate ViewController (Parent)

class MainColorViewController: UIViewController {
    
    private let colorDisplayView: UIView = {
        let view = UIView()
        view.backgroundColor = .white
        view.layer.cornerRadius = 20
        view.layer.borderWidth = 2
        view.layer.borderColor = UIColor.systemGray.cgColor
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    private let colorLabel: UILabel = {
        let label = UILabel()
        label.text = "No color selected"
        label.textAlignment = .center
        label.font = .systemFont(ofSize: 18, weight: .medium)
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let chooseColorButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Choose Color", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Color Manager"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
    }
    
    private func setupUI() {
        view.addSubview(colorDisplayView)
        view.addSubview(colorLabel)
        view.addSubview(chooseColorButton)
        
        NSLayoutConstraint.activate([
            colorDisplayView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40),
            colorDisplayView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            colorDisplayView.widthAnchor.constraint(equalToConstant: 200),
            colorDisplayView.heightAnchor.constraint(equalToConstant: 200),
            
            colorLabel.topAnchor.constraint(equalTo: colorDisplayView.bottomAnchor, constant: 20),
            colorLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            colorLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            chooseColorButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -40),
            chooseColorButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            chooseColorButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            chooseColorButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func setupActions() {
        chooseColorButton.addTarget(self, action: #selector(chooseColorButtonTapped), for: .touchUpInside)
    }
    
    @objc private func chooseColorButtonTapped() {
        let colorPickerVC = ColorPickerViewController()
        
        // Step 4: Set self as the delegate
        colorPickerVC.delegate = self
        
        navigationController?.pushViewController(colorPickerVC, animated: true)
    }
}

// MARK: - Step 5: Conform to Protocol and Implement Methods

extension MainColorViewController: ColorPickerDelegate {
    
    func colorPicker(_ picker: ColorPickerViewController, didSelectColor color: UIColor) {
        // Receive data from child ViewController
        colorDisplayView.backgroundColor = color
        colorLabel.text = "Color selected successfully!"
        
        // Save color to UserDefaults
        saveColor(color)
        
        // Perform additional actions
        animateColorChange()
    }
    
    func colorPickerDidCancel(_ picker: ColorPickerViewController) {
        colorLabel.text = "Color selection cancelled"
        
        // Show cancellation feedback
        showCancellationAlert()
    }
    
    private func saveColor(_ color: UIColor) {
        var red: CGFloat = 0
        var green: CGFloat = 0
        var blue: CGFloat = 0
        var alpha: CGFloat = 0
        
        color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
        
        UserDefaults.standard.set(Double(red), forKey: "savedColorRed")
        UserDefaults.standard.set(Double(green), forKey: "savedColorGreen")
        UserDefaults.standard.set(Double(blue), forKey: "savedColorBlue")
    }
    
    private func animateColorChange() {
        UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.3) {
            self.colorDisplayView.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)
        } completion: { _ in
            UIView.animate(withDuration: 0.3) {
                self.colorDisplayView.transform = .identity
            }
        }
    }
    
    private func showCancellationAlert() {
        let alert = UIAlertController(
            title: "Cancelled",
            message: "Color selection was cancelled",
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

Advanced Delegation with Multiple Data Types

import UIKit

// MARK: - Complex Protocol with Multiple Methods

protocol UserProfileEditorDelegate: AnyObject {
    func userProfileEditor(_ editor: UserProfileEditorViewController, didUpdateProfile profile: UserProfile)
    func userProfileEditor(_ editor: UserProfileEditorViewController, didUpdateAvatar image: UIImage)
    func userProfileEditor(_ editor: UserProfileEditorViewController, didChangePrivacySettings settings: PrivacySettings)
    func userProfileEditorDidRequestDelete(_ editor: UserProfileEditorViewController)
    func userProfileEditorDidCancel(_ editor: UserProfileEditorViewController)
}

// MARK: - Data Models

struct UserProfile {
    var firstName: String
    var lastName: String
    var email: String
    var phoneNumber: String?
    var bio: String?
    var dateOfBirth: Date?
    var location: String?
    var website: URL?
}

struct PrivacySettings {
    var profileVisibility: ProfileVisibility
    var showEmail: Bool
    var showPhoneNumber: Bool
    var allowMessages: Bool
    var allowFriendRequests: Bool
}

enum ProfileVisibility {
    case publicProfile
    case friendsOnly
    case privateProfile
}

// MARK: - Editor ViewController

class UserProfileEditorViewController: UIViewController {
    
    weak var delegate: UserProfileEditorDelegate?
    
    var currentProfile: UserProfile?
    var currentPrivacySettings: PrivacySettings?
    
    // UI Elements
    private let scrollView: UIScrollView = {
        let scroll = UIScrollView()
        scroll.translatesAutoresizingMaskIntoConstraints = false
        return scroll
    }()
    
    private let contentView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    private let avatarImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        imageView.layer.cornerRadius = 60
        imageView.backgroundColor = .systemGray5
        imageView.isUserInteractionEnabled = true
        imageView.translatesAutoresizingMaskIntoConstraints = false
        return imageView
    }()
    
    private let firstNameTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "First Name"
        textField.borderStyle = .roundedRect
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let lastNameTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Last Name"
        textField.borderStyle = .roundedRect
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let emailTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Email"
        textField.borderStyle = .roundedRect
        textField.keyboardType = .emailAddress
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let phoneTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Phone Number"
        textField.borderStyle = .roundedRect
        textField.keyboardType = .phonePad
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private let bioTextView: UITextView = {
        let textView = UITextView()
        textView.font = .systemFont(ofSize: 16)
        textView.layer.borderColor = UIColor.systemGray4.cgColor
        textView.layer.borderWidth = 1
        textView.layer.cornerRadius = 8
        textView.translatesAutoresizingMaskIntoConstraints = false
        return textView
    }()
    
    private let profileVisibilitySegment: UISegmentedControl = {
        let segment = UISegmentedControl(items: ["Public", "Friends", "Private"])
        segment.selectedSegmentIndex = 0
        segment.translatesAutoresizingMaskIntoConstraints = false
        return segment
    }()
    
    private let showEmailSwitch: UISwitch = {
        let toggle = UISwitch()
        toggle.translatesAutoresizingMaskIntoConstraints = false
        return toggle
    }()
    
    private let showPhoneSwitch: UISwitch = {
        let toggle = UISwitch()
        toggle.translatesAutoresizingMaskIntoConstraints = false
        return toggle
    }()
    
    private let saveButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Save Changes", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    private let cancelButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Cancel", for: .normal)
        button.backgroundColor = .systemGray5
        button.setTitleColor(.systemBlue, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    private let deleteButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Delete Profile", for: .normal)
        button.backgroundColor = .systemRed
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Edit Profile"
        view.backgroundColor = .systemBackground
        
        setupUI()
        setupActions()
        populateFields()
    }
    
    private func setupUI() {
        view.addSubview(scrollView)
        scrollView.addSubview(contentView)
        
        contentView.addSubview(avatarImageView)
        contentView.addSubview(firstNameTextField)
        contentView.addSubview(lastNameTextField)
        contentView.addSubview(emailTextField)
        contentView.addSubview(phoneTextField)
        contentView.addSubview(bioTextView)
        contentView.addSubview(profileVisibilitySegment)
        contentView.addSubview(showEmailSwitch)
        contentView.addSubview(showPhoneSwitch)
        contentView.addSubview(saveButton)
        contentView.addSubview(cancelButton)
        contentView.addSubview(deleteButton)
        
        NSLayoutConstraint.activate([
            scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            
            contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
            
            avatarImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20),
            avatarImageView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            avatarImageView.widthAnchor.constraint(equalToConstant: 120),
            avatarImageView.heightAnchor.constraint(equalToConstant: 120),
            
            firstNameTextField.topAnchor.constraint(equalTo: avatarImageView.bottomAnchor, constant: 30),
            firstNameTextField.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            firstNameTextField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            firstNameTextField.heightAnchor.constraint(equalToConstant: 44),
            
            lastNameTextField.topAnchor.constraint(equalTo: firstNameTextField.bottomAnchor, constant: 16),
            lastNameTextField.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            lastNameTextField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            lastNameTextField.heightAnchor.constraint(equalToConstant: 44),
            
            emailTextField.topAnchor.constraint(equalTo: lastNameTextField.bottomAnchor, constant: 16),
            emailTextField.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            emailTextField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            emailTextField.heightAnchor.constraint(equalToConstant: 44),
            
            phoneTextField.topAnchor.constraint(equalTo: emailTextField.bottomAnchor, constant: 16),
            phoneTextField.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            phoneTextField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            phoneTextField.heightAnchor.constraint(equalToConstant: 44),
            
            bioTextView.topAnchor.constraint(equalTo: phoneTextField.bottomAnchor, constant: 16),
            bioTextView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            bioTextView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            bioTextView.heightAnchor.constraint(equalToConstant: 100),
            
            profileVisibilitySegment.topAnchor.constraint(equalTo: bioTextView.bottomAnchor, constant: 30),
            profileVisibilitySegment.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            profileVisibilitySegment.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            
            saveButton.topAnchor.constraint(equalTo: profileVisibilitySegment.bottomAnchor, constant: 40),
            saveButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            saveButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            saveButton.heightAnchor.constraint(equalToConstant: 50),
            
            cancelButton.topAnchor.constraint(equalTo: saveButton.bottomAnchor, constant: 12),
            cancelButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            cancelButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            cancelButton.heightAnchor.constraint(equalToConstant: 50),
            
            deleteButton.topAnchor.constraint(equalTo: cancelButton.bottomAnchor, constant: 30),
            deleteButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
            deleteButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
            deleteButton.heightAnchor.constraint(equalToConstant: 50),
            deleteButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -30)
        ])
    }
    
    private func setupActions() {
        let avatarTapGesture = UITapGestureRecognizer(target: self, action: #selector(avatarTapped))
        avatarImageView.addGestureRecognizer(avatarTapGesture)
        
        saveButton.addTarget(self, action: #selector(saveButtonTapped), for: .touchUpInside)
        cancelButton.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside)
        deleteButton.addTarget(self, action: #selector(deleteButtonTapped), for: .touchUpInside)
        
        profileVisibilitySegment.addTarget(self, action: #selector(privacySettingsChanged), for: .valueChanged)
        showEmailSwitch.addTarget(self, action: #selector(privacySettingsChanged), for: .valueChanged)
        showPhoneSwitch.addTarget(self, action: #selector(privacySettingsChanged), for: .valueChanged)
    }
    
    private func populateFields() {
        guard let profile = currentProfile else { return }
        
        firstNameTextField.text = profile.firstName
        lastNameTextField.text = profile.lastName
        emailTextField.text = profile.email
        phoneTextField.text = profile.phoneNumber
        bioTextView.text = profile.bio
        
        if let settings = currentPrivacySettings {
            switch settings.profileVisibility {
            case .publicProfile: profileVisibilitySegment.selectedSegmentIndex = 0
            case .friendsOnly: profileVisibilitySegment.selectedSegmentIndex = 1
            case .privateProfile: profileVisibilitySegment.selectedSegmentIndex = 2
            }
            
            showEmailSwitch.isOn = settings.showEmail
            showPhoneSwitch.isOn = settings.showPhoneNumber
        }
    }
    
    @objc private func avatarTapped() {
        let imagePicker = UIImagePickerController()
        imagePicker.delegate = self
        imagePicker.sourceType = .photoLibrary
        imagePicker.allowsEditing = true
        present(imagePicker, animated: true)
    }
    
    @objc private func saveButtonTapped() {
        guard validateInputs() else { return }
        
        // Create updated profile
        let updatedProfile = UserProfile(
            firstName: firstNameTextField.text ?? "",
            lastName: lastNameTextField.text ?? "",
            email: emailTextField.text ?? "",
            phoneNumber: phoneTextField.text,
            bio: bioTextView.text,
            dateOfBirth: currentProfile?.dateOfBirth,
            location: currentProfile?.location,
            website: currentProfile?.website
        )
        
        // Notify delegate about profile update
        delegate?.userProfileEditor(self, didUpdateProfile: updatedProfile)
        
        navigationController?.popViewController(animated: true)
    }
    
    @objc private func cancelButtonTapped() {
        // Notify delegate about cancellation
        delegate?.userProfileEditorDidCancel(self)
        navigationController?.popViewController(animated: true)
    }
    
    @objc private func deleteButtonTapped() {
        let alert = UIAlertController(
            title: "Delete Profile",
            message: "Are you sure you want to delete your profile? This action cannot be undone.",
            preferredStyle: .alert
        )
        
        alert.addAction(UIAlertAction(title: "Delete", style: .destructive) { [weak self] _ in
            guard let self = self else { return }
            self.delegate?.userProfileEditorDidRequestDelete(self)
            self.navigationController?.popViewController(animated: true)
        })
        
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
        
        present(alert, animated: true)
    }
    
    @objc private func privacySettingsChanged() {
        let visibility: ProfileVisibility
        switch profileVisibilitySegment.selectedSegmentIndex {
        case 0: visibility = .publicProfile
        case 1: visibility = .friendsOnly
        case 2: visibility = .privateProfile
        default: visibility = .publicProfile
        }
        
        let updatedSettings = PrivacySettings(
            profileVisibility: visibility,
            showEmail: showEmailSwitch.isOn,
            showPhoneNumber: showPhoneSwitch.isOn,
            allowMessages: currentPrivacySettings?.allowMessages ?? true,
            allowFriendRequests: currentPrivacySettings?.allowFriendRequests ?? true
        )
        
        // Notify delegate about privacy settings change
        delegate?.userProfileEditor(self, didChangePrivacySettings: updatedSettings)
    }
    
    private func validateInputs() -> Bool {
        guard let firstName = firstNameTextField.text, !firstName.isEmpty,
              let lastName = lastNameTextField.text, !lastName.isEmpty,
              let email = emailTextField.text, !email.isEmpty else {
            showAlert(message: "Please fill in all required fields")
            return false
        }
        
        return true
    }
    
    private func showAlert(message: String) {
        let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

// MARK: - UIImagePickerControllerDelegate

extension UserProfileEditorViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        if let editedImage = info[.editedImage] as? UIImage {
            avatarImageView.image = editedImage
            
            // Notify delegate about avatar change
            delegate?.userProfileEditor(self, didUpdateAvatar: editedImage)
        } else if let originalImage = info[.originalImage] as? UIImage {
            avatarImageView.image = originalImage
            delegate?.userProfileEditor(self, didUpdateAvatar: originalImage)
        }
        
        dismiss(animated: true)
    }
    
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        dismiss(animated: true)
    }
}

// MARK: - Parent ViewController Implementing Delegate

class UserProfileViewController: UIViewController {
    
    private var userProfile: UserProfile?
    private var privacySettings: PrivacySettings?
    
    private let profileImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        imageView.layer.cornerRadius = 60
        imageView.backgroundColor = .systemGray5
        imageView.translatesAutoresizingMaskIntoConstraints = false
        return imageView
    }()
    
    private let nameLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 24, weight: .bold)
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let emailLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 16)
        label.textColor = .secondaryLabel
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let editButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Edit Profile", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "My Profile"
        view.backgroundColor = .systemBackground
        
        setupUI()
        loadUserProfile()
    }
    
    private func setupUI() {
        view.addSubview(profileImageView)
        view.addSubview(nameLabel)
        view.addSubview(emailLabel)
        view.addSubview(editButton)
        
        editButton.addTarget(self, action: #selector(editButtonTapped), for: .touchUpInside)
        
        NSLayoutConstraint.activate([
            profileImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40),
            profileImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            profileImageView.widthAnchor.constraint(equalToConstant: 120),
            profileImageView.heightAnchor.constraint(equalToConstant: 120),
            
            nameLabel.topAnchor.constraint(equalTo: profileImageView.bottomAnchor, constant: 20),
            nameLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            nameLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            emailLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 8),
            emailLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            emailLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            editButton.topAnchor.constraint(equalTo: emailLabel.bottomAnchor, constant: 40),
            editButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            editButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            editButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    private func loadUserProfile() {
        userProfile = UserProfile(
            firstName: "John",
            lastName: "Doe",
            email: "john.doe@example.com",
            phoneNumber: "+1234567890",
            bio: "iOS Developer"
        )
        
        privacySettings = PrivacySettings(
            profileVisibility: .publicProfile,
            showEmail: true,
            showPhoneNumber: false,
            allowMessages: true,
            allowFriendRequests: true
        )
        
        updateUI()
    }
    
    private func updateUI() {
        guard let profile = userProfile else { return }
        
        nameLabel.text = "\(profile.firstName) \(profile.lastName)"
        emailLabel.text = profile.email
    }
    
    @objc private func editButtonTapped() {
        let editorVC = UserProfileEditorViewController()
        editorVC.currentProfile = userProfile
        editorVC.currentPrivacySettings = privacySettings
        
        // Set self as delegate
        editorVC.delegate = self
        
        navigationController?.pushViewController(editorVC, animated: true)
    }
}

// MARK: - Conform to Delegate Protocol

extension UserProfileViewController: UserProfileEditorDelegate {
    
    func userProfileEditor(_ editor: UserProfileEditorViewController, didUpdateProfile profile: UserProfile) {
        // Receive updated profile
        self.userProfile = profile
        updateUI()
        
        // Save to database or API
        saveProfileToServer(profile)
        
        // Show success message
        showSuccessAlert(message: "Profile updated successfully!")
    }
    
    func userProfileEditor(_ editor: UserProfileEditorViewController, didUpdateAvatar image: UIImage) {
        // Receive new avatar
        profileImageView.image = image
        
        // Upload to server
        uploadAvatarToServer(image)
    }
    
    func userProfileEditor(_ editor: UserProfileEditorViewController, didChangePrivacySettings settings: PrivacySettings) {
        // Receive privacy settings changes
        self.privacySettings = settings
        
        // Save settings
        savePrivacySettings(settings)
        
        print("Privacy settings updated: \(settings)")
    }
    
    func userProfileEditorDidRequestDelete(_ editor: UserProfileEditorViewController) {
        // Handle profile deletion request
        let alert = UIAlertController(
            title: "Confirm Deletion",
            message: "Your profile will be permanently deleted. Continue?",
            preferredStyle: .alert
        )
        
        alert.addAction(UIAlertAction(title: "Delete", style: .destructive) { [weak self] _ in
            self?.deleteProfile()
        })
        
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
        
        present(alert, animated: true)
    }
    
    func userProfileEditorDidCancel(_ editor: UserProfileEditorViewController) {
        print("Profile editing cancelled")
    }
    
    // Helper methods
    
    private func saveProfileToServer(_ profile: UserProfile) {
        // API call to save profile
        print("Saving profile to server: \(profile)")
    }
    
    private func uploadAvatarToServer(_ image: UIImage) {
        // API call to upload avatar
        print("Uploading avatar to server")
    }
    
    private func savePrivacySettings(_ settings: PrivacySettings) {
        // Save privacy settings
        print("Saving privacy settings")
    }
    
    private func deleteProfile() {
        // API call to delete profile
        print("Deleting profile")
        
        // Navigate back or logout
        navigationController?.popToRootViewController(animated: true)
    }
    
    private func showSuccessAlert(message: String) {
        let alert = UIAlertController(title: "Success", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

Optional Protocol Methods

import UIKit

// MARK: - Protocol with Optional Methods

@objc protocol FormDelegate: AnyObject {
    func formDidSubmit(_ form: FormViewController, withData data: [String: Any])
    
    // Optional methods
    @objc optional func formDidStartEditing(_ form: FormViewController)
    @objc optional func form(_ form: FormViewController, didUpdateField fieldName: String, value: Any)
    @objc optional func formDidCancel(_ form: FormViewController)
    @objc optional func form(_ form: FormViewController, didEncounterError error: Error)
}

// MARK: - Form View Controller

class FormViewController: UIViewController {
    
    weak var delegate: FormDelegate?
    
    private var formData: [String: Any] = [:]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }
    
    private func setupUI() {
        // UI setup code
    }
    
    private func notifyEditingStarted() {
        // Call optional delegate method
        delegate?.formDidStartEditing?(self)
    }
    
    private func notifyFieldUpdate(fieldName: String, value: Any) {
        formData[fieldName] = value
        
        // Call optional delegate method
        delegate?.form?(self, didUpdateField: fieldName, value: value)
    }
    
    @objc private func submitButtonTapped() {
        // Call required delegate method
        delegate?.formDidSubmit(self, withData: formData)
    }
    
    @objc private func cancelButtonTapped() {
        // Call optional delegate method
        delegate?.formDidCancel?(self)
    }
    
    private func handleError(_ error: Error) {
        // Call optional delegate method
        delegate?.form?(self, didEncounterError: error)
    }
}

// MARK: - Implementing Optional Methods

class ParentViewController: UIViewController, FormDelegate {
    
    // Required method
    func formDidSubmit(_ form: FormViewController, withData data: [String : Any]) {
        print("Form submitted with data: \(data)")
    }
    
    // Optional methods - implement only what you need
    func form(_ form: FormViewController, didUpdateField fieldName: String, value: Any) {
        print("Field \(fieldName) updated to: \(value)")
    }
    
    func formDidCancel(_ form: FormViewController) {
        print("Form cancelled")
    }
    
    // formDidStartEditing and didEncounterError are not implemented
    // No error because they're marked as @objc optional
}

Method 4: Closures and Callbacks

Closures (also known as callbacks or completion handlers) provide a modern, Swift-native way to pass data between ViewControllers. They're especially useful for asynchronous operations and simple backward data flow.

How It Works

  1. Define a closure property in the source ViewController
  2. The destination ViewController assigns a closure to this property
  3. When ready, the source ViewController executes the closure with data
  4. The destination ViewController receives and processes the data

Advantages

  • ✅ Swift-native and modern
  • ✅ Less boilerplate than delegates
  • ✅ Perfect for simple callbacks

Comments

Popular posts from this blog

Complete iOS Developer Guide - Swift 5 by @hiren_syl |  You Must Have To Know 😎 

Debugging

Swift Fundamentals You Can’t Miss! A List by @hiren_syl