LoginSignup
27
10

More than 5 years have passed since last update.

VIPERでこれってどこに書けば良いの?

Last updated at Posted at 2018-08-28

MVCしかやってこなかった自分が、半年ほどの間に学んだVIPERの書き方で詰まったところについての記事です。
今までのMVCならこうやって書くけど、VIPERだとどうなるの?って思ったことが中心です。

VIPERとは

iOSのアーキテクチャの一つです。
VIPERについては先人がいくつもの素晴らしい記事を書かれているので、ここは割愛しますが、VIPERを書く際に自分が参考にしている記事のリンクを貼っておきます。

iOS Project Architecture : Using VIPER [和訳]

ぼくのやっているVIPER(のようなもの)

ファイル生成が大変なのでこういうのもあります。

iOS開発で VIPER / Clean Architecture を使うなら、ファイル自動生成の Generamba もどうぞ

【iOS】GenerambaのテンプレートエンジンLiquidの書き方

それでは、自分がVIPERでコードを書くに当たってどうすれば良いのかわからなかった部分について見ていきましょう。

UIAlertController

VIPERでまずぶち当たる問題がこれです。
UIAlertControllerはUIViewControllerのpresentメソッドで出すことから画面遷移としてRouterで処理を書きたくなります。
しかし、そうするとアラートのボタンを押したActionで何かをやりたいときに、Presenterに伝えるのが難しくなる(最悪クロージャーやNotificationで出来なくもないが無理がある。)ので、View側で出してあげると上手く行きます。

SampleViewController.swift
class SampleViewController: UIViewController, SampleViewInterface {

    var presenter: SamplePresentation!

    func showAlert() {
        // UIAlertControllerのextensionで追加したメソッドです。
        UIAlertController.showOkCancelAlert(self,
                                            title: "Hoge",
                                            message: "Fuga",
                                            okHandler: { self.presenter.didSelectOK() },
                                            cancelHandler: nil)
    }
}

ただ、これの問題は、UIViewControllerのVIPERモジュールであれば、ViewがUIViewControllerなので問題なくUIAlertControllerを表示させることが出来ますが、UIViewでモジュールを作成した場合、Viewからアラートを出すことが出来ないので、それを考えると、一概にViewで出すと決めるのは難しいところです。

そういう問題になりそうな場合はUIViewのVIPERモジュールを絶対に作らないと決めて、その代わりにCustomViewを作る際は、UIViewControllerで一旦作成して、viewをaddSubViewする前提で作成すればこのあたりは解決できます。

画面間のDelegate

画面間のDelegateもなかなか難しい問題です。
Delegateの実体をViewにするかPresenterにするか、実際のdelegateを保持してメソッドを呼び出すのはどこになるかも考えなければなりません。

自分がやった中で一番しっくりくるのはPresenterを実体として渡し、呼び出し元のPresenterで保持する方法です。

例としてFirstモジュールからSecondモジュールにdelegateを渡す処理を挙げてみます。

1.SecondPresenter

SecondモジュールのPresenterにDelegateを定義して、イニシャライズメソッドでDelegateを受け取るようにします。
加えて、呼び出しのメソッドも書きましょう。

SecondPresenter.swift
protocol SecondDelegate: class {
    func hoge()
}

class SecondPresenter: SecondPresentation {

    weak var view: SecondViewInterface?
    var interactor: SecondUsecase!
    var router: SecondWireframe!

    private weak var delegate: SecondDelegate?

    init(_ delegate: SecondDelegate) {
        self.delegate = delegate
    }

    // Viewから受け取った処理
    func didTapHogeButton() {
        // デリゲートメソッドを呼ぶ
        self.delegate?.hoge()
    }
}

2.SecondRouter

SecondRouterのモジュール生成メソッドの引数にDelegateを追加します。
そして、Presenterの生成時は、1で新たに作成したイニシャライズのメソッドを使用して生成します。

SecondRouter.swift
class SecondRouter {

    static func assembleModule(_ delegate: SecondDelegate) -> UIViewController {
        // UIViewControllerの生成とか色々
        let presenter = SecondPresenter(delegate)

        return viewController
    }
}

3.FirstRouter

FirstRouterのSecondへの遷移メソッドに引数を追加して、SecondRouterのassembleModuleにそのまま引数を渡すようにします。

FirstRouter.swift

class FirstRouter {

    weak var viewController: UIViewController?

    func showSecond(_ delegate: SecondDelegate) {
        let vc = SecondRouter.assembleModule(delegate)
        // 遷移処理
    }
}

4.FirstPresenter

FirstPresenterが呼び出すFirstRouterのSecond遷移メソッドの引数にselfを渡します。
加えて、SecondDelegateを実装します。

FirstPresenter.swift

class FirstPresenter: FistPresentation {

    weak var view: FirstViewInterface?
    var interactor: FirstUsecase!
    var router: FirstWireframe!

    func didTapShowSecondButton() {
        // デリゲートにselfを渡す
        self.router.showSecond(self)
    }
}

extension FirstPresenter: SecondDelegate {

    func hoge() {
        // SecondPresenterから受け取った後の処理を書く
    }
}

考え方としては、VIPERにおいて、処理を振り分けるのはPresenterなので、ViewやInteractorやRouterに処理を振り分ける中で、Delegateにも振り分けるよ。という感じがしっくり来たので各モジュール間のDelegateにおいては、遷移元画面のPresenterを遷移先のPresenterに渡すやり方が良いなと思いました。

27
10
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
27
10