LoginSignup
8
8

CellにPresenter渡してみた(MVP)

Last updated at Posted at 2024-01-15

仕事でMVPアーキテクチャを用いてiOSアプリの実装をしているときにCellにPresenterを渡してみた結果かなりコード量を削減できたのでメモ程度にここに残しておきます。

final class TestViewController: UIViewController {

    typealias Cell = TestTableViewCell
    private var presenter: TestPresenterInput!

    override func viewDidLoad() {
    super.viewDidLoad()
    presenter = TestPresenter(view: self)

}

extension TestViewController: TestPresenterOutput {

    // View側の描画処理を記述

}

上記がVCのPresenterの設定だとして

extension TestViewController: UITableViewDelegate, UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // 使用している配列の個数をReturn
    }

    func func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier, for: indexPath) as! Cell
        cell.configure(presenter: presenter) // cell描画時にPresenterを持たせる
        return cell
    }
    
}

上記のようにcellの描画関数に引数としてPresenterを持たせることでCell側から直接Presenterの関数にアクセスできるようにします。

final class TestTableViewCell: UITableViewCell {

    static let identifier = "TestTableViewCell"
    static func nib() -> UINib {
        UINib(nibName: identifier, bundle: nil)
    }

    @IBAction private func tappedTestButton(_sender: UIButton) {
        presenter.tappedTestButton(cell: self) // CellのインスタンスをPresenterの関数の引数に渡すことでボタンタップ後の描画処理をPresenterから呼び出せるようにしています
    }

    private presenter: TestPresenter!

    func configure(presenter: TestPresenterInput) {
        self.presenter = presenter // Cell内に定義したPresenterに代入してIBActionなどで使えるようにする
        // その他の描画処理を行う
    }

    func configureAlreadyTappedTestButton() {
        // ボタンタップ後の描画処理を行う(Presenterから呼び出す)
    }
    
}

Cellのコードは上記の通りです。VCからPresenterをもらっておけばIBActionの中からPresenterの関数を呼び出せて楽です。
Presenterの関数呼び出し時に引数としてCell自身のインスタンスを渡しておけば、ボタンタップ後の描画処理(ここではconfigureAlreadyTappedTestButton())をPresenterから直接呼び出すこともできます。

protocol TestPresenterInput: AnyObject {
    func tappedTestButton(cell: TestTableViewCell)
}

protocol TestPresenterOutput: AnyObject {
    // VC側の描画処理記述
}

final class TestPresenter {

    private weak var view: TestPresenterOutput!

    init(view: TestPresenterOutput) {
        self.view = view
    }

}

extension TestPresenter: TestPresenterInput {

    func tappedTestButton(cell: TestTableViewCell) {
        // Cellのボタンタップ時の処理を記述
        cell.configureAlreadyTappedTestButton() // Cellに定義しているボタンタップ時の描画処置をPresenterから呼び出す
    }

}

上記がPresenterのコードです。

これでCell↔️Presenter間に相互的に関数を呼び出せる状況が完成しました。
今まではVCを経由してDelegateを用いてコーディングしていて、とても追いにくいコードになっていました。
この方法なら一発で該当の関数に飛べるし、余分なDelegateがなくなってスッキリしました。

まだ試しに一回使ってみた程度でいろんなパターンに対応できているかはわからないので、使ってみて不便だったりしたポイントがあれば教えていただけると嬉しいです!

個人的にはSelfが解放されてしまうパターンなどがないかが心配なところです…🥺

本当にメモ程度で申し訳ありませんが、以上です。

2024/01/16追記:
記事投稿時にはTableViewに対して1つのCellしか使っていなかったので、コードがスッキリしたように見えましたが、複数になってくるとどうなんだろうという意見をいただきました。
CellごとにInputを作成してそれぞれのCellのOutputをPresenterに渡していく方がいいのか、やりすぎると逆に複雑になってしまうのか。。迷いどころです。
いい折り合いをつけてからもっといい設計にして投稿しなおしたいです。

8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8