MVPパターン

MVPパターンとは
---
MVPには2つの種類が存在する。
①,Passive方式（フロー同期）
絶対にPresentarを通る設計になる。(ViewはModelに直接アクセスできない)
データの流れ(Passive方式、フロー同期)
1,ユーザー操作
2,入力、タップなどのイベント
3,Presentaｒで受け取る（Input）
4,Modelに通知してデータをもらってくる
5,PresentarでUIの更新を指示していく
⇨コードは長くなりそうだけどデータの移動は読みやすそう？

フロー同期　Presentarの処理中に何らかのタイミングでViewを更新させていく状態管理の方法

②,SuperVising方式(フロー同期、オブザーバー同期)
1ユーザー操作
2,入力、タップなどのイベント
3、Presentarで受け取り(Input)Modelに値を渡す
4、データが変更された通知を直接Viewに渡してView自身に更新させる

オブザーバー同期：Model の変更通知を受け取り、View自身のロジックで更新していく

なぜMVPを実装するのか
--

よく言われるFatViewControllerにならないようにするため.
UIの変更やライフサイクルの管理などもViewControllerがしなくてはならないので、可読性が下がってしまうから。

Model
--
実際にデータの処理を行う。ドメインロジックを担う
View
--
Presentarにイベントを渡して（Input)、outputとしてUI更新の指示をもらい更新する。
Passive方式では Viewは基本的に受動的にUI更新の指示を待つだけの存在となる。
また、Modelと直接アクセスしないためModelとViewが疎の状態となっている。
Presentar
---
ModelとViewを仲介する役割を持つ。
プレゼンテーションロジックを担当する。プレゼンテーションロジックはユーザーの挙動によって、アプリケーションの動きを指示していくロジックのこと

感想
MVCに慣れているので違う設計は楽しい！
もっとIOS設計について学びたい！！


PokemonAPIでポケモン図鑑を作る（MVP,Passive View方式）
https://pokeapi.co/
簡単にPokemonAPIを使用してMVPのアプリを作成しました.
コピペではつかえませんが、、、

Model

```swift
protocol GetPokemonDataInput {
    func fetchPokemon(completion:@escaping([PokemonModel])->Void)
}
class PokemonDataModel:GetPokemonDataInput {
    
    func fetchPokemon(completion:@escaping([PokemonModel])->Void) {
        var pokemonArray = [PokemonModel]()
        let dispatchGroup = DispatchGroup()
        for i in 1...386 {
            dispatchGroup.enter()
            let pokemonURL = "https://pokeapi.co/api/v2/pokemon-species/\(i)"
            let pokemonDetailURL = "https://pokeapi.co/api/v2/pokemon/\(i)"
            let keyNumber = i > 251 ? 23 : i > 151 ? 26 : 29
            let keyNumber2 = i > 251 ? 31 : i > 151 ? 34 : 37
            let keyNumber3 = i > 251 ? 47 : i > 151 ? 50 : 38
            AF.request(pokemonURL).responseJSON { response in
                switch response.result {
                case .success:
                    do {
                        guard let safeData = response.data else { return }
                        let data = try JSONDecoder().decode(PokemonData.self,from:safeData)
                        let name = data.names[0].name
                        let id = data.id
                        let genera = data.genera[0].genus
                        let explain = data.flavor_text_entries[keyNumber].flavor_text
                        let explain2 = data.flavor_text_entries[keyNumber2].flavor_text
                        let explain3 = data.flavor_text_entries[keyNumber3].flavor_text
                        AF.request(pokemonDetailURL).responseJSON { response in
                            switch response.result {
                            case .success:
                                do {
                                    defer { dispatchGroup.leave() }
                                    let data = try JSONDecoder().decode(PokemonDetail.self, from: response.data!)
                                    let height = data.height
                                    let weight = data.weight
                                    let imageUrl = data.sprites.front_default
                                    let type = data.types[0].type.name
                                    let pokemon = PokemonModel(name: name, id: id, genus: genera, explain: explain, explain2: explain2, explain3: explain3, height: height, weight: weight, urlImage: imageUrl,type:type)
                                    pokemonArray.append(pokemon)
                                } catch {
                                    print(error)
                                }
                            case .failure(let error):
                                print(error)
                            }
                        }
                    } catch {
                        print(error)
                    }
                case .failure(let error):
                    print(error)
                }
            }
        }
        dispatchGroup.notify(queue: .main) {
            completion(pokemonArray)
        }
    }
}

```

Presentar

```swift
import Foundation
protocol PokemonPresentarInput:AnyObject {
    var numberOfPokemon:Int { get }
    var numberOfSavePokemon:Int { get }
    func didSelectTap(indexPath:IndexPath)
    func viewDidLoad()
    func pokemon(row:Int)->PokemonModel?
    func savePokemon(row:Int)->PokemonModel?
    func addPokemon(index:Int)
    func searchTextInput(text:String)
    func deleteFavorite(index:Int)
}
protocol PokemonPresentarOutput:AnyObject {
    func gotoPokemonDetail(pokemon:PokemonModel)
    func pokemonDataOutPut(pokemon:[PokemonModel])
    func filterPokemonOutput(pokemon:[PokemonModel])
}
protocol PokemonFavoritePresentarOutput:AnyObject {
    func deleteComplete()
}
class PokemonPresentar:PokemonPresentarInput {
    //Properties
    private var pokemons = [PokemonModel]()
    private weak var viewOutput:PokemonPresentarOutput!
    private var pokemonDataModel:GetPokemonDataInput
    private var savePokemons = UserDefaultsRepository.loadFromUserDefaults()
    private weak var favoriteOutput:PokemonFavoritePresentarOutput!
    
    var numberOfPokemon: Int {
        return pokemons.count
    }
    var numberOfSavePokemon:Int {
        return savePokemons.count
    }
    
    //Mark initialize
    init(viewOutput:PokemonPresentarOutput,modelInput:GetPokemonDataInput) {
        self.viewOutput = viewOutput
        self.pokemonDataModel = modelInput
    }
    
    init(favoriteViewOutput:PokemonFavoritePresentarOutput,modelInput:GetPokemonDataInput) {
        self.favoriteOutput = favoriteViewOutput
        self.pokemonDataModel = modelInput
    }
    
    //Mark inputMethod
    func viewDidLoad() {
        print(#function)
        pokemonDataModel.fetchPokemon { [weak self] pokemons in
            guard let self = self else { return }
            let pokemonArray = pokemons.sorted(by: { $0.id < $1.id })
            self.pokemons = pokemonArray
            self.viewOutput.pokemonDataOutPut(pokemon: self.pokemons)
        }
    }
    
    func addPokemon(index: Int) {
        print(#function)
        self.savePokemons =  UserDefaultsRepository.loadFromUserDefaults()
        self.savePokemons.append(pokemons[index])
        UserDefaultsRepository.saveToUserDefaults(pokemon: savePokemons)
    }
   
    func pokemon(row:Int)->PokemonModel? {
        print(#function)
        return  row >= pokemons.count ? nil:pokemons[row]
    }
    func savePokemon(row:Int)->PokemonModel? {
        print(#function)
        return row >= savePokemons.count ? nil:savePokemons[row]
    }
    
    func didSelectTap(indexPath:IndexPath) {
        print(#function)
        let pokemon = pokemons[indexPath.row]
        viewOutput.gotoPokemonDetail(pokemon: pokemon)
    }
    
    func searchTextInput(text: String) {
        print(#function)
        let filterPokemonArray = self.pokemons.filter { return $0.name.contains(text)}
        self.pokemons = filterPokemonArray
        viewOutput.filterPokemonOutput(pokemon: self.pokemons)
    }
    
    func deleteFavorite(index: Int) {
        self.savePokemons = UserDefaultsRepository.deleteFromUserDefaults(index: index, pokemons: savePokemons)
        favoriteOutput.deleteComplete()
    }
}

```

View

```swift
import UIKit

class PokemonViewController: UICollectionViewController{
    //Properties
    private let cellId = "cellId"
    private let headerId = "headerId"
    private var pokemonPresentar:PokemonPresentarInput!
    private var indicatorView = UIActivityIndicatorView()
    private var searchController = UISearchController()
    //Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        setupCollectionView()
        pokemonPresentar = PokemonPresentar(viewOutput: self, modelInput: PokemonDataModel())
        pokemonPresentar.viewDidLoad()
        setupSeachController()
    }
    //Mark setupMethod
    private func setupCollectionView() {
        collectionView.register(PokemonCell.self, forCellWithReuseIdentifier: cellId)
        collectionView.register(PokemonHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerId)
        indicatorView.center = view.center
        indicatorView.style = .whiteLarge
        indicatorView.color = .gray
        view.addSubview(indicatorView)
        indicatorView.startAnimating()
    }
    //Initialize
    init() {
        super.init(collectionViewLayout: UICollectionViewFlowLayout())
    }
    required init?(coder: NSCoder) {
        fatalError()
    }
    
    //setupMethod
    private func setupSeachController() {
        navigationItem.title = "Pokemon Picture Book"
        searchController = UISearchController(searchResultsController: nil)
        searchController.searchResultsUpdater = self
        searchController.obscuresBackgroundDuringPresentation = false
        navigationItem.searchController = searchController
        navigationItem.hidesSearchBarWhenScrolling = true
    }
}
//Mark collectionViewdelegate Method
extension PokemonViewController {
    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return pokemonPresentar.numberOfPokemon
    }
    
    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! PokemonCell
        guard let pokemon = pokemonPresentar.pokemon(row: indexPath.row) else { return cell}
        cell.pokemon = pokemon
        cell.delegate = self
        return cell
    }
    override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerId, for: indexPath) as! PokemonHeader
        header.delegate = self
        return header
    }
    override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        print(#function)
        pokemonPresentar.didSelectTap(indexPath: indexPath)
        
    }
}
//Mark collectionviewflowlayoutMethod
extension PokemonViewController :UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: view.frame.width, height: 60)
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        return CGSize(width: view.frame.width, height: 150)
    }
}
//Mark PokemonSaveDelegate
extension PokemonViewController:PokemonSaveDelegate {
    func addPokemon(cell: PokemonCell) {
        print(#function)
        let indexTapped = collectionView.indexPath(for: cell)
        guard let index = indexTapped?[1] else { return }
        let vc = UIAlertController(title: "お気に入りに追加しますか", message: "", preferredStyle: .alert)
        let alertAction = UIAlertAction(title: "はい", style: .default) { _ in
            self.pokemonPresentar.addPokemon(index: index)
        }
        let cancleAction = UIAlertAction(title: "いいえ", style: .cancel) { _ in
            print("canle")
        }
        vc.addAction(alertAction)
        vc.addAction(cancleAction)
        present(vc, animated: true, completion: nil)
    }
}
//Mark PokemonHeaderDelegate
extension PokemonViewController:PokemonHeaderDelegate {
    func allFavoritePokemon() {
        print(#function)
        let vc = FavoritePokemonController()
        navigationController?.pushViewController(vc, animated: true)
    }
}
//Mark PokemonPresarOutput
extension PokemonViewController :PokemonPresentarOutput {
    func filterPokemonOutput(pokemon: [PokemonModel]) {
        print(#function)
        DispatchQueue.main.async {
            self.collectionView.reloadData()
        }
    }
    
    func pokemonDataOutPut(pokemon: [PokemonModel]) {
        print(#function)
        DispatchQueue.main.async {
            self.indicatorView.stopAnimating()
            self.collectionView.reloadData()
        }
    }
    
    func gotoPokemonDetail(pokemon: PokemonModel) {
        print(#function)
        let vc = PokemonDetailController(pokemon: pokemon)
        navigationController?.pushViewController(vc, animated: true)
    }
}
//Mark searchResultUpdating
extension PokemonViewController:UISearchResultsUpdating {
    func updateSearchResults(for searchController: UISearchController) {
        print(#function)
        guard let text = searchController.searchBar.text else { return }
        if !text.isEmpty {
            pokemonPresentar.searchTextInput(text: text)
        } else {
            pokemonPresentar.viewDidLoad()
        }
    }
}

```