[Swift]超簡単なToDoリストをMVPアーキテクチャで実装してみる

## はじめに

現在、MVPアーキテクチャを学習中です。
さっと動かせるものを作りたいと思い、TableViewを使用したToDo形式のサンプルを作成しました。

個人の備忘録としても残しておきたいと思います。

## MVPアーキテクチャとは？

MVPに関しては過去に書いた記事に詳しく書いたので、そちらを参考にしていただければと思います。
https://qiita.com/taro-ken/items/e2432d2028134e9a7307


## 実践　

下記のようなサンプルを作成します！

![ezgif-2-fa249491a7.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1112697/78b02132-a54e-3abb-e76a-e7339f40bac2.gif)


はい、超シンプルなUIですが…笑

Cellに追加されて、タップするとその文字をアラートとして出します。

ではコードを見ていきます。

## ソースコード

まずはPresenterです。

PresenterがViewControllerからの入力を受け取り
その結果をViewControllerに出力しています。

```ruby:ToDoPresenter.swift

import Foundation

protocol ToDoPresenterInput {
    var numberOfItems: Int { get }
    func item(index: Int) -> ToDoModel
    func add(text:String?)
    func didSelect(index: Int)
    func didEditingDelete(index: IndexPath)
}

protocol ToDoPresenterOutput:AnyObject {
    func update()
    func alert(text:String?)
}

final class ToDoPresenter {
    private var output:ToDoPresenterOutput!
    private var todoModel:[ToDoModel]
    
    init(output:ToDoPresenterOutput){
        self.output = output
        self.todoModel = []
    }
}

extension ToDoPresenter:ToDoPresenterInput{
    func didSelect(index: Int) {
        output.alert(text: todoModel[index].title)
    }
    func didEditingDelete(index: IndexPath) {
        todoModel.remove(at: index.item)
        output.update()
    }
   
    func add(text:String?){
        guard let text = text, !text.isEmpty else { return }
        let todoModel = ToDoModel.init(title: text)
        self.todoModel.append(todoModel)
        output.update()
    }
   
    func item(index: Int) -> ToDoModel {
        todoModel[index]
    }
    
    var numberOfItems: Int {
        todoModel.count
    }
    
    
}

```
次はViewControllerです。

inputプロパティとinjectメソッドを用意して
ここで外部からPresenterを繋げてます。


```ruby:ToDoViewController.swift
import UIKit

final class ToDoViewController: UIViewController {
    
    @IBOutlet private weak var todoText: UITextField!
    
    @IBOutlet private weak var tableView: UITableView! {
        didSet {
            tableView.register(UINib.init(nibName: ToDoTableViewCell.className, bundle: nil), forCellReuseIdentifier: ToDoTableViewCell.className)
            tableView.delegate = self
            tableView.dataSource = self
        }
    }
    
    private var presenter: ToDoPresenterInput!
    func inject(presenter: ToDoPresenterInput) {
        self.presenter = presenter
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction private func tapAdd(_ sender: Any) {
        presenter.add(text: todoText.text)
        todoText.text = ""
    }
    
    
}

private extension ToDoViewController {
   ## //アラートを出す処理
    func showAlert(text: String?) {
        let alertVC = UIAlertController(title: "タイトル", message: text, preferredStyle: .alert)
        alertVC.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
        self.present(alertVC, animated: true, completion: nil)
    }
}

extension ToDoViewController:ToDoPresenterOutput{
    func alert(text: String?) {
        self.showAlert(text: text)
    }
    
    func update() {
        tableView.reloadData()
    }
}
extension ToDoViewController:UITableViewDelegate,UITableViewDataSource {
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: ToDoTableViewCell.className) as? ToDoTableViewCell else {
            fatalError()
        }
        let todoModel = presenter.item(index: indexPath.item)
        cell.configure(todoModel: todoModel)
        return cell
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        presenter.numberOfItems
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        presenter.didSelect(index: indexPath.row)
    }
    
    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }
    
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            presenter.didEditingDelete(index: indexPath)
        }
    }
    
}


```


画面の立ち上げはRouterクラスにて行っています。
その際、PresenterとViewControllerを繋げています。

```ruby:Router.swift
import UIKit

final class Router {
  static let shared = Router()
  private init() {}

  private var window: UIWindow?

  func showRoot(window: UIWindow) {
    guard let vc = UIStoryboard.init(name: "ToDo", bundle: nil).instantiateInitialViewController() as? ToDoViewController else {
      return
    }
   ## //presenterとvc同士を繋ぎ合う
    let presenter = ToDoPresenter(output: vc)
    vc.inject(presenter: presenter)

    let nav = UINavigationController(rootViewController: vc)
    window.rootViewController = nav
    window.makeKeyAndVisible()
    self.window = window
  }
    
  private func show(from: UIViewController, to: UIViewController, completion:(() -> Void)? = nil){
    if let nav = from.navigationController {
      nav.pushViewController(to, animated: true)
      completion?()
    } else {
      from.present(to, animated: true, completion: completion)
    }
  }
}
```

このような感じで、シンプルではありますがMVPで実装できたかと思います。

全体のソースコードは下記にまとめています！
https://github.com/taro-ken/ToDo-MVP

間違っている箇所等あれば、ご指摘お願いいたします。
ご覧いただきありがとうございました♪