SwiftのFlowControllerによる画面遷移の基本

FlowControllerの元祖は[こちらのIssue](https://github.com/onmyway133/blog/issues/106)で、このIssue提案者が英語で書いた説明記事が[こちら](https://dev.to/onmyway133/coordinator-and-flowcontroller-8cg)です。しかし全て英語でコードが所々しかなく難しかったので、[@shizさんの記事](https://qiita.com/shiz/items/4a828dc8e4eb0c96d397)をベースにしてつくったサンプルアプリを元に、基本的な『FlowControllerによるpush遷移とpresent遷移のやり方』を解説します。

## サンプルアプリ
今回は以下のようなアプリを[@shizさんのサンプルコード](https://github.com/stzn/ViperSampleGeneramba/tree/flowcontroller)をベースに作りました。

### デモ
![Mar-06-2019 11-58-33.gif](https://qiita-image-store.s3.amazonaws.com/0/242610/8b30ee4c-d7a0-d4ea-4444-011d06868e26.gif)
[他のボタンの動作デモはYoutube版へ](https://youtu.be/e5IlzBT6F8Y)

### 画面遷移の流れ

1. 記事一覧（ListView）を表示
2. push(DetailFirstView）
3. push(DetailSecondView)
4. push(DetailThirdView)
5. present(ModalView)
6. push(ModalDetail)
7. 完了ボタンで全ての流れを終了

### コード
[GitHub Repository](https://github.com/maztak/FlowControllerSample)

## FlowControllerとは何なのか
原文や@shizさんの記事ではCoordinatorパターンと比較する形でFlowControllerのメリットを紹介されています。ただペーペープログラマである僕らにとっては「まずCoordinatorパターンって何？」から入らなければならないため理解に苦しむと思います。

FlowControllerとは、簡単に言うと「通常はViewControllerが担っていた画面遷移の処理（責務）を、ViewControllerから切り分けて代わりに担当してくれるController」です。

具体的には「UIViewControllerを継承したViewControllerのラッパー」であり、構造的にはAppFlowController＞HogeFlowController＞（NavigationController）＞ViewControllerという親子関係になっています。

今までViewControllerで行なっていたpushやpresentの画面遷移を親のHogeFlowControllerに（delegateを使って）委任することで、ViewControllerの責務を減らし、コードを少なくスッキリさせています。

### ちょっと何いってるかわかんない
実際のところ、色々な設計パターン（アーキテクチャ）や実装の経験を経てより良い設計に辿り着くのが本来の流れだと思う（知識は必要になった時に初めて身につく）ので、「ちょっと何いっているかわかんない」という人は無理にFlowControllerやCoordinatorパターンを使わなくて良いと思います（←説明の怠慢）。

## 何が良いのか
こういった設計パターンやアーキテクチャの主目的はどれも同じで「責務の切り分け」とそれによる「テストを容易にすること」です。

このFlowControllerの目的・良さも「ViewControllerの画面遷移の責務を切り分けてやることで、コードの可読性や保守性を良くし、テストも容易にできること」と言えると思います。

Coordinatorパターンでもほぼ同じメリットを得られる（はず）ですが、原文や@shizさんの記事にもあるように「Coordinatorパターンにはないメリットを得られるからFlowControllerを使う」わけで、その詳しい違いについては本記事では割愛します。

### 追記
僕なりにCoordinatorパターンとFlowControllerパターンの違いをまとめたので最後におまけとして付けておきます）。

## 今回のサンプルの処理流れ
今回作成したサンプルの構造としては以下の画像のようになっています。
![2019-03-05 16:59 Office Lens.jpeg](https://qiita-image-store.s3.amazonaws.com/0/242610/76c0ba73-668b-1c6b-7e54-aa62e0259f2f.jpeg)


`AppFlowController`がすべてのFlowControllerの親です（アプリ起動後に`AppDelegate`の`application(_:didFinishLaunchingWithOptionsAppFlowController)`で`AppFlowController`の`start()`を呼んでフローをスタートしています）。

### 初期画面
最初に`ListViewController`を表示したいので、まず`ListFlowController`を生成します（この過程で`embeddedNavigationController = UINavigationController()`も生成している）。

次に`ListFlowController`の中で`ListViewController`を生成し、`embeddedNavigationController.viewControllers = [listViewController]`とすることで初期画面を表示します。

### push遷移
そして`Next`ボタンが押された時に`delegate`である`ListFlowController`に画面遷移を依頼し、そこで今度は`DetailFirstViewController`を生成し、`embeddedNavigationController.viewControllers = [firstViewController]`とすることで画面遷移をするわけです。

### present遷移
present遷移の場合は新たなナビゲーションを開始（新しいNavigationControllerを生成）しなければならないため、`ThirdViewController`から`delegate`である`ListFlowController`に依頼し、さらに`ListFlowController`が`delegate`である`AppFlowController`に新たなフロー（`ModalFlowController`）を開始するように依頼します。

ここでpresent遷移をするのは`ModalFlowController`です。`ModalFlowController`もUIViewControllerを継承しているのでpresent遷移が可能で、その子が上下左右ぴたぴたに張り付いているだけなので、見た目としては`ModalViewController`がpresent遷移したのと同じになります。

```ModalFlowController.swift
override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        children.first?.view.frame = view.bounds
    }
```

### flowのremove
最後の`FifthViewController`で「完了」ボタンを押した時は、同様にdelegateを使って`NewModalFlowController`を経由して一番親である`AppFlowController`に「フローを終了して！」と依頼する形になります。

フロー開始時に`AppFlowController`で`add(childController: listFlowController)`のようにaddしているだけなら、同じく`AppFlowController`で`self.children.forEach { remove(childController: $0) }`を実行してやれば、今のフローを全て削除してホーム画面などに戻ることができます（サンプルではListFlowを再度startして記事一覧画面を表示しています）。

しかし今回はpresent遷移（下からViewが上がってくる遷移）なので`AppFlowController`で`add(childController: newModalFlowController)`とせず`viewController.present(newModalFlowController, animated: true)`としています（ここでのviewControllerはpresent遷移する直前のThirdViewControllerのインスタンスを、delegateで依頼する時に引数としてAppFlowControllerまで渡してきたものです）。

なので画面を消すときもdismiss（上から下に下がる消え方）をしなければなりません。そこで`ModalFlowController`が`AppFlowController`に「フローを終了して」と依頼した直後に`self.dismiss(animated: true)`で自分自身をdismissしています。

## Coordinatorパターンとの違い（おまけ）
[@shizさんの記事](https://qiita.com/shiz/items/4a828dc8e4eb0c96d397)の各見出しを僕なりに整理してみました。@shizさんの記事や原文を読んで良くわからなかったときに、僕なりの補足を見ることで皆さんの理解の助けになればと思います（ListFlowControllerのフローの前にもうひとつ、LoginFlowControllerのフローが開始されているので、AppFlowController>LoginFlowController>ViewControllerという構造になっている前提で以下は読んでいただければと）。

### 3.FlowControllerは依存関係を管理
LoginDependencyContainerなどに依存すべきプロパティ？を追加してやることで簡単に依存性を注入できる？←Coordinatorパターンは？ 

### 4. 子FlowControllerの管理が簡単
FlowControllerはUIViewControllerを継承しているので、 addとremoveのメソッド（Extension）だけで子FlowControllerできる。←Coordinatorパターンは？ 

### 5.UIWinowを保持する必要がない
Coordinatorパターンの場合はinitで都度UIWindowを触っている。

FlowControllerならUIViewControllerを継承しているので、UIWindowをいじるコードを各必要がない。

### 6.個々のフローを疎結合にできる
 疎結合とはシンプルな結合という意味です（対義語は密結合）。Coordinatorパターンでは親のAppCoordinatorでNavigationControllerを生成して、子であるLoginCoordinatorをinitする時に引数で渡さなければならないから結合度が高い（密結合である）。 

FlowControllerなら子であるLoginFlowController自身でNavigationControllerを生成できる。

### 7. UIResponderを継承している 
Coordinatorパターンと違って、FlowControllerはUIViewControllerを継承しているので`touchesBegan`を始めとした様々なイベントのデリゲートメソッドを使用することができる 。

### 8. FlowControllerから画面の表示などを管理できる 
本来は子VCにさせる表示関係の仕事も、親であるFlowControllerも一応できるよってことではないかと思います（でもそれをしてしまうと役割切り分けた意味なくね？臨時策？ ）。

### 9. NavigationControllerの戻るボタン 
CoordinatorパターンはNavigationの「戻る」ボタンを手動で設定しなければならない。

FlowControllerは（UIViewControllerを継承しているので？）自動でやってくれる。

### 10. コールバックで画面遷移を依頼する
よく分からない。Coordinatorパターンがどうやって画面遷移をしているか知らない（おい）。

FlowControllerは「ViewControllerが、delegate（親であるLoginFlowController）に依頼して画面遷移する」のですが、コールバックは使ってないんじゃないのかな…（画面遷移完了したらViewControllerでバック後の処理を実行するとかにはなっていないと思うのだが）。

## まとめ
まだ記事を書いてる途中で理解がうやむやだったりするので、リプいただければ随時補足・修正していきます！