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()を呼ぶだけです。
どの画面へ進むか、どうプッシュするかを気にする必要はありません。
直接pushやSegueとは何が違う?
混同しやすい部分なので、表にまとめました。
| 項目 | 直接push / Segue | Coordinatorパターン |
|---|---|---|
| 遷移ロジックの場所 | ビューコントローラー内 | Coordinatorに集約 |
| 画面間の依存関係 | 互いを直接知る | 互いを知らなくてもよい |
| 画面の再利用 | 難しい | 簡単 |
| フローの把握 | 複数ファイルに分散 | 一か所で管理 |
| 初期作業量 | 少ない | やや多い |
Coordinatorは万能ではありません。
画面が2、3個しかない小規模アプリでは、コードだけ増えて逆効果になることもあります。
一方、ログインやオンボーディング、タブ切り替えなど、フローが分岐するアプリでは効果を発揮します。
よくある質問
Q. 画面が複数ある場合、Coordinatorも複数作りますか?
はい。通常はログインフロー、メインフローのようにフロー単位で分けます。上位Coordinatorが子Coordinatorを管理する構成が一般的です。
Q. 子Coordinatorはいつ破棄しますか?
フロー終了時に、親が配列から子への参照を削除します。そうしないとメモリーに残り続け、リークが発生します。
Q. SwiftUIでも使いますか?
SwiftUIにはNavigationStackとpathに基づくルーティングがあるため、少し性質が異なります。ただし、フローを別オブジェクトに切り出す考え方は応用できます。
最初はファイルが一つ増えるため、面倒に感じるかもしれません。
画面が10個、20個と増えたとき、この構成のありがたさを実感するはずです。
まずは小さな画面フロー一つをCoordinatorに切り出してみてください。ビューコントローラーが軽くなるのを実感できます。🙂

