carplay について
Swift開発者向けのCarPlay開発スキルです。カテゴリ別の権限とシステムテンプレートを使うアプリについて、シーン設定、ナビゲーション、オーディオ、通信、地点表示、シミュレーターでのテストを扱います。
主な機能
- 01CarPlayカテゴリ権限の設定
- 02CarPlayシーンとデリゲートの構成
- 03システムテンプレートによる画面構築
- 04ナビ・オーディオ・通信フローの実装
- 05地点情報と詳細の表示
- 06CarPlay Simulatorでのテスト
活用シーン
- 01車載ナビアプリの開発
- 02オーディオの閲覧と再生画面の表示
- 03車載メッセージ・連絡先画面の構築
- 04充電・駐車・飲食地点の表示
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill carplayインストール後、Claude Code で/carplayを入力して読み込まれたか確認します。
同梱ファイルevals/evals.jsonreferences/carplay-patterns.md
詳細
CarPlay
車載ディスプレイ向けに、カテゴリのエンタイトルメントを持つテンプレートベースの CarPlay アプリを構築します。 対象範囲:Swift 6.3、iOS 26 以降。
完全なナビゲーションセッション、ダッシュボードシーン、高度なテンプレート構成など、拡張パターンについてはreferences/carplay-patterns.mdを参照してください。
対象範囲の境界:CarPlay フレームワークを使用する完全なアプリでは、カテゴリのエンタイトルメント、CPTemplateApplicationScene、CPTemplateApplicationSceneDelegate、CPInterfaceController、およびシステムのCPTemplateナビゲーションを使用します。CarPlay に表示される WidgetKit ウィジェットと ActivityKit Live Activities は、別個のシステム体験です。その実装はそれぞれの領域に振り分け、CarPlay 固有の検証はここで行ってください。
目次
- エンタイトルメントとセットアップ
- シーンの構成
- テンプレートの概要
- ナビゲーションアプリ
- オーディオアプリ
- コミュニケーションアプリ
- スポット情報アプリ
- CarPlay シミュレータでのテスト
- よくある間違い
- レビュー用チェックリスト
- 参考資料
エンタイトルメントとセットアップ
Apple の CarPlay エンタイトルメント申請フォームからカテゴリ固有のエンタイトルメントを申請し、追加条項に同意した後、承認されたカテゴリキーを以下のようにプロビジョニングしてください。
カテゴリ別のエンタイトルメントキー
| エンタイトルメント | カテゴリ |
|---|---|
com.apple.developer.carplay-audio | オーディオ |
com.apple.developer.carplay-communication | コミュニケーション |
com.apple.developer.carplay-maps | ナビゲーション |
com.apple.developer.carplay-charging | EV 充電 |
com.apple.developer.carplay-parking | 駐車 |
com.apple.developer.carplay-quick-ordering | クイックフード注文 |
プロジェクトの構成
- デベロッパーポータルの「追加機能」で App ID を更新します。
- 更新した App ID 用の新しいプロビジョニングプロファイルを生成します。
- Xcode で自動署名を無効にし、CarPlay プロビジョニングプロファイルを読み込みます。
- エンタイトルメントキーを
trueに設定したEntitlements.plistを追加します。 - Code Signing Entitlements のビルド設定に
Entitlements.plistのパスを設定します。
主な型
| 型 | 役割 |
|---|---|
CPTemplateApplicationScene | CarPlay ディスプレイ用の UIScene サブクラス |
CPTemplateApplicationSceneDelegate | シーンの接続/切断ライフサイクル |
CPInterfaceController | ルートテンプレートの設定、テンプレートのプッシュ、表示、ポップを行う CarPlay 提供のコントローラー |
CPTemplate | すべての CarPlay テンプレートの抽象基底クラス |
CPSessionConfiguration | 車載ディスプレイの制限とコンテンツスタイル |
シーンの構成
Info.plistで CarPlay シーンを宣言し、CarPlay の接続時に応答するようCPTemplateApplicationSceneDelegateを実装します。
Info.plist のシーンマニフェスト
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>CPTemplateApplicationSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>CPTemplateApplicationScene</string>
<key>UISceneConfigurationName</key>
<string>CarPlaySceneConfiguration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).CarPlaySceneDelegate</string>
</dict>
</array>
</dict>
</dict>
シーンデリゲート(ナビゲーション以外)
ナビゲーション以外のアプリが受け取るのはインターフェースコントローラーのみです。ウィンドウはありません。
import CarPlay
final class CarPlaySceneDelegate: UIResponder,
CPTemplateApplicationSceneDelegate {
var interfaceController: CPInterfaceController?
func templateApplicationScene(
_ templateApplicationScene: CPTemplateApplicationScene,
didConnect interfaceController: CPInterfaceController
) {
self.interfaceController = interfaceController
interfaceController.setRootTemplate(buildRootTemplate(),
animated: true, completion: nil)
}
func templateApplicationScene(
_ templateApplicationScene: CPTemplateApplicationScene,
didDisconnectInterfaceController interfaceController: CPInterfaceController
) {
self.interfaceController = nil
}
}
シーンデリゲート(ナビゲーション)
ナビゲーションアプリはインターフェースコントローラーとCPWindowの両方を受け取ります。
地図コンテンツを描画するには、ウィンドウのルートビューコントローラーを設定します。
func templateApplicationScene(
_ templateApplicationScene: CPTemplateApplicationScene,
didConnect interfaceController: CPInterfaceController,
to window: CPWindow
) {
self.interfaceController = interfaceController
self.carWindow = window
window.rootViewController = MapViewController()
let mapTemplate = CPMapTemplate()
mapTemplate.mapDelegate = self
interfaceController.setRootTemplate(mapTemplate, animated: true,
completion: nil)
}
テンプレートの概要
CarPlay には固定のテンプレートタイプが用意されています。アプリがコンテンツを提供し、システムが車載ディスプレイに表示します。
汎用テンプレート
| テンプレート | 用途 |
|---|---|
CPTabBarTemplate | タブ付きの子テンプレートを格納するコンテナー |
CPListTemplate | スクロール可能なセクション別リスト |
CPGridTemplate | タップ可能なアイコンボタンのグリッド(最大 8 個) |
CPInformationTemplate | 最大 3 個のアクションを備えたキーと値の情報 |
CPAlertTemplate | 最大 2 個のアクションを備えたモーダルアラート |
CPActionSheetTemplate | モーダルアクションシート |
カテゴリー別テンプレート
| テンプレート | カテゴリー |
|---|---|
CPMapTemplate | ナビゲーション -- ナビゲーションバー付きの地図オーバーレイ |
CPSearchTemplate | ナビゲーション -- 目的地の検索 |
CPNowPlayingTemplate | オーディオ -- 共有の「再生中」画面 |
CPPointOfInterestTemplate | EV 充電/駐車場/飲食 -- POI マップ |
CPContactTemplate | コミュニケーション -- 連絡先カード |
ナビゲーション階層
pushTemplate(_:animated:completion:) を使用してテンプレートをスタックに追加します。
モーダル表示には presentTemplate(_:animated:completion:) を使用します。
戻るには popTemplate(animated:completion:) を使用します。
CPTabBarTemplate はルートに設定する必要があり、プッシュまたは表示することはできません。
CPTabBarTemplate
let browseTab = CPListTemplate(title: "Browse",
sections: [CPListSection(items: listItems)])
browseTab.tabImage = UIImage(systemName: "list.bullet")
let tabBar = CPTabBarTemplate(templates: [browseTab, settingsTab])
tabBar.delegate = self
interfaceController.setRootTemplate(tabBar, animated: true, completion: nil)
CPListTemplate
let item = CPListItem(text: "Favorites", detailText: "12 items")
item.handler = { selectedItem, completion in
self.interfaceController?.pushTemplate(detailTemplate, animated: true,
completion: nil)
completion()
}
let section = CPListSection(items: [item], header: "Library",
sectionIndexTitle: nil)
let listTemplate = CPListTemplate(title: "My App", sections: [section])
ナビゲーションアプリ
ナビゲーションアプリでは com.apple.developer.carplay-maps を使用します。地図コンテンツの描画に CPWindow を受け取るのは、このカテゴリーだけです。ルートテンプレートには CPMapTemplate を設定する必要があります。
トリップのプレビューとルート選択
let routeChoice = CPRouteChoice(
summaryVariants: ["Fastest Route", "Fast"],
additionalInformationVariants: ["Via Highway 101"],
selectionSummaryVariants: ["25 min"]
)
let trip = CPTrip(origin: origin, destination: destination,
routeChoices: [routeChoice])
mapTemplate.showTripPreviews([trip], textConfiguration: nil)
ナビゲーションセッションの開始
extension CarPlaySceneDelegate: CPMapTemplateDelegate {
func mapTemplate(_ mapTemplate: CPMapTemplate,
startedTrip trip: CPTrip,
using routeChoice: CPRouteChoice) {
let session = mapTemplate.startNavigationSession(for: trip)
session.pauseTrip(for: .loading, description: "Calculating route...")
let maneuver = CPManeuver()
maneuver.instructionVariants = ["Turn right onto Main St"]
maneuver.symbolImage = UIImage(systemName: "arrow.turn.up.right")
session.upcomingManeuvers = [maneuver]
let estimates = CPTravelEstimates(
distanceRemaining: Measurement(value: 5.2, unit: .miles),
timeRemaining: 900)
session.updateEstimates(estimates, for: maneuver)
}
}
地図ボタン
let zoomIn = CPMapButton { _ in self.mapViewController.zoomIn() }
zoomIn.image = UIImage(systemName: "plus.magnifyingglass")
mapTemplate.mapButtons = [zoomIn, zoomOut]
CPSearchTemplate
extension CarPlaySceneDelegate: CPSearchTemplateDelegate {
func searchTemplate(_ searchTemplate: CPSearchTemplate,
updatedSearchText searchText: String,
completionHandler: @escaping ([CPListItem]) -> Void) {
performSearch(query: searchText) { results in
completionHandler(results.map {
CPListItem(text: $0.name, detailText: $0.address)
})
}
}
func searchTemplate(_ searchTemplate: CPSearchTemplate,
selectedResult item: CPListItem,
completionHandler: @escaping () -> Void) {
// Navigate to selected destination
completionHandler()
}
}
オーディオアプリ
オーディオアプリでは com.apple.developer.carplay-audio を使用します。リストに閲覧可能なコンテンツを表示し、再生コントロールには CPNowPlayingTemplate を使用します。オーディオの権限を持つアプリでは CPInformationTemplate を利用できません。
「再生中」テンプレート
CPNowPlayingTemplate は共有シングルトンです。MPNowPlayingInfoCenter からメタデータを読み込みます。新しいインスタンスを作成しないでください。
let nowPlaying = CPNowPlayingTemplate.shared
nowPlaying.isUpNextButtonEnabled = true
nowPlaying.isAlbumArtistButtonEnabled = true
nowPlaying.updateNowPlayingButtons([
CPNowPlayingShuffleButton { _ in self.toggleShuffle() },
CPNowPlayingRepeatButton { _ in self.toggleRepeat() }
])
nowPlaying.add(self) // Register as CPNowPlayingTemplateObserver
Siri アシスタントセル
INPlayMediaIntent をサポートするオーディオアプリでは、アシスタントセルを表示できます。コミュニケーションアプリでは .startCall とともに INStartCallIntent を使用します。
let config = CPAssistantCellConfiguration(
position: .top, visibility: .always, assistantAction: .playMedia)
let listTemplate = CPListTemplate(
title: "Playlists",
sections: [CPListSection(items: items)],
assistantCellConfiguration: config)
コミュニケーションアプリ
コミュニケーションアプリでは com.apple.developer.carplay-communication を使用します。メッセージリストと連絡先を表示し、Siri による発信に INStartCallIntent をサポートします。
CPMessageListItem には、アプリ側で指定する選択ハンドラーがありません。選択すると、その項目の電話番号/メールアドレス、未読状態、または既存の会話設定に基づいて、CarPlay が Siri による作成、読み上げ、返信の動作を実行します。
let leading = CPMessageListItemLeadingConfiguration(
leadingItem: .star, leadingImage: nil, unread: true)
let trailing = CPMessageListItemTrailingConfiguration(
trailingItem: .none, trailingImage: nil)
let message = CPMessageListItem(
conversationIdentifier: "conv-123",
text: "Jane",
leadingConfiguration: leading,
trailingConfiguration: trailing,
detailText: "Meeting at 3pm",
trailingText: "2:45 PM")
let messageList = CPListTemplate(title: "Messages",
sections: [CPListSection(items: [message])])
POI アプリ
EV 充電、駐車場、飲食の注文アプリでは、CPPointOfInterestTemplate と CPInformationTemplate を使用して場所と詳細を表示します。
CPPointOfInterestTemplate には、最大 12 件の POI が表示されます。
CPPointOfInterestTemplate
let poi = CPPointOfInterest(
location: MKMapItem(placemark: MKPlacemark(
coordinate: CLLocationCoordinate2D(latitude: 37.7749,
longitude: -122.4194))),
title: "SuperCharger Station", subtitle: "4 available",
summary: "150 kW DC fast charging",
detailTitle: "SuperCharger Station", detailSubtitle: "$0.28/kWh",
detailSummary: "Open 24 hours",
pinImage: UIImage(systemName: "bolt.fill"))
poi.primaryButton = CPTextButton(title: "Navigate",
textStyle: .confirm) { _ in }
let poiTemplate = CPPointOfInterestTemplate(
title: "Nearby Chargers", pointsOfInterest: [poi], selectedIndex: 0)
poiTemplate.pointOfInterestDelegate = self
CPInformationTemplate
let infoTemplate = CPInformationTemplate(
title: "Order Summary", layout: .leading,
items: [
CPInformationItem(title: "Item", detail: "Burrito Bowl"),
CPInformationItem(title: "Total", detail: "$12.50")],
actions: [
CPTextButton(title: "Place Order", textStyle: .confirm) { _ in
self.placeOrder() },
CPTextButton(title: "Cancel", textStyle: .cancel) { _ in
self.interfaceController?.popTemplate(animated: true,
completion: nil) }])
CarPlay シミュレータでのテスト
- Xcode で iOS シミュレータを使ってビルドし、実行します。
- I/O > External Displays > CarPlay を選択します。
デフォルトのウィンドウサイズは 800x480、スケールは @2x です。ナビゲーションアプリでは、追加のオプションを有効にします。
defaults write com.apple.iphonesimulator CarPlayExtraOptions -bool YES
推奨テスト構成
| 構成 | ピクセル数 | スケール |
|---|---|---|
| 最小 | 748 x 456 | @2x |
| 縦向き | 768 x 1024 | @2x |
| 標準 | 800 x 480 | @2x |
| 高解像度 | 1920 x 720 | @3x |
シミュレータでは、iPhone のロック中の動作、Siri、カーラジオとのオーディオの共存、物理入力ハードウェア(ノブ、タッチパッド)をテストできません。可能な場合は、CarPlay 対応の実車またはアフターマーケットのヘッドユニットでテストしてください。 CarPlay の使用中に iPhone の操作を必要としないよう、主要な CarPlay フローを設計してください。
よくある間違い
やってはいけないこと:間違ったシーンデリゲートメソッドを使う
ナビゲーションアプリでは templateApplicationScene(_:didConnect:to:)(CPWindow を使用)を実装する必要があります。ナビゲーション以外のアプリでは templateApplicationScene(_:didConnect:)(ウィンドウなし)を使用します。間違ったメソッドを使うと、CarPlay の UI が表示されません。
やってはいけないこと:ナビゲーションウィンドウにカスタム UI を描画する
CPWindow は地図コンテンツ専用です。すべてのオーバーレイ、アラート、コントロールには CarPlay テンプレートを使用する必要があります。
やってはいけないこと:CPTabBarTemplate をプッシュまたは表示する
CPTabBarTemplate はルートにのみ設定できます。プッシュまたは表示すると失敗します。setRootTemplate(_:animated:completion:) を使用してください。
やってはいけないこと:CPNowPlayingTemplate をインスタンス化する
CPNowPlayingTemplate.shared を使用してください。新しいインスタンスを作成すると、問題が発生します。
禁止:CPMessageListItem にハンドラーを追加する
CPMessageListItem は Siri によって管理されます。CPListItem とは異なります。
message.handler は設定せず、コンテキストにはアイテムの設定と userInfo を使用してください。
禁止:ウィジェットを CarPlay テンプレートアプリとして扱う
CarPlay に表示されるウィジェットとライブアクティビティは、WidgetKit と ActivityKit に属します。このスキルは、カテゴリのエンタイトルメントを持つ CarPlay テンプレートアプリのシーンと、車内でのそれらの表示面の検証に使用してください。
禁止:車載ディスプレイの制限を無視する
CPSessionConfiguration.limitedUserInterfaces を確認し、リストテンプレートの maximumItemCount / maximumSectionCount を守ってください。
禁止:完了ハンドラーの呼び出しを忘れる
CPListItem.handler は、すべてのコードパスで完了ハンドラーを呼び出す必要があります。呼び出さないと、リストが読み込み中の状態のままになります。
レビューチェックリスト
-
Entitlements.plistの CarPlay エンタイトルメントキーが正しい -
UIApplicationSupportsMultipleScenesがtrueに設定されている - Info.plist に
CPTemplateApplicationSceneSessionRoleApplicationシーンがある - シーンデリゲートのクラス名が
UISceneDelegateClassNameと一致している - 正しいデリゲートメソッドを使用している(
CPWindowの有無) - 戻る前に
didConnectでルートテンプレートを設定している - 切断時にインターフェイスコントローラーとウィンドウへの参照をクリアしている
-
CPTabBarTemplateはルートとしてのみ使用し、プッシュしていない - 新しいインスタンスではなく
CPNowPlayingTemplate.sharedを使用している - 通信の行では、カスタムハンドラーを設定せずに
CPMessageListItemを使用している - WidgetKit / ActivityKit の表示面を CarPlay テンプレートアプリのコード外に振り分けている
- リストにデータを入力する前に
maximumItemCount/maximumSectionCountを確認している -
CPListItem.handlerがすべてのコードパスで完了ハンドラーを呼び出している - 地図のみのコンテンツは
CPWindowのルートビューコントローラーに配置している(ナビゲーションアプリ) - iPhone がロックされていてもアプリが動作する
- 最小、標準、高解像度のシミュレーターサイズでテストしている
- 再生中でないときにオーディオセッションを非アクティブ化している
参考資料
- 拡張パターン(ダッシュボード、インストルメントクラスター、ナビゲーション全体のフロー、タブの構成):references/carplay-patterns.md
- CarPlay フレームワーク
- CPTemplateApplicationSceneDelegate
- CPInterfaceController
- CPMapTemplate
- CPListTemplate
- CPNowPlayingTemplate
- CPPointOfInterestTemplate
- CPNavigationSession
- CarPlay エンタイトルメントの申請
- CarPlay でのコンテンツの表示
- CarPlay シミュレーターの使用
- CarPlay HIG