FlutterアプリのiOS CI/CD設定手順

Bundle Identifierの登録

Certificates, Identifiers & ProfilesでBundle Identifierの登録を行う。

App Store Connectでのアプリの作成

App Store ConnectのApps画面から「New App」を選んでアプリを作成する。Bundle Identifierは↑のステップで作成したものを選ぶ。

Xcode ProjectのBundle Identifier変更

Flutterから書き出されたXcode Projectを開いて、Bundle Identifierを↑のステップで作成したものに変更する。Product Flavorを使っている場合はそれぞれのFlavor用にConfigを作成して、それぞれのBundle Identifierを設定する。

Complianceの設定

Info.plistを開き、App Uses Non-exempt Encryptionの設定を追加する。

Post Cloneスクリプトの追加

Xcode Cloudでプレビルド処理を行うためci_scripts/ci_post_clone.sh を作成する。

パッケージの使用有無によってCocoaPodsの呼び出しの有無、Product Flavor / Dart-define-from-fileの使用有無によって flutter build の呼び出し方/引数が変わるので注意する。

ここまでの設定内容をレポジトリにPushしておく。

Testing Groupの作成

App Store ConnectのアプリのTestFlight設定画面で、Internal Testing用のTesting Groupを作成し、テスト用のApple IDを招待しておく。

Workflowの作成とテスト実行

Xcode CloudのWorkflowの作成を行う。ArchiveアクションのDeployment PreparationはTestFlight (Internal Testing Only)かTestFlight and App Storeのいずれかを選ぶ。Xcode Cloudの仕様なのか不具合なのか、この段階ではTestFlightの配信の設定ができないことがあるので、一旦ここまでで設定を完了し、手動で初回のビルドをトリガーし、ビルドが成功するかを確認する。

Post-Actionの設定とテスト再実行

Workflowの実行に成功したら、WorkflowをEditし、「TestFlight Internal Testing」のPost Actionを追加する。↑で作ったTesting Groupを指定し、再びビルドをトリガーし、ビルドが自動でアップロードされてTestFlightでテスト可能になることを確認する。

Xcode Cloudでビルドの前後に処理を行う

今更だが、Xcode Cloudが便利だ。Apple純正のCI/CD環境ということもあって、設定で複雑なことをする必要がなく、やれることも限られている分わかりやすい。

大抵のアプリのビルドでは、Xcode Cloudの標準の機能だけで問題なくApp Storeへの申請/TestFlightでの配布ができているのだが、稀にビルドの前後に何らかの処理を行いたいケースがある。

https://developer.apple.com/documentation/xcode/writing-custom-build-scripts

Xcode Cloudには、Custom Build Scriptという仕組みが用意されており、このルールに従ってスクリプトを用意すると、下記のタイミングで任意の処理を実行できる。

  • レポジトリからのCloneが終わった直後(ci_post_clone.sh)
  • xcodebuildが実行される直前(ci_pre_xcodebuild.sh)
  • xcodebuildが終わった直後(ci_post_xcodebuild.sh) ※失敗時も呼ばれる

ビルド番号(CI_BUILD_NUMBER)や何をトリガーにワークフローが開始されたのか(CI_START_CONDITION)などの情報を環境変数として参照できるようになっている。

https://developer.apple.com/documentation/xcode/environment-variable-reference

デバッグ用の情報(COMMIT HASHなど)をアプリ内で参照できるようにする、など色々使える場面があって、覚えておくと何かと便利。

GodotプロジェクトのiOSアプリ出力で、リソースをpckファイルにまとめるかフォルダ参照するかで挙動が変わる話

GodotではiOS用のエクスポートテンプレートを追加すると、ExportメニューからXcodeプロジェクトを書き出してiOSでアプリを動かすことができる。書き出し作業自体は手軽なのだが、毎回数十秒程度処理に時間がかかることやXcode側での設定(アプリ名のローカライズなど)がリセットされてしまうので、コードやリソースを変更するたびにExportを実行するのはなかなか面倒だ。

そこで、公式ドキュメントの「Active Development Considerations」の項では、XcodeプロジェクトからGodotのプロジェクトをフォルダ参照することで、変更のたびに毎回Exportし直さなくても常に最新の内容でiOSアプリをビルド・実行可能にする方法が説明されている。(※プラグインの追加時など、Xcodeプロジェクトの構造変更が必要な時はプロジェクトの出力し直しが必要になる)これは実に便利で、特にアプリ内課金の機能などiOSのネイティブ機能を使う部分の開発・デバッグには不可欠な手順になっている。

ただし、上の公式ドキュメントには特に記載がないがプロジェクトを毎回Exportした場合と、一度ExportしたXcodeプロジェクトからGodotフォルダ参照する場合ではResourceに関する挙動が異なるので注意する必要がある。

GodotからXcodeプロジェクトをExportすると、プロジェクト内で使用される画像やSceneなどのResourceを一つにまとめたリソースパック(pckファイル)が生成される。このpckファイルには、GodotでResourceとして扱われるファイルのみが含まれている。例えば2022年9月現在Godot 3.5ではJSONファイルはResourceファイルとしては扱われないため、プロジェクトフォルダ内に存在したとしてもpckファイルには含まれない。(2022年9月4日のissues #65295でJSONをResourceとして扱うための変更が提案されMergeされたため、将来はJSONもpckファイルに含まれるようになる予定)

一方でフォルダ参照をしている場合は、Godotのプロジェクトディレクトリ内にある全てのファイルをres://プロトコルのURLで参照することができる。Godotでは本来Resourceとして扱われないJSONファイル等もフォルダ参照の場合は参照することができてしまう。そのようなコードを書いてしまうと、フォルダ参照時は動作するのにpckファイル読み込み時は動作しない、という現象を引き起こしてしまうことになる。

一見全て参照できる分、基本的にはフォルダ参照にしておいた方が便利そうな気もするが、そうとも言えない。AppStoreへのサブミット時にリジェクト要因になってしまうファイルがバイナリに組み込まれてしまう場合があるためだ。例えばアプリ内課金を利用する場合、公式ドキュメントのやり方に従うとプロジェクトのres://ios/pluginsにiOSプラグインのファイルを配置することになるが、このプラグインのファイルをフォルダ参照で含めたままAppStoreにサブミットしようとするとInvalid Bundle Structureエラーが発生してしまう。Xcodeのビルド設定で特定のファイルを明示的に除外することでエラーを回避することも可能かもしれないが、開発をしているとプラグイン追加のタイミングなどXcodeプロジェクト自体を出力し直す機会が時々発生し、その度にこの辺りの細かい設定するのはできれば避けたい。

おそらく、現実的に無難な選択肢はpckファイルのみを更新するやり方だろう。Godotには、pckファイルのみをExportする機能が用意されている。

ワンステップ挟むことになるのでフォルダ参照に比べると多少不便ではあるが、この方法なら「フォルダ参照で想定外に動いてしまった」というような実装も防げるので、他プラットフォームへの展開なども考えると最も無難なのではないかと思う。

Privateレポジトリへの依存があるプロジェクトをBitriseでビルドする

Swift Package ManagerでPrivateなレポジトリのパッケージへの依存を含むプロジェクトをBitrise上でビルドしようとして、認証周りで少しハマってしまった。

“Privateレポジトリへの依存があるプロジェクトをBitriseでビルドする” の続きを読む

ARKitでカメラからの映像にCIFilterでエフェクトをかける

こちらのポストQiitaの投稿を参考に、カメラからの映像にCIFilterを使ってエフェクトをかけようとしたのだが、手元のiPhone Xで試してみたところ映像に歪みが発生してしまった。公式ドキュメントetc.から正しい対処法を見つけられなかったのだが、普通にアスペクト比を揃えて中央でクロップすれば良さそうだったので、やり方を記しておく。

“ARKitでカメラからの映像にCIFilterでエフェクトをかける” の続きを読む

iPhoneと発泡スチロールでけものフレンズに出てくるぼすてきな何かを作った時の技術的な記録

経緯や詳細は下記の記事に書いたのだが、けものフレンズというアニメに出てくるボス(ラッキービースト)というロボットみたいな何かをiPhoneで作ってみた。

至極簡易的ではあるものの、はじめてロボット/AI的なものを自分で作ってみて色々と技術的にも学んだことが多かったので、ここに書き記しておこうと思う。 “iPhoneと発泡スチロールでけものフレンズに出てくるぼすてきな何かを作った時の技術的な記録” の続きを読む

iPhoneでLINEやAmazon Prime Photosが勝手にログアウトするようになってしまった話

ある日、LINEの通知が届いたのでアプリを開こうとしたら、ログイン画面が立ち上がった。
乗っ取られたのかとも思ったが、他の端末でログインした旨の通知はなかった。IDとパスワードを入力したところ、普通にログインすることができた。会話のログは失われてしまったが。念のため、パスワードを変更しておいた。
しばらくして、再びLINEの通知が届いたのでアプリを開こうとしたところ、再びログイン画面が表示され、IDとパスワードの入力を要求された。その後は、アプリを起動するたびにIDとパスワードを要求されるようになった。 “iPhoneでLINEやAmazon Prime Photosが勝手にログアウトするようになってしまった話” の続きを読む