3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-03-14

はじめに

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

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

MVPアーキテクチャとは?

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

実践 

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

ezgif-2-fa249491a7.gif

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

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

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

ソースコード

まずはPresenterです。

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

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を繋げてます。

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を繋げています。

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

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

3
2
1

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
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?