7
4

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

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

Last updated at Posted at 2019-03-05

FlowControllerの元祖はこちらのIssueで、このIssue提案者が英語で書いた説明記事がこちらです。しかし全て英語でコードが所々しかなく難しかったので、@shizさんの記事をベースにしてつくったサンプルアプリを元に、基本的な『FlowControllerによるpush遷移とpresent遷移のやり方』を解説します。

サンプルアプリ

今回は以下のようなアプリを@shizさんのサンプルコードをベースに作りました。

デモ

Mar-06-2019 11-58-33.gif
他のボタンの動作デモはYoutube版へ

画面遷移の流れ

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

コード

GitHub Repository

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

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

初期画面

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

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

push遷移

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

present遷移

present遷移の場合は新たなナビゲーションを開始(新しいNavigationControllerを生成)しなければならないため、ThirdViewControllerからdelegateであるListFlowControllerに依頼し、さらにListFlowControllerdelegateである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に「フローを終了して!」と依頼する形になります。

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

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

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

Coordinatorパターンとの違い(おまけ)

@shizさんの記事の各見出しを僕なりに整理してみました。@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でバック後の処理を実行するとかにはなっていないと思うのだが)。

まとめ

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

7
4
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
7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?