iOSエンジニアリング

iOS Coordinatorパターン:画面遷移コードをビューコントローラーから切り離す方法

iOSアプリを作っていると、ビューコントローラーが次第に肥大化することがあります。

読了 5 分
iOS Coordinatorパターン:画面遷移コードをビューコントローラーから切り離す方法のカバー画像

iOSアプリを作っていると、ビューコントローラーが次第に肥大化することがあります。

画面描画やデータ処理に加え、次の画面へ進むpushViewControllerまでが混在してしまいます。

そのままにすると、画面フローが複雑になるほど遷移元と遷移先を追いにくくなります。

今回は、画面遷移ロジックをビューコントローラーからきれいに切り離すiOS Coordinatorパターンを紹介します。

Coordinatorパターンは、画面遷移を担当する別オブジェクトを置く方法です。ビューコントローラーは画面表示に専念し、次にどこへ進むかはCoordinatorに任せます。


Coordinatorパターンとは?

一言で定義すると、次のとおりです。

Coordinatorは、画面遷移(ナビゲーション)フローを専門に管理するオブジェクトです。

従来のビューコントローラーは、画面表示、入力処理、次の画面への遷移まで一人で担っていました。

そのうち次の画面へ遷移する処理だけを取り出し、Coordinatorに任せます。

ボタンが押されたら、ビューコントローラーはその事実だけをCoordinatorに伝えれば済みます。

実際にどこへ、どう遷移するかを知る必要はありません。

ビューコントローラー同士が互いを直接知る必要もなくなります。A画面がB画面の存在を知らなくてもよいため、再利用もしやすくなります。


なぜ画面遷移を切り離すのか?

ビューコントローラーに画面遷移コードを置くと、さまざまな問題が起こります。

私が実際に経験した問題をまとめます。

  • 依存関係が複雑:A画面がB画面を直接生成するため、Bの変更時にAも修正が必要です。
  • 再利用しにくい:同じ画面を別フローで使うには、遷移コードも再度修正する必要があります。
  • フローを把握しにくい:画面遷移ロジックが20ファイルに散らばり、全体像が見えません。
  • テストしにくい:遷移ロジックがビューコントローラーに結び付き、単体テストが難しくなります。

遷移ロジックを一か所に集めれば、これらの問題の多くを解決できます。

Coordinatorのファイル一つを見るだけで、このアプリの画面フローが把握できます。


Coordinatorの作り方

基本構成は思ったよりシンプルです。

まず共通仕様となるプロトコルを定義します。start()が起点になります。

protocol Coordinator: AnyObject {
    var navigationController: UINavigationController { get }
    func start()
}

次に実際のCoordinatorを作ります。最初の画面を作成し、表示するところまで担当します。

final class MainCoordinator: Coordinator {
    let navigationController: UINavigationController
    init(nav: UINavigationController) { self.navigationController = nav }

    func start() {
        let vc = HomeViewController()
        vc.coordinator = self          // 遷移リクエストを受け取る窓口
        navigationController.pushViewController(vc, animated: false)
    }

    func showDetail() {                // 次の画面への遷移はここだけで行う
        let vc = DetailViewController()
        navigationController.pushViewController(vc, animated: true)
    }
}

ボタンが押されたら、ビューコントローラーはcoordinator?.showDetail()を呼ぶだけです。

ボタンを一度押せば、あとはCoordinatorが処理します。
ボタンを一度押せば、あとはCoordinatorが処理します。

どの画面へ進むか、どうプッシュするかを気にする必要はありません。

この一行の呼び出しだけを残せば、ビューコントローラーはずっと軽くなります。
この一行の呼び出しだけを残せば、ビューコントローラーはずっと軽くなります。

直接pushやSegueとは何が違う?

混同しやすい部分なので、表にまとめました。

項目 直接push / Segue Coordinatorパターン
遷移ロジックの場所 ビューコントローラー内 Coordinatorに集約
画面間の依存関係 互いを直接知る 互いを知らなくてもよい
画面の再利用 難しい 簡単
フローの把握 複数ファイルに分散 一か所で管理
初期作業量 少ない やや多い

Coordinatorは万能ではありません。

画面が2、3個しかない小規模アプリでは、コードだけ増えて逆効果になることもあります。

一方、ログインやオンボーディング、タブ切り替えなど、フローが分岐するアプリでは効果を発揮します。

フローが多岐にわたるアプリほど効果的です。
フローが多岐にわたるアプリほど効果的です。

よくある質問

Q. 画面が複数ある場合、Coordinatorも複数作りますか?

はい。通常はログインフロー、メインフローのようにフロー単位で分けます。上位Coordinatorが子Coordinatorを管理する構成が一般的です。

Q. 子Coordinatorはいつ破棄しますか?

フロー終了時に、親が配列から子への参照を削除します。そうしないとメモリーに残り続け、リークが発生します。

Q. SwiftUIでも使いますか?

SwiftUIにはNavigationStackとpathに基づくルーティングがあるため、少し性質が異なります。ただし、フローを別オブジェクトに切り出す考え方は応用できます。


最初はファイルが一つ増えるため、面倒に感じるかもしれません。

画面が10個、20個と増えたとき、この構成のありがたさを実感するはずです。

まずは小さな画面フロー一つをCoordinatorに切り出してみてください。ビューコントローラーが軽くなるのを実感できます。🙂

あわせて読みたい