既存のiOSアプリにFlutterを追加する方法

既存のiOSアプリにFlutter画面を追加するのは、公式ドキュメントで見るとAndroidと同じくらい簡単そうに見えます。Flutterモジュールを作成し、Xcodeプロジェクトにリンクして、SwiftからFlutterViewControllerを表示するだけです。新しく作ったデモアプリなら、本当にそれだけで済みます。しかし長い歴史を持つ実際のiOSアプリでは、ここでもその最初の画面は簡単な部分にすぎず、難しくなるポイントはAndroidとは異なります。

add-to-appがiOSチームにもたらすものは同じです。両プラットフォーム向けに一度だけ書けば済む画面と、ネイティブアプリのリリースを続けながら開発者がhot reloadで反復開発できるFlutterモジュールです。一方、ホストに求められることはiOS特有です。カスタムのビルド構成によって、Flutterがdebugとreleaseのどちらのコードを含めてリリースされるかが決まります。Flutter画面はUINavigationControllerの中で、iOSユーザーが期待するとおりに振る舞わなければなりません。このページでは、こうした判断を下す立場にあるiOSのテックリードの視点から、それらの判断と、私たちがその過程で遭遇してきた問題を順に見ていきます。

Androidと同様に、ここで必要になる配管の大部分は汎用的なadd-to-appの作業で、どのチームも何らかの形で構築することになります。私たちはそれをカバーするためにInlayを作り、関連する箇所で紹介していますが、フレームワークを使っても自前で書いても、下すべき判断は同じです。ステップごとのセットアップについてはInlayのはじめにのiOSセクションを、最初にどの画面を移行するかについては計画のページと、自社のコードから分析するならConvergeを参照してください。

iOSプロジェクトで何が変わるか

Flutterは、隠れた.iosラッパープロジェクトを持つモジュールとして導入されます。ホストはそれを依存関係としてリンクするだけで、ホスト側のものは何も移動しません。Androidと同様に、Flutterのコードはアプリごとにちょうど一つのモジュールに置かれるため、パッケージに分割した共有モジュールを前提に計画し(スケーリングとコード共有)、日々の作業がXcodeを経由せずに済むよう、モジュールを単体のFlutterアプリとして実行できる状態に保ちましょう。Flutter側に属するネイティブのSwiftコードは、ツールによって再生成されるラッパープロジェクトではなく、モジュールが依存するプラグインパッケージに置きます。

Swift Package Manager、CocoaPods、ビルド済みXCFrameworkによるFlutterの統合

iOSアプリにFlutterのバイナリを組み込む方法は複数あります。

  • Swift Package Managerは現在推奨されている方法です(Flutter 3.44以降)。モジュールはローカルパッケージを生成し、ホストはそれをプロジェクトに追加します。Dartに変更があるとビルドフェーズで再ビルドされます。新しく統合するならこの方法を選びましょう。
  • CocoaPodsは今も動作しますが、Flutterではメンテナンスモードの扱いで、CocoaPodsのレジストリは2026年12月2日に読み取り専用になります。現在ホストがCocoaPodsを使っているなら、Flutterの統合は移行を先送りする理由ではなく、移行を計画する理由になります。
  • ビルド済みXCFramework(flutter build ios-framework)を使えば、Flutter SDKをホストのビルドから完全に切り離せます。ホストはDebug、Profile、Releaseの各フレームワークを、他のベンダーSDKと同じようにリンクします。別のチームがモジュールを所有している場合や、ホストのCIにFlutterをインストールできない場合に適した選択肢です。その代わり、公開用のパイプラインが必要になり、Dartに変更があるたびに新しいアーティファクトを作ることになります。

ビルド構成

iOSにFlutterを組み込む際、Xcodeのカスタム構成では追加のセットアップが必要になることがあります。Flutterのビルドツールは、FLUTTER_BUILD_MODEが設定されていればそれを使い、設定されていなければ構成名からDebug、Profile、Releaseのいずれかを推測します。そのため、Staging、UAT、AppStoreのような名前の構成には、Flutterのビルドモードを明示的に指定する必要があります。ビルド済みXCFrameworkを統合する場合は、ホストの各構成を意図したFlutterのアーティファクトに対応付け、エンジンとDartアプリのバリアントを一致させておきましょう。

Flutter画面とネイティブ画面を混在させる場合は、ユーザーにシームレスな体験を提供し、どの画面がどちらなのか気づかれないようにすることが重要です。考慮すべきナビゲーションの概念がいくつかあります。

  • スワイプバックのジェスチャー:Flutter画面が内部にルートを持っている場合、UINavigationControllerのエッジスワイプはFlutterのview controller全体、つまり内部のルートすべてを一度にpopしてしまいます。Flutter側にpopできるルートがある間はこのジェスチャーを無効にし、ルートに戻ったら再び有効にする必要があります。InlayのInlayNativePopGestureObserverが、2つのスタックを同期させます。
  • ナビゲーションバー:Flutterが独自のAppBarを描画する場合は、Flutter画面が最前面にある間はネイティブのバーを非表示にします。ネイティブのバーを残す場合は、Flutterからタイトルと戻るボタンの状態をバーに伝える必要があります。どちらにするかは、アプリ全体で一度決めておきましょう。
  • カスタムトランジション:カスタムアニメーションを持つネイティブのナビゲーションバーは、Flutterとうまく噛み合わないことがあります。私たちのあるプロジェクトでは、アニメーションを連携させるよりも、もう数画面をFlutterで書き直したほうが早いとすぐに判明しました。

最初のFlutter画面を本番に出す前に

  • ローカルネットワークの権限:iOS 14以降では、hot reloadとDevToolsにローカルネットワークへのアクセスが必要です。セットアップガイドでは、これをdebug専用のplistに記述し、App Storeでリジェクトされる可能性があるためReleaseには決して含めないよう警告しています。カスタム構成を使っている場合は、StagingやUAT経由でreleaseビルドに紛れ込まないよう確認してください。
  • デプロイメントターゲット:少なくともFlutter、利用しているプラグイン、Inlayが要求するバージョン以上にします。Flutterはこれを時折引き上げるため(3.47ではiOS 15に移行)、古い端末をどう扱うかというプロダクト上の判断を迫られることがあります。
  • 実機でのアーカイブビルド:シミュレーターやdebugビルドでは、ビルドモード、組み込みフレームワークの署名、プラグイン登録に関する問題が表に出てきません。
  • ステータスバーとダークモード:Flutter画面を両方のモードで確認し、ステータスバーのスタイルがFlutter画面に追従しているかを確かめましょう。ステータスバーをグローバルに管理しているホストでは、それが上書きされることがあります。
  • クラッシュレポート:Dartのエラーは、明示的にレポートしてシンボルをアップロードする必要があります。

プライバシーポリシー

このページの内容