UIKit delegates and protocols

Sofia Martinez Jan 2026
1 tab
import UIKit

// MARK: - Custom Delegate Protocol
protocol PostCellDelegate: AnyObject {
    func postCell(_ cell: PostTableViewCell, didTapLike post: Post)
    func postCell(_ cell: PostTableViewCell, didTapComment post: Post)
    func postCell(_ cell: PostTableViewCell, didTapShare post: Post)
}

class PostTableViewCell: UITableViewCell {
    weak var delegate: PostCellDelegate?
    private var post: Post?

    private let likeButton: UIButton = {
        let button = UIButton(type: .system)
        button.setImage(UIImage(systemName: "heart"), for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    private let commentButton: UIButton = {
        let button = UIButton(type: .system)
        button.setImage(UIImage(systemName: "bubble.left"), for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupUI()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setupUI() {
        contentView.addSubview(likeButton)
        contentView.addSubview(commentButton)

        likeButton.addTarget(self, action: #selector(likeTapped), for: .touchUpInside)
        commentButton.addTarget(self, action: #selector(commentTapped), for: .touchUpInside)

        NSLayoutConstraint.activate([
            likeButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -12),
            likeButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),

            commentButton.centerYAnchor.constraint(equalTo: likeButton.centerYAnchor),
            commentButton.leadingAnchor.constraint(equalTo: likeButton.trailingAnchor, constant: 20)
        ])
    }

    func configure(with post: Post) {
        self.post = post
    }

    @objc private func likeTapped() {
        guard let post = post else { return }
        delegate?.postCell(self, didTapLike: post)
    }

    @objc private func commentTapped() {
        guard let post = post else { return }
        delegate?.postCell(self, didTapComment: post)
    }
}

// MARK: - View Controller Implementing Delegate
class FeedViewController: UIViewController {
    private let tableView = UITableView()
    private var posts: [Post] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        setupTableView()
    }

    private func setupTableView() {
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(PostTableViewCell.self, forCellReuseIdentifier: "PostCell")
    }
}

extension FeedViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return posts.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath) as! PostTableViewCell
        cell.configure(with: posts[indexPath.row])
        cell.delegate = self
        return cell
    }
}

extension FeedViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        let post = posts[indexPath.row]
        // Navigate to detail
    }
}

extension FeedViewController: PostCellDelegate {
    func postCell(_ cell: PostTableViewCell, didTapLike post: Post) {
        print("Like tapped for post \(post.id)")
        // Handle like action
    }

    func postCell(_ cell: PostTableViewCell, didTapComment post: Post) {
        print("Comment tapped for post \(post.id)")
        // Navigate to comments
    }

    func postCell(_ cell: PostTableViewCell, didTapShare post: Post) {
        print("Share tapped for post \(post.id)")
        // Show share sheet
    }
}

// MARK: - Protocol with Associated Type
protocol DataProvider {
    associatedtype Item
    func fetchItems(completion: @escaping ([Item]) -> Void)
}

class PostsDataProvider: DataProvider {
    typealias Item = Post

    func fetchItems(completion: @escaping ([Post]) -> Void) {
        // Fetch posts from API
        completion([])
    }
}
1 file · swift Explain with highlit

Delegates enable objects to communicate events and customize behavior. The delegate pattern uses protocols to define a contract—I create a protocol with optional and required methods, and the delegating object holds a weak reference to avoid retain cycles. Common system delegates include UITableViewDelegate, UITextFieldDelegate, and UICollectionViewDelegate. For custom delegates, I define protocols for specific responsibilities like network callbacks or view controller events. The weak keyword prevents retain cycles when parent objects delegate to children. Protocols can have associated types with associatedtype for generic constraints. This pattern promotes loose coupling and testability through dependency injection.