2
0

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 3 years have passed since last update.

VIPER学ぼう 

Last updated at Posted at 2021-05-16

目次

1.はじめに
2.VIPERとは
3.VIPERのそれぞれの役割
4.View
5.Interactor
6.Presenter
7.Entity
8.Router
9.最後に
参考

1.はじめに

iosアプリ開発を勉強していくうちに必ずぶち当たるアーキテクチャの一つであるVIPERについて記事を書きたいと思います。
今回は地図アプリを作成します。
僕はVIPERのサンプルをコーディングをしていくうちに感動しました。

2.VIPERとは

VIPERとはViewInteractorPresenterEntityRouterのそれぞれの頭文字をとったものです。
また基本としてDI(Dependency Injection)で外部から注入し、単一原則のもとに分割します。
MVC、MVVMなどのアーキテクチャはViewが画面遷移も担当する必要があります。

3.VIPERのそれぞれの役割

役割
View UIの更新やPresenterにViewイベントの通知
Interactor ビジネスロジックを担当
Presenter ハブの役割※
Entity データ構造の定義(Model)
Router 画面遷移

ViewUISearchBarがあったとしてエンターを押したときにPresenterへ通知し、PresenterInteractorにデータの取得を依頼するなど

4.View

早速Viewのコードを見ていきます。

import UIKit
import MapKit



protocol MapView: AnyObject {
    func updatePin(coordinate: CLLocationCoordinate2D)
    func showErrorAlert()
}

final class MapViewController: UIViewController, Storyboardable {
    
    
    var presenter: MapPresentation!
    
    private let pin = MKPointAnnotation()
    
    @IBOutlet private weak var mapView: MKMapView!
    
    private var searchBar: UISearchBar = {
        let searchBar = UISearchBar()
        searchBar.placeholder = "住所"
        searchBar.autocorrectionType = .no
        searchBar.autocapitalizationType = .none
        return searchBar
    }()
    override func viewDidLoad() {
        
        navigationItem.titleView = searchBar
        searchBar.delegate = self
        
    }
}

extension MapViewController: MapView {
    func updatePin(coordinate: CLLocationCoordinate2D) {
        guard let adress = searchBar.text else { return }
        updateScreenCenter(coordinate: coordinate)
        pin.title = adress
        pin.coordinate = coordinate
        mapView.addAnnotation(pin)
    }
    
    func showErrorAlert() {
        let alertController = UIAlertController(title: "No Result", message: "該当する検索結果がありませんでした", preferredStyle: .alert)
        let ok = UIAlertAction(title: "OK", style: .default)
        alertController.addAction(ok)
        present(alertController, animated: true)
    }
}

extension MapViewController: UISearchBarDelegate {
    
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        guard let searchText = searchBar.text else { return }
        presenter.searchButtonPushed(adress: searchText)
    }
}

private extension MapViewController {
    func updateScreenCenter(coordinate: CLLocationCoordinate2D) {
        let span = MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
        let region = MKCoordinateRegion(center: coordinate, span: span)
        mapView.region = region
    }
}

①はPresenterから呼び出す関数をprotocolで定義?します。

関数名 機能
updatePin(coordinate: ) 検索した住所の位置にピンをさす
func showErrorAlert() エラーが発生したらアラートを出す

②はpresenterにイベントを通知するために定義します。
③で検索したい住所をpresenterに渡しています。

5.Interactor

import CoreLocation

protocol MapUsecase: AnyObject {
    func searchAdress(adress: String, completion: @escaping (Result<CLLocationCoordinate2D, Error>) -> Void)
}

final class MapInteractor {
    
}

extension MapInteractor: MapUsecase {
    
    func searchAdress(adress: String, completion: @escaping (Result<CLLocationCoordinate2D, Error>) -> Void) {
        Geocoder.fetchAdress(adress: adress) { result in
            switch result {
            case .success(let coordinate):
                completion(.success(coordinate))
                break
            case .failure(let error):
                completion(.failure(error))
                break
            }
        }
    }
    
}

Interactorはビジネスロジックを担当します。
特に説明はいらないと思いますが、APIを叩くなどです。

6.Presenter

import Foundation

protocol MapPresentation: AnyObject {
    func searchButtonPushed(adress: String)
}

final class MapPresenter {
    private weak var view: MapView?
    private let router: MapWriteframe
    private let interactor: MapUsecase
    
    
    init(view: MapView, router: MapWriteframe, interactor: MapUsecase) {
        self.view = view
        self.router = router
        self.interactor = interactor
    }
}

extension MapPresenter: MapPresentation {
    
    func searchButtonPushed(adress: String) {
        //interactorに住所の検索を依頼
        interactor.searchAdress(adress: adress) {[weak self] result in
            switch result {
            case .success(let coordinate):
                self?.view?.updatePin(coordinate: coordinate)
                break
            case .failure:
                self?.view?.showErrorAlert()
                break
            }
        }
    }
}

①DIの部分です。Presenterはハブの役割なのでそれぞれのインスタンスが必要になります。
②viewでsearchBarSearchButtonClicked(_ searchBar: )が呼び出されたときにsearchBar.textを渡されてinteractorに検索を依頼します。

7.Entity

今回のアプリは自分で定義するEntityはありませんが(CLLocationCoordinate2D)、githubのAPIなどを叩くときにRepositoryEntityなどを定義しましょう。

8.Router

protocol MapWriteframe: AnyObject {
    
}

final class MapRouter {
    
    
    private weak var viewControlelr: UIViewController?
    
    private init(viewController: UIViewController) {
        self.viewControlelr = viewController
    }
    
    
    static func assembleModules() -> UIViewController {
        let view = MapViewController.instantiate()
        let router = MapRouter(viewController: view)
        let interactor = MapInteractor()
        let presenter = MapPresenter(view: view, router: router, interactor: interactor)
        
        view.presenter = presenter
        
        return view
    }
    
}

extension MapRouter: MapWriteframe {
    
}

①viewController使ってないのになんで宣言してるのと思われるかもしれないですが、画面遷移する場合に必要になります。(今回は必要ない)
②viewControllerの表示用の関数。

9.最後に

一つの画面ごとにView、Interactor、Presenter、Entity、Routerが必要でめんどくささもありますが、テストのしやすさ、可読性、修正しやすさなど多くのメリットがあります。
僕が一番感動したのはコードの綺麗さです。
自分ができる人みたいな感覚になれておすすめです。

今回なんとなく記事にしてみましたがまだまだ理解が足りていないと感じたので精進します。
アドバイスなどありましたらよろしくお願い致します。

参考

VIPERアーキテクチャ まとめ

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?