paint-brush
Apple エコシステム全体の複数のプラットフォーム向けに Xcode プロジェクトを構築する方法@darrylbayliss
165 測定値 新しい歴史

Apple エコシステム全体の複数のプラットフォーム向けに Xcode プロジェクトを構築する方法

Darryl Bayliss9m2025/02/12
Read on Terminal Reader

長すぎる; 読むには

Apple エコシステム全体の複数のプラットフォーム向けに Xcode プロジェクトを構築する方法を学びます。
featured image - Apple エコシステム全体の複数のプラットフォーム向けに Xcode プロジェクトを構築する方法
Darryl Bayliss HackerNoon profile picture
0-item
1-item

2023年6月、AppleがVision Proを発表したとき、私はヘッドセットでうまく機能しそうなアイデアを思いつきました。それは、日常の使用に合わせて再生されるループ動画のコレクションです。私はすでに、これを実現できるChristmas Chillというアプリを持っていました。これは、App Storeをサポートする最初のApple TVが利用可能になったときに作成したものです。このアプリには、お祝いの背景として使用できるループ動画のコレクションが含まれています。


毎年冬になると、数日かけて改良し、新しいコンテンツを追加し、コードベースを改善しています。プロジェクトに加えられた大きな変更の 1 つは、2023 年 12 月に UI が UIKit と Storyboards から SwiftUI に移行されたときです。


まあ、ほとんど移行されました。 UIViewRepresentableでラップされた AVPlayer ベースのビューが必要でした。必要であれば、UIKit と SwiftUI 間の相互運用性を提供する優れた API です。


私はReactJetpack Compose を使った他の作業で Declarative UI とその概念をよく理解していたため、移行を早めることに多少躊躇していました。しかし、Apple が Apple Vision Pro を導入し、SwiftUI をサポートしたことで状況は変わりました。Christmas Chill は、私の Apple Dev の知識を最新の状態に保つための素晴らしいプロジェクトであり、アプリをさまざまなデバイスに拡張する経験を積みたいと思っていました。


2023 年の Christmas Chill の SwiftUI への移行が完了したら、2024 年に Vision Pro のサポートを追加することにしました。以下は、私がどのように取り組んだか、また、皆さんが自分のアプリで同じことをしようとしている場合にお勧めする方法です。

新しいプラットフォームの追加

まず、プロジェクトを Vision Pro を宛先としてビルドできる必要があります。これは驚くほど簡単です。Xcode 内で.xcodeprojファイルを選択し、 [サポートされている宛先] ドロップダウンの下にあるプラス ボタンをクリックします。


サポートされている目的地



利用可能なすべての Apple プラットフォームのドロップダウンが表示されます。宛先として追加する目的のプラットフォーム (この場合は Apple Vision) にマウスを移動し、新しく表示されるセクションで Apple Vision をクリックします。


Apple Vision Proの宛先を追加



Xcode がターゲットに対して行う必要がある変更を通知する小さなポップアップが表示されます。 [有効にする]をクリックします。


宛先サポートを有効にする


次に、visionOS シミュレータを使用してアプリをビルドします。Vision Pro をお持ちの場合は、デバイスにインストールする方法の説明をこちらで確認できます。


コンパイル中に、Xcode がコンパイラ エラーを検出したり、アプリがクラッシュしたりする可能性があります。これは想定内のことであり、忍耐の練習になります。この時点から、アプリがコンパイルされてクラッシュしなくなるまで、プロジェクト内のエラーを修正する必要があります。


私の場合、これに約 30 分かかりましたが、これは以前にアプリを UIKit から SwiftUI に移行するという大変な作業を行ったおかげでもあります。

条件付きコンパイルブロック

SwiftUI は、本質的にはマルチプラットフォーム フレームワークです。つまり、SwiftUI コードを別のプラットフォーム用にコンパイルするだけで、外観が変わります。プラットフォームのスタイルとさまざまなインタラクション方法を考慮します。


これは開発を迅速に進めるのに役立ちますが、アプリの外観をより細かく制御し、各プラットフォームの長所を活用したい場合もあります。良い例は Vision Pro の Immersion 機能です。SwiftUI は、visionOS でのみ利用可能な API であるImmersiveSpaceを介して、この機能の API を提供します。


Apple TV 用にプロジェクトをコンパイル中にこの API を使用しようとすると、Xcode はこの API が利用できないことを通知するエラーをスローします。


では、この状況を回避するにはどのような解決策があるのでしょうか。その答えは、 条件付きコンパイル ブロックを使用することです。コンパイル ブロックは、ブロック内のコードをコンパイラがいつコンパイルするかを指示するコード セクションです。


これらはさまざまな条件をサポートしていますが、私たちのニーズに最も役立つのは、コードがどのプラットフォーム用にコンパイルされているかを検出することです。これは、わずか数行のコードで実行できます。


 var body: some Scene { #if os(tvOS) WindowGroup { HStack { Text("I am running on tvOS!") } } #elseif os(visionOS) ImmersiveSpace(id: "MyImmersiveSpace") { } #endif }


Xcode が条件付きコンパイル ブロックをサポートするために行っている便利な機能は、コンパイル用に選択されたプラットフォームに応じて、どのコードがコンパイルされるかを明確にすることです。また、コンパイルされないコードはわずかにフェードアウトされます。


条件付きコンパイルブロック

ビルドフェーズによる依存性注入

私が見つけた便利なトリックの 1 つは、ソースのコンパイルバンドル リソースのコピーのビルド フェーズを依存関係の注入の形式として使用することです。これらのプロセスは、アプリのビルド時に実行され、Xcode プロジェクトの[ビルド フェーズ]タブで確認できます。


ビルドフェーズ


Compile Sources は、ソース コードをマシン コードにコンパイルするという面倒な作業を実行します。Swift、Objective-C、さらには C/C++ でも同じです。

バンドル リソースのコピーは、アプリ ターゲットのすべての関連リソースをApp Bundleにコピーします。画像、ビデオ、ローカライズ可能な文字列など、アプリのすべてのコードとリソースを格納するコンテナーのようなものです


これら 2 つのビルド フェーズにより、新しいターゲットごとに独自のビルド フェーズ (上記の 2 つの手順を含む) が提供されるため、アプリの柔軟性が大幅に高まります。企業がコンテンツをカスタマイズできる手段を提供するホワイトラベル アプリでは、この手法などが使用されています。


アプリが実行されるプラットフォームに応じて、独自のアプリに異なるコンテンツを提供したい場合があります。これらのビルド フェーズを活用して、2 つの異なるコンテンツ ソースを提供しましょう。


まず、Swift プロトコルを使用して、構造体またはクラスによって実行されることが期待されるコントラクトを提供しましょう。


 protocol ContentManager { var content: [Content] { get } }


次に、プロトコルの 2 つの実装を見てみましょう。最初の実装は次のとおりです。


 class TargetAppAContentManager : ContentManager { var content: [Content] { return [ Content(name: TargetAppAContentIdentifier.videoOneName.rawValue, image: TargetAppAImagePreviewIdentifier.videoOnePreview.rawValue, video: TargetAppAImageVideoIdentifier.videoOneVideo.rawValue), Content(name: TargetAppAContentIdentifier.videoTwoName.rawValue, image: TargetAppAImagePreviewIdentifier.videoTwoPreview.rawValue, video: TargetAppAImageVideoIdentifier.videoTwoVideo.rawValue), Content(name: TargetAppAContentIdentifier.videoThreeName.rawValue, image: TargetAppAImagePreviewIdentifier.videoThreePreview.rawValue, video: TargetAppAImageVideoIdentifier.videoThreeVideo.rawValue), ] return contentToShow } }


TargetAppAContentManager 、最初のアプリ ターゲットに使用される具体的な実装です。これは、ターゲットのアプリ バンドルにあるリソース名を参照するContentの配列を提供します。


 class TargetAppBContentManager : ContentManager { var content: [Content] { return [ Content(name: TargetAppBContentIdentifier.videoOneName.rawValue, image: TargetAppBImagePreviewIdentifier.videoOnePreview.rawValue, video: TargetAppBImageVideoIdentifier.videoOneVideo.rawValue), Content(name: TargetAppBContentIdentifier.videoTwoName.rawValue, image: TargetAppBImagePreviewIdentifier.videoTwoPreview.rawValue, video: TargetAppBImageVideoIdentifier.videoTwoVideo.rawValue), Content(name: TargetAppBContentIdentifier.videoThreeName.rawValue, image: TargetAppBImagePreviewIdentifier.videoThreePreview.rawValue, video: TargetAppBImageVideoIdentifier.videoThreeVideo.rawValue), ] } }


次はTargetAppBContentManagerです。これは、2 番目のアプリ ターゲットに使用される具体的な実装です。App B の識別子が異なることを除いて、最初の実装と非常によく似ています。


両方の実装が作成されると、オブジェクトのタイプをContentManagerに設定することで、コード内で間接的に参照できるようになります。以下の ViewModel の例を確認してください。


 @Observable class VideoListViewModel { var contentManager: ContentManager init(contentManager: ContentManager) { self.contentManager = contentManager } }


ViewModel は、初期化子を介して渡されるContentManagerのタイプを想定しています。ViewModel は、どちらのタイプのContentManagerでも渡すことができ、期待どおりに機能し続けます。これは、ViewModel が両方のアプリ ターゲットで再利用できることも意味します。


最後に、正しい ContentManager がソースのコンパイル フェーズに追加されていることを確認します。この場合、App A にはソースの一部としてTargetAppAContentManangerが渡され、App B にはTargetAppBContentManagerが渡されます。


コンテンツ マネージャーをソース ビルド フェーズのコンパイルに追加する

アプリバンドルリソースの追加

最後に、各アプリ バンドルに、アプリで使用される識別子と一致する名前のリソースが含まれていることを確認します。簡単な方法は、各アプリ ターゲットのCopy Bundle Resourcesビルド フェーズをチェックし、リソースがコンテンツ マネージャーによって参照されていることを確認することです。参照されていない場合は、Xcode プロジェクトからリソースのコピー フェーズにドラッグします。


参照されているリソースがバンドル内で利用できない場合はコンパイル時エラーが発生しないため、テストには少し時間と注意が必要です。実行時にはクラッシュが発生します。


チェックを自動化する良い方法は、 ContentManagerによって参照されるすべてのリソースがバンドルに格納されていることを確認するための単体テストを作成することです。実行時にテストが失敗した場合、バンドル内に不足しているリソースがあることがわかります。

次はどこへ行く?

ここまで読んでいただければ、アプリを他の Apple プラットフォームに移植する方法についてよく理解できたはずです。


この投稿を締めくくるにあたり、私がお勧めするヒントとリソースをいくつか紹介します。


  1. 既存のアプリに Apple Vision サポートを追加する場合は、まず、できるだけ多くのコードを UIKit から SwiftUI に移行します。Vision Pro で動作する既存のアプリを SwiftUI に移行したときの速度を確認したので、信頼できるアプリであることが分かります。


  2. 既存のアプリを visionOS に移行する際の Apple のガイダンスをお読みください。このガイダンスには、移行方法や visionOS 機能の活用方法に関する役立つヒントや提案が記載されています。


  3. 自分で新しいマルチプラットフォーム アプリを始めようと考えている場合は、Xcode にマルチプラットフォーム タブがあり、使用できるアプリ テンプレートが多数用意されています。このテーマについては、 WWDC 2022 のビデオもあります。


  4. 複数のプラットフォームで動作するアプリの例をご覧になりたい場合は、私の個人アプリChristmas ChillOcean Chillをチェックすることをお勧めします。これらは、単一のコードベースから構築された、tvOS と Vision Pro で動作する 2 つのアプリです。(Ocean Chill の tvOS サポートは近日中に開始されます!)