Swiftのdelegateについて


はじめに

TableViewCell内にボタンを配置した際の、delegateについての備忘録になります。

動作環境

【Xcode】Version 12.3
【Swift】Version 5.3.2

実装後の画面

上のセルから順番にボタンをタップした際の出力結果

0番目のボタンがタップされました!
1番目のボタンがタップされました!
2番目のボタンがタップされました!
3番目のボタンがタップされました!
4番目のボタンがタップされました!

実装コード

TableViewCell.swift
import UIKit

// (1)
protocol TableViewCellDelegate {
    func buttonTapAction()
}

class TableViewCell: UITableViewCell {

    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var button: UIButton!

    var delegate: TableViewCellDelegate? // (2)

    var cellDone: (()->Void)?

    override func awakeFromNib() {
        super.awakeFromNib()
        button.layer.masksToBounds = true
        button.layer.cornerRadius = 5
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }

    @IBAction func buttonTapAction(_ sender: Any) {
        cellDone?()
        delegate?.buttonTapAction() // (3)
    }

}
ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!

    private let CELL_IDENTIFIER = "Cell"
    var selectedCellIndex: IndexPath?

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

}

extension ViewController: UITableViewDelegate, UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: CELL_IDENTIFIER, for: indexPath) as? TableViewCell else {
            return UITableViewCell()
        }
        cell.delegate = self // (4)
        cell.cellDone = { [weak self] in
            self?.selectedCellIndex = indexPath
        }
        cell.label.text = "セル:\(indexPath.row)"
        return cell
    }

}

// (4)
extension ViewController: TableViewCellDelegate {
    func buttonTapAction() {
        if let selectedCell = selectedCellIndex {
            print("\(selectedCell.row)番目のボタンがタップされました!")
        }
    }
}

実装詳細

1. protocolで宣言

  • protocolを宣言します。
protocol TableViewCellDelegate {
    func buttonTapAction()
}

protocolとは

具体的な処理内容は書かず、クラスや構造体が実装するプロパティとメソッドを定義する機能です。
プロトコルを適用したクラスや構造体は、プロトコルに定義されているプロパティとメソッドを必ず実装しなければなりません。

2. delegateを宣言

  • TableViewCellクラス内にdelegateを宣言します。
var delegate: TableViewCellDelegate?

delegateとは

デザインパターンのひとつです。

delegateは英語で「人に任せる」という意味を持っており、「あるクラスから他のクラスに処理を任せる」というデザインパターンです。
実際の処理はプロトコルを適合する側で実装するため後から変更することができますが、処理を実行する側はどんな処理をするのか意識することなく、呼び出すことができます。

3. 受け渡し元のViewdelegateをセット

  • 受け渡し元( @IBAction func buttonTapAction(_ sender: Any) )にセットします。
delegate?.buttonTapAction()

4. 受け渡し先のViewdelegateを呼び出す

  • 受け渡し先( cell )に呼び出します。
cell.delegate = self

上記を追加すると、下記エラーが出ます。

Cannot assign value of type 'ViewController' to type 'TableViewCellDelegate?'
タイプ「ViewController」の値をタイプ「TableViewCellDelegate?」に割り当てることができません。

これは、上記で説明した通り、プロトコルに定義されているプロパティとメソッドを必ず実装しなければならないためになります。
下記を追加し完成です。

extension ViewController: TableViewCellDelegate {
    func buttonTapAction() {
        // ここに処理内容がはいります。
    }
}

参考