carplay について
carplay は、iOS 向け CarPlay アプリを開発する人のためのリファレンススキルです。10 種類のアプリカテゴリ、フレームワーク、テンプレートに加え、権限、通知、ナビゲーション、ウィジェット、シミュレーターでのテストを扱います。
主な機能
- 0110 種類の CarPlay アプリカテゴリを網羅
- 02テンプレート、UI、ナビゲーション API を参照
- 03カテゴリごとの権限とテンプレート制限を説明
- 04通知、ウィジェット、Live Activities を解説
- 05CarPlay Simulator でのテストを案内
活用シーン
- 01CarPlay の音声、ポッドキャスト、音楽アプリを開発
- 02ナビゲーション、通信、駐車、充電アプリを構築
- 03CarPlay に通知、ウィジェット、Live Activities を追加
- 04シミュレーターで複数の車載画面サイズをテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ ios-agent/ iosagent.dev --skill carplayインストール後、Claude Code で/carplayを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/api_reference.mdaudio.mdcommunication.mddashboard.mddeveloper_guide.mdgetting_started.mdindex.mdnavigation.mdpoi.mdtemplates.mdui_elements.md詳細
CarPlay スキル
このスキルを使用する場面
次の場合にこのスキルを使用します:
- iOS 向けの CarPlay アプリを開発する
- CarPlay 向けのオーディオ、ポッドキャスト、または音楽アプリを構築する
- ターンバイターン方式の案内に対応したナビゲーションアプリを作成する
- コミュニケーションアプリ(メッセージ、VoIP)を実装する
- 駐車場、EV 充電、またはフード注文アプリを構築する
- 運転タスク、給油、または公共安全アプリを構築する
- 音声ベースの会話型アプリ(iOS 26.4 以降)を作成する
- CarPlay にウィジェットやライブアクティビティを追加する(iOS 26 以降)
- CarPlay の通知を実装する
- CarPlay マップでマルチタッチナビゲーションを有効にする(iOS 26 以降)
- インストルメントクラスターまたは HUD のメタデータを提供する(iOS 17.4 以降)
- CPTemplate、CPInterfaceController、または CarPlay API を扱う
説明
CarPlay フレームワークの包括的なドキュメントで、オーディオ、コミュニケーション、ナビゲーション、駐車場、EV 充電、クイックフード注文、給油、運転タスク、公共安全、音声ベースの会話型の 10 種類すべてのアプリカテゴリを網羅しています。すべてのテンプレート、UI 要素、ナビゲーション API、ウィジェット、ライブアクティビティ、CarPlay Ultra、通知、インストルメントクラスターのメタデータ、マルチタッチナビゲーションが含まれています。
クイックリファレンス
コアコンポーネント
API リファレンス
CPInterfaceControllerCPWindowCarPlay
コミュニケーション
CPContactCPMessageListItem
はじめに
CPSessionConfigurationCPTemplateApplicationSceneCPTemplateApplicationSceneDelegateCPTemplateApplicationScene.open(_:options:completionHandler:)
ナビゲーション
CPManeuverCPNavigationSessionCPRouteChoiceCPRouteInformationCPTripCPManeuverTypeCPJunctionTypeCPTrafficSideCPLaneStatus
POI
CPPointOfInterest
テンプレート
CPActionSheetTemplateCPAlertTemplateCPContactTemplateCPGridTemplateCPInformationTemplateCPListTemplateCPMapTemplateCPNowPlayingTemplateCPPointOfInterestTemplateCPSearchTemplateCPTabBarTemplateCPTemplateCPVoiceControlTemplate
UI 要素
CPAlertActionCPBarButtonCPButtonCPGridButtonCPInformationItemCPListImageRowItemCPListItemCPListSectionCPTextButton
ダッシュボード(iOS 13.4 以降)
CPDashboardButtonCPDashboardControllerCPTemplateApplicationDashboardSceneCPTemplateApplicationDashboardSceneDelegate
インストルメントクラスター(iOS 15.4 以降)
CPInstrumentClusterControllerCPTemplateApplicationInstrumentClusterSceneCPTemplateApplicationInstrumentClusterSceneDelegate
通知
UNAuthorizationOptions.carPlay.allowInCarPlay
ウィジェットとライブアクティビティ
.supportedFamilies.supplementalActivityFamilies
主要な概念
プラットフォームのサポート
- iOS 12.0 以降(CarPlay のコア機能)
- iOS 13.4 以降(ダッシュボードシーン)
- iOS 14.0 以降(タブバー、POI、「再生中」)
- iOS 15.4 以降(インストルメントクラスター)
- iOS 16.0 以降(給油、運転タスクのカテゴリ、POI の selectedPinImage、Information のナビゲーションバーボタン)
- iOS 17.4 以降(CPRouteInformation、resumeTrip、ナビゲーションメタデータ、ルート再設定)
- iOS 18.4 以降(「再生中」のスポーツモード)
- iOS 26 以降(CarPlay のウィジェット、CarPlay のライブアクティビティ、マルチタッチナビゲーション、リスト要素のスタイル)
- iOS 26.4 以降(音声ベースの会話型アプリ、音声コントロールのアクションボタン/ナビゲーションバーボタン)
- iPadOS 12.0 以降
- Mac Catalyst 13.1 以降
CarPlay アプリのカテゴリ
⚠️ 重要: CarPlay アプリには Apple のエンタイトルメントが必要です。アプリを CarPlay に接続する前に、アプリのカテゴリに対する申請を行い、承認を受ける必要があります。
CarPlay は以下の 10 カテゴリのアプリをサポートしています(それぞれ個別のエンタイトルメントが必要です)。
-
オーディオとポッドキャスト(iOS 12 以降、階層の上限:5)
- エンタイトルメント:
com.apple.developer.carplay-audio - 要件:オーディオ再生、「再生中」との連携、
MPNowPlayingInfoCenter
- エンタイトルメント:
-
コミュニケーション(iOS 12 以降、階層の上限:5)
- エンタイトルメント:
com.apple.developer.carplay-communication - 要件:CallKit との連携、SiriKit との連携
- エンタイトルメント:
-
ナビゲーション(iOS 12 以降、階層の上限:5)
- エンタイトルメント:
com.apple.developer.carplay-maps - 要件:リアルタイムナビゲーション、ルート案内、地図の描画
- エンタイトルメント:
-
駐車(iOS 14 以降、階層の上限:5)
- エンタイトルメント:
com.apple.developer.carplay-parking - 要件:位置情報サービス、駐車場の空き状況
- エンタイトルメント:
-
EV 充電(iOS 14 以降、階層の上限:5)
- エンタイトルメント:
com.apple.developer.carplay-charging - 要件:充電ステーションのデータ、利用状況
- エンタイトルメント:
-
クイックフード注文(iOS 14 以降、階層の上限:3)
- エンタイトルメント:
com.apple.developer.carplay-quick-ordering - 要件:簡潔なメニュー閲覧、注文、受け取り
- エンタイトルメント:
-
給油(iOS 16 以降、階層の上限:3)
- エンタイトルメント:
com.apple.developer.carplay-fueling - 要件:給油所の位置、ポンプの操作、支払い
- エンタイトルメント:
-
運転タスク(iOS 16 以降、階層の上限:3)
- エンタイトルメント:
com.apple.developer.carplay-driving-task - 要件:運転中に行うタスク(道路状況、料金所、アクセサリなど)。ナビゲーションと重複してはなりません。
- エンタイトルメント:
-
公共安全(iOS 14 以降、階層の上限:3)
- エンタイトルメント:
com.apple.developer.carplay-public-safety - 要件:リアルタイムの安全情報(危険、速度取締カメラ、緊急警報)
- エンタイトルメント:
-
音声ベースの会話(iOS 26.4 以降、階層の上限:3)
- エンタイトルメント:
com.apple.developer.carplay-voice-based-conversation - 要件:主に音声で操作するインタラクション。
CPVoiceControlTemplateを主要な UI として使用します。
- エンタイトルメント:
エンタイトルメントの申請方法:
- https://developer.apple.com/contact/carplay/ にアクセスします
- アプリの機能とカテゴリを説明します
- Apple の審査と承認を待ちます(通常 1~2 週間)
- 承認されたエンタイトルメントを Xcode プロジェクトに追加します
テンプレートベースの UI
CarPlay はテンプレートベースのシステムを採用しており、Apple が UI コンポーネントを提供し、アプリ側がコンテンツを提供します。これにより、次のことが実現します。
- 車内での使用に最適化された、ドライバーに配慮したインターフェイス
- すべての CarPlay アプリで一貫したユーザー体験
- 車両ごとに異なるディスプレイへの自動対応
テンプレートの利用可否一覧
すべてのテンプレートをすべてのカテゴリで利用できるわけではありません。主な制限は次のとおりです。
- ナビゲーションのみ:
CPMapTemplate - コミュニケーションのみ:
CPContactTemplate - オーディオアプリ:
CPTabBarTemplateのタブは最大 4 個。それ以外は最大 5 個 - フード注文/給油/運転タスク/公共安全/音声ベース: テンプレートの階層は最大 3(オーディオ、コミュニケーション、ナビゲーション、駐車、EV 充電は最大 5)
カテゴリ別のテンプレート一覧については、references/developer_guide.md を参照してください。
CarPlay Ultra
CarPlay Ultra は、計器クラスター、センターコンソール、乗員用ディスプレイなど、車両のディスプレイと緊密に連携する Apple の次世代車載体験です。CarPlay に対応するアプリは自動的に CarPlay Ultra でも動作するため、追加の API を採用する必要はありません。CarPlay Ultra は同じテンプレートベースのシステムとエンタイトルメントを使用します。
CarPlay のウィジェット(iOS 26 以降)
WidgetKit ウィジェットは CarPlay のホーム画面に表示できます。CarPlay に対応するには、次のようにします。
- ウィジェットの
supportedFamiliesに.accessoryCircularまたは.accessoryRectangularを追加します - ウィジェットのコンテンツはシンプルで一目で分かるものにします。インタラクティブなコントロールは使用できません
- ウィジェットは iPhone と同じタイムラインで更新されます
CarPlay のライブアクティビティ(iOS 26+)
ライブアクティビティを CarPlay に表示して、配達状況、スポーツのスコア、配車状況などをリアルタイムで更新できます。利用するには、次の手順を実行します。
ActivityConfigurationのsupplementalActivityFamiliesに.carPlayを追加します- 運転に適したコンパクト表示と最小表示を使用します
- ライブアクティビティは CarPlay インターフェイスに固定バナーとして表示されます
音声ベースの会話型アプリ(iOS 26.4+)
AI アシスタントや音声主導のエクスペリエンス向けの新しいアプリカテゴリです。主要なインターフェイスとして CPVoiceControlTemplate を使用し、次の機能を備えています。
- 音声表示の下に、素早い操作のためのアクションボタンを配置します
- 補助コントロール用のナビゲーションバーボタンを配置します
- ビジュアル UI は最小限に抑え、主に音声で操作します
CarPlay での通知
アプリは CarPlay に通知を配信できます。要件は次のとおりです。
- 通知の許可をリクエストする際に、
UNAuthorizationOptionsで.carPlayをリクエストします - CarPlay のディスプレイに表示するカテゴリについて、
UNNotificationCategoryに.allowInCarPlayを設定します - 通知の内容は簡潔にし、運転状況に関係する内容にします
アセットサイズのガイド
| アセット | 推奨サイズ |
|---|---|
| リスト項目の画像 | 44×44 pt(@2x:88×88 px、@3x:132×132 px) |
| グリッドボタンの画像 | 44×44 pt |
| マップボタンの画像 | 44×44 pt |
| タブバーアイコン | 30×30 pt |
| アラート画像 | 44×44 pt |
| POI ピン画像 | 44×44 pt |
| ナビゲーションの操作記号 | 44×44 pt |
| 再生中のアートワーク | 44×44 pt(サムネイル)、最大 256×256 pt(フルサイズ) |
オーディオの処理
- オーディオアプリでは、
AVAudioSessionのカテゴリ.playbackを使用します - ナビゲーションの音声案内では、モード
.voicePromptとオプション.duckOthersおよび.interruptSpokenAudioAndMixWithOthersを使用します - オーディオアプリは、
MPRemoteCommandCenterのイベント(再生、一時停止、スキップ)に対応する必要があります - 再生前にオーディオセッションを有効にし、再生終了後に無効にします
iPhone がロックされている間のデータへのアクセス
iPhone がロックされていると、ファイルへのアクセスが制限される場合があります。CarPlay アプリが必要とするファイルには、FileProtectionType.completeUntilFirstUserAuthentication または .none を使用します。デフォルトの .completeUnlessOpen ではアクセスできない場合があります。
他のアプリの起動
CarPlay アプリは、CPTemplateApplicationScene.open(_:options:completionHandler:) を使用して他のアプリを開けます。これは、電話ベースのワークフロー(購入手続きの完了など)に引き継ぐ場合に便利です。切り替えの前に、システムがユーザーに確認します。
使用ガイドライン
- アプリのカテゴリに適したテンプレートを使用します
- コンテンツをシンプルに保ちます - ドライバーの安全のため、項目とテキストを制限します
- 見やすい大きな画像を用意します
- 接続と切断を適切に処理します
- 車両でテストする前に CarPlay Simulator でテストします
- 注意散漫を防ぐためのガイドラインに従います - 運転中は動画、アニメーション、広告を表示しません
CarPlay シミュレータでのテスト
CarPlay をテストする方法は 2 つあります。
オプション 1:Xcode シミュレータウインドウ
- CarPlay アプリのプロジェクトを Xcode で開きます
- 実行先として iOS シミュレータを選択します
- シミュレータのメニューで、I/O > External Displays > CarPlay を選択します
- iOS シミュレータと並んで CarPlay ディスプレイのウインドウが表示されます
オプション 2:スタンドアロン CarPlay シミュレータ(Mac アプリ)
- developer.apple.com から Additional Tools for Xcode をダウンロードします
- CarPlay Simulator アプリをインストールします
- 物理的な iPhone を Mac に接続します
- CarPlay Simulator がデバイスに接続し、CarPlay インターフェイスを表示します
追加オプションを有効にする:
defaults write com.apple.iphonesimulator CarPlayExtraOptions -bool YES
これにより、画面サイズの選択やクラスター表示設定などの追加オプションが有効になります。
テスト用のナビゲーション画面サイズ:
| 画面サイズ | 倍率 |
|---|---|
| 748 × 456 | @2x |
| 800 × 480 | @2x |
| 960 × 540 | @2x |
| 1024 × 600 | @2x |
| 1080 × 600 | @2x |
| 1280 × 720 | @2x |
| 900 × 1200 | @3x |
シミュレータの機能:
- すべての CarPlay テンプレートと UI 要素をテストできます
- 昼/夜モードをシミュレートできます(システムのダークモードに基づいて自動的に切り替わります)
- さまざまな画面サイズとアスペクト比をテストできます
- Siri とのやり取りや音声コマンドをシミュレートできます
- 接続/切断のシナリオをテストできます
- インストルメントクラスターの描画をテストできます(追加オプションを有効にした場合)
制限事項:
- 実際の車両との統合はテストできません
- 物理コントロール(ノブ、タッチスクリーン)のテストには制限があります
- 車両固有の機能の一部は利用できません
- オーディオのルーティングが実際の車両と異なる場合があります
ベストプラクティス: まず複数の画面サイズでシミュレータ上のテストを行い、リリース前に実車または CarPlay 対応のヘッドユニットで必ず確認してください。
ナビゲーション
カテゴリ別に整理された詳細な API ドキュメントについては、references/ ディレクトリを参照してください。
references/api_reference.md- API リファレンスreferences/communication.md- コミュニケーションreferences/dashboard.md- ダッシュボードとインストルメントクラスターreferences/developer_guide.md- 開発者ガイド(カテゴリ、チュートリアル、テスト、公開)references/getting_started.md- はじめにreferences/navigation.md- ナビゲーションreferences/poi.md- POIreferences/templates.md- テンプレートreferences/ui_elements.md- UI 要素
ベストプラクティス
- シーン管理:CarPlay 接続を処理するために
CPTemplateApplicationSceneDelegateを実装します - テンプレートの上限:最大項目数を守ります(CPListTemplate:12 セクション、CPGridTemplate:8 ボタン)
- テンプレートの深さの上限:オーディオ、コミュニケーション、ナビゲーション、駐車、EV 充電アプリは最大 5 階層です。食品注文、給油、運転タスク、公共安全、音声ベースのアプリは最大 3 階層です。
- 画像サイズ:テンプレートと UI 要素の推奨画像サイズに従います(アセットサイズガイドを参照)
- 動的な更新:更新メソッド(
updateSections、updateButtons)を使ってコンテンツを更新します - ナビゲーション階層:ナビゲーションには push/pop を使い、モーダルには present を使います
- オーディオセッション管理:
AVAudioSessionを適切に設定します。オーディオアプリでは.playback、ナビゲーションの音声プロンプトでは.voicePromptモードを使います。完了後は非アクティブ化します。 - ファイル保護:iPhone がロックされている間に必要なファイルには
.completeUntilFirstUserAuthenticationを使います - 通知:運転に関連する通知カテゴリにのみ
.allowInCarPlayを有効にします。内容は簡潔にします。 - エラー処理:失敗を適切に処理し、ユーザーにフィードバックを提供します
よくあるパターン
基本的なリストテンプレート
// Create list items
let item1 = CPListItem(text: "Song Title", detailText: "Artist Name")
item1.handler = { item, completion in
// Handle selection
completion()
}
// Create section and template
let section = CPListSection(items: [item1, item2])
let listTemplate = CPListTemplate(title: "My Playlist", sections: [section])
// Set as root
interfaceController.setRootTemplate(listTemplate, animated: true)
マップテンプレートを使ったナビゲーション
// Create map template
let mapTemplate = CPMapTemplate()
mapTemplate.mapDelegate = self
// Show trip preview
let trip = CPTrip(/* route data */)
mapTemplate.showTripPreviews([trip], textConfiguration: nil)
// Start navigation
let navigationSession = mapTemplate.startNavigationSession(for: trip)
navigationSession.upcomingManeuvers = [maneuver1, maneuver2]
タブバーのテンプレート
// Create root templates for each tab
let musicTab = CPListTemplate(title: "Music", sections: [musicSection])
musicTab.tabTitle = "Music"
musicTab.tabImage = UIImage(systemName: "music.note")
let podcastsTab = CPListTemplate(title: "Podcasts", sections: [podcastSection])
podcastsTab.tabTitle = "Podcasts"
podcastsTab.tabImage = UIImage(systemName: "mic")
// Create tab bar
let tabBarTemplate = CPTabBarTemplate(templates: [musicTab, podcastsTab])
interfaceController.setRootTemplate(tabBarTemplate, animated: false)
再生中テンプレート
// Configure Now Playing buttons
let playButton = CPNowPlayingPlaybackRateButton()
let skipButton = CPNowPlayingSkipButton(handler: { _ in
// Skip to next track
})
CPNowPlayingTemplate.shared.updateNowPlayingButtons([playButton, skipButton])
CPNowPlayingTemplate.shared.isUpNextButtonEnabled = true
ライブイベントのスポーツモード(iOS 18.4+)
// Create team logos
let homeTeamLogo = CPNowPlayingSportsTeamLogo(teamLogo: UIImage(named: "home_logo")!)
let awayTeamLogo = CPNowPlayingSportsTeamLogo(teamInitials: "VIS")
// Create teams with scores and possession
let homeTeam = CPNowPlayingSportsTeam(
name: "Home Team",
logo: homeTeamLogo,
teamStandings: "1st Place",
eventScore: "21",
possessionIndicator: .solid, // Home team has possession
favorite: true
)
let awayTeam = CPNowPlayingSportsTeam(
name: "Away Team",
logo: awayTeamLogo,
teamStandings: "2nd Place",
eventScore: "17",
possessionIndicator: nil, // No possession
favorite: false
)
// Create game clock (counting down from 15:00)
let gameClock = CPNowPlayingSportsClock(
timeRemaining: 15 * 60, // 15 minutes in seconds
paused: false
)
// Create event status with status text and clock
let eventStatus = CPNowPlayingSportsEventStatus(
eventStatusText: ["3rd Quarter", "4th Down", "Goal Line"],
eventStatusImage: UIImage(named: "field_position"),
eventClock: gameClock
)
// Create sports mode
let sportsMode = CPNowPlayingModeSports(
leftTeam: homeTeam,
rightTeam: awayTeam,
eventStatus: eventStatus,
backgroundArtwork: UIImage(named: "stadium_background")
)
// Set as active now playing mode
CPNowPlayingTemplate.shared.nowPlayingMode = sportsMode
// Update clock during game (e.g., every second)
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
// Update time remaining
let newClock = CPNowPlayingSportsClock(
timeRemaining: getCurrentGameTime(),
paused: isGamePaused()
)
// Update event status with new clock
let updatedStatus = CPNowPlayingSportsEventStatus(
eventStatusText: eventStatus.eventStatusText,
eventStatusImage: eventStatus.eventStatusImage,
eventClock: newClock
)
// Refresh sports mode
let updatedMode = CPNowPlayingModeSports(
leftTeam: homeTeam,
rightTeam: awayTeam,
eventStatus: updatedStatus,
backgroundArtwork: sportsMode.backgroundArtwork
)
CPNowPlayingTemplate.shared.nowPlayingMode = updatedMode
}
マヌーバーを使ったナビゲーション
// Create maneuvers for turn-by-turn guidance
let maneuver = CPManeuver()
maneuver.instructionVariants = ["Turn right onto Main Street"]
maneuver.symbolImage = UIImage(named: "turn-right")
maneuver.initialTravelEstimates = CPTravelEstimates(
distanceRemaining: Measurement(value: 500, unit: UnitLength.meters),
timeRemaining: 60
)
// Add lane guidance
let lane1 = CPLane(angles: [.left, .straight])
lane1.status = .recommended
let lane2 = CPLane(angles: [.straight, .right])
lane2.status = .preferred
maneuver.linkedLaneGuidance = CPLaneGuidance(lanes: [lane1, lane2], instructionVariants: ["Use right lane"])
// Add maneuver to navigation session
navigationSession.upcomingManeuvers = [maneuver]
画像付きの属性付き案内
// Create attributed instruction with embedded image
let instruction = NSMutableAttributedString(string: "Turn right on Apple Park Way")
// Attach an image icon
let image = UIImage(systemName: "arrow.turn.up.right")!
let attachment = NSTextAttachment(image: image)
let imageString = NSAttributedString(attachment: attachment)
instruction.append(NSAttributedString(string: " "))
instruction.append(imageString)
// Create maneuver with attributed instruction
let maneuver = CPManeuver()
maneuver.attributedInstructionVariants = [instruction]
// Also set dashboard-specific attributed instructions
let dashboardInstruction = NSMutableAttributedString(string: "Turn Right ")
let dashboardImage = UIImage(systemName: "arrow.turn.up.right.circle.fill")!
let dashboardAttachment = NSTextAttachment(image: dashboardImage)
dashboardInstruction.append(NSAttributedString(attachment: dashboardAttachment))
dashboardInstruction.append(NSAttributedString(string: " Apple Park Way"))
maneuver.dashboardAttributedInstructionVariants = [dashboardInstruction]
// Set symbol image for main display
maneuver.symbolImage = UIImage(named: "turn-right")
// Add to navigation session
navigationSession.upcomingManeuvers = [maneuver]
ルート選択
// Create route choices for trip preview
let fastRoute = CPRouteChoice(
summaryVariants: ["Via Highway - 25 min"],
additionalInformationVariants: ["Fastest route"],
selectionSummaryVariants: ["Take the highway"]
)
let scenicRoute = CPRouteChoice(
summaryVariants: ["Via Scenic Road - 35 min"],
additionalInformationVariants: ["No tolls"],
selectionSummaryVariants: ["Take the scenic route"]
)
let trip = CPTrip(
origin: originMapItem,
destination: destinationMapItem,
routeChoices: [fastRoute, scenicRoute]
)
mapTemplate.showTripPreviews([trip], textConfiguration: nil)
ダッシュボード統合(iOS 13.4+)
// Configure dashboard scene delegate
func templateApplicationDashboardScene(
_ dashboardScene: CPTemplateApplicationDashboardScene,
didConnect dashboardController: CPDashboardController,
to window: UIWindow
) {
// Create dashboard shortcut buttons
let homeButton = CPDashboardButton(
titleVariants: ["Home"],
subtitleVariants: ["123 Main St"],
image: UIImage(systemName: "house.fill")!
) { button in
// Navigate to home
}
let workButton = CPDashboardButton(
titleVariants: ["Work"],
subtitleVariants: ["456 Office Blvd"],
image: UIImage(systemName: "building.2.fill")!
) { button in
// Navigate to work
}
// Set dashboard shortcut buttons
dashboardController.shortcutButtons = [homeButton, workButton]
}
インストルメントクラスター統合(iOS 15.4+)
// Configure instrument cluster scene delegate
func templateApplicationInstrumentClusterScene(
_ instrumentClusterScene: CPTemplateApplicationInstrumentClusterScene,
didConnect instrumentClusterController: CPInstrumentClusterController
) {
// Configure instrument cluster display
instrumentClusterController.inactiveDescriptionVariants = [
"No active navigation"
]
// Set compass display
instrumentClusterController.compassSetting = .automatic
// Update with route information (iOS 17.4+)
let routeInfo = CPRouteInformation(
maneuvers: upcomingManeuvers,
laneGuidances: lanGuidances,
currentManeuvers: [currentManeuver],
currentLaneGuidance: currentLaneGuidance,
trip: currentTrip,
maneuverTravelEstimates: travelEstimates
)
// Display on instrument cluster
instrumentClusterController.compassSetting = .showAlways
}
グリッドテンプレート
// Create grid buttons with icons
let homeButton = CPGridButton(
titleVariants: ["Home"],
image: UIImage(systemName: "house.fill")!
) { button in
// Navigate to home
}
let workButton = CPGridButton(
titleVariants: ["Work"],
image: UIImage(systemName: "building.2.fill")!
) { button in
// Navigate to work
}
let favoritesButton = CPGridButton(
titleVariants: ["Favorites"],
image: UIImage(systemName: "star.fill")!
) { button in
// Show favorites
}
// Create grid template with up to 8 buttons
let gridTemplate = CPGridTemplate(
title: "Quick Actions",
gridButtons: [homeButton, workButton, favoritesButton]
)
interfaceController.setRootTemplate(gridTemplate, animated: true)
// Update buttons dynamically
let newButtons = [homeButton, workButton, favoritesButton, newButton]
gridTemplate.updateGridButtons(newButtons)
検索テンプレート
// Create search template
let searchTemplate = CPSearchTemplate()
searchTemplate.delegate = self
// Set as root
interfaceController.setRootTemplate(searchTemplate, animated: true)
// Implement CPSearchTemplateDelegate
func searchTemplate(
_ searchTemplate: CPSearchTemplate,
updatedSearchText searchText: String,
completionHandler: @escaping ([CPListItem]) -> Void
) {
// Perform search
let results = performSearch(query: searchText)
// Convert to list items
let listItems = results.map { result in
let item = CPListItem(text: result.name, detailText: result.address)
item.handler = { item, completion in
// Handle selection
self.showResultDetails(result)
completion()
}
return item
}
// Return results
completionHandler(listItems)
}
func searchTemplateSearchButtonPressed(_ searchTemplate: CPSearchTemplate) {
// User tapped search button
print("Search initiated")
}
POI テンプレート
// Create points of interest
let poi1 = CPPointOfInterest(
location: MKMapItem(placemark: MKPlacemark(coordinate: coordinate1)),
title: "Apple Park",
subtitle: "1 Apple Park Way",
summary: "Apple headquarters",
detailTitle: "Visitor Center",
detailSubtitle: "Open 9 AM - 5 PM",
detailSummary: "Tours available",
pinImage: UIImage(systemName: "applelogo")
)
let poi2 = CPPointOfInterest(
location: MKMapItem(placemark: MKPlacemark(coordinate: coordinate2)),
title: "Golden Gate Bridge",
subtitle: "San Francisco",
summary: "Iconic landmark",
detailTitle: "Vista Point",
detailSubtitle: "Always Open",
detailSummary: "Great views",
pinImage: UIImage(systemName: "bridge")
)
// Create POI template
let poiTemplate = CPPointOfInterestTemplate(
title: "Nearby Attractions",
pointsOfInterest: [poi1, poi2],
selectedIndex: 0
)
poiTemplate.pointOfInterestDelegate = self
// Implement delegate
func pointOfInterestTemplate(
_ template: CPPointOfInterestTemplate,
didSelectPointOfInterest poi: CPPointOfInterest
) {
// User selected a POI
print("Selected: \(poi.title)")
// Show navigation options
showNavigationOptions(for: poi)
}
func pointOfInterestTemplate(
_ template: CPPointOfInterestTemplate,
didChangeMapRegion region: MKCoordinateRegion
) {
// Map region changed (user panned/zoomed)
fetchNearbyPOIs(in: region)
}
情報テンプレート
// Create information items
let addressItem = CPInformationItem(
title: "Address",
detail: "1 Apple Park Way, Cupertino, CA"
)
let hoursItem = CPInformationItem(
title: "Hours",
detail: "9:00 AM - 9:00 PM"
)
let phoneItem = CPInformationItem(
title: "Phone",
detail: "(408) 996-1010"
)
// Create action buttons
let callButton = CPTextButton(text: "Call", textStyle: .normal) { button in
// Make phone call
makePhoneCall()
}
let directionsButton = CPTextButton(text: "Directions", textStyle: .confirm) { button in
// Start navigation
startNavigation()
}
// Create information template
let infoTemplate = CPInformationTemplate(
title: "Apple Store",
layout: .leading, // or .twoColumn
items: [addressItem, hoursItem, phoneItem],
actions: [callButton, directionsButton]
)
interfaceController.pushTemplate(infoTemplate, animated: true)
アクションシートテンプレート
// Create actions for action sheet
let confirmAction = CPAlertAction(title: "Confirm", style: .default) { action in
// User confirmed
self.performAction()
}
let cancelAction = CPAlertAction(title: "Cancel", style: .cancel) { action in
// User cancelled
print("Action cancelled")
}
let destructiveAction = CPAlertAction(title: "Delete", style: .destructive) { action in
// User chose destructive action
self.deleteItem()
}
// Create action sheet
let actionSheet = CPActionSheetTemplate(
title: "Choose Action",
message: "What would you like to do?",
actions: [confirmAction, destructiveAction, cancelAction]
)
// Present modally
interfaceController.presentTemplate(actionSheet, animated: true, completion: nil)
アラートテンプレート
// Create alert actions
let okAction = CPAlertAction(title: "OK", style: .default) { action in
// User tapped OK
print("User acknowledged")
}
let retryAction = CPAlertAction(title: "Retry", style: .default) { action in
// User wants to retry
self.retryOperation()
}
let cancelAction = CPAlertAction(title: "Cancel", style: .cancel) { action in
// User cancelled
}
// Create alert with multiple title variants for different screen sizes
let alert = CPAlertTemplate(
titleVariants: [
"Connection Lost",
"Lost Connection",
"No Network"
],
actions: [retryAction, cancelAction]
)
// Present alert
interfaceController.presentTemplate(alert, animated: true, completion: nil)
// Auto-dismiss after 5 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
interfaceController.dismissTemplate(animated: true, completion: nil)
}
音声コントロールテンプレート
// Create voice control states
let listeningState = CPVoiceControlState(
identifier: "listening",
titleVariants: ["Listening...", "Say a command"],
image: UIImage(systemName: "waveform")!,
repeats: true
)
let processingState = CPVoiceControlState(
identifier: "processing",
titleVariants: ["Processing...", "Working on it"],
image: UIImage(systemName: "gear")!,
repeats: false
)
let successState = CPVoiceControlState(
identifier: "success",
titleVariants: ["Done!", "Complete"],
image: UIImage(systemName: "checkmark.circle.fill")!,
repeats: false
)
// Create voice control template
let voiceTemplate = CPVoiceControlTemplate(
voiceControlStates: [listeningState, processingState, successState]
)
// Present voice control
interfaceController.presentTemplate(voiceTemplate, animated: true) {
// Start listening
self.startVoiceRecognition()
}
// Update state based on recognition progress
voiceTemplate.activateVoiceControlState(withIdentifier: "processing")
// Show success when done
voiceTemplate.activateVoiceControlState(withIdentifier: "success")
// Dismiss after completion
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
interfaceController.dismissTemplate(animated: true, completion: nil)
}
連絡先テンプレート(コミュニケーション)
// Create contact
let contact = CPContact(
name: "John Appleseed",
image: UIImage(named: "contact_photo")
)
contact.subtitle = "Friend"
contact.informativeText = "Last contact: Today"
// Create contact action buttons
let callButton = CPContactCallButton(handler: { button in
// Initiate phone call
self.makePhoneCall(to: contact)
})
let messageButton = CPContactMessageButton(phoneOrEmail: "[email protected]")
let directionsButton = CPContactDirectionsButton(handler: { button in
// Navigate to contact's location
self.navigateToContact(contact)
})
contact.actions = [callButton, messageButton, directionsButton]
// Create contact template
let contactTemplate = CPContactTemplate(contact: contact)
// Present contact card
interfaceController.pushTemplate(contactTemplate, animated: true)
メッセージリストテンプレート(コミュニケーション)
// Create leading configurations (profile images)
let profileImage = UIImage(named: "profile")!
let leadingConfig = CPMessageListItemLeadingConfiguration(
leadingItem: .image(profileImage),
leadingImage: profileImage,
unreadIndicator: true
)
// Create trailing configurations (timestamps/badges)
let trailingConfig = CPMessageListItemTrailingConfiguration(
trailingItem: .image(UIImage(systemName: "chevron.right")!),
trailingImage: UIImage(systemName: "paperclip")
)
// Create message list items
let message1 = CPMessageListItem(
conversationIdentifier: "conv_1",
text: "John Appleseed",
leadingConfiguration: leadingConfig,
trailingConfiguration: trailingConfig,
detailText: "Hey, are you free for lunch?",
trailingText: "12:30 PM"
)
message1.handler = { item, completion in
// Open conversation
self.openConversation(id: "conv_1")
completion()
}
let message2 = CPMessageListItem(
fullName: "Jane Smith",
phoneOrEmailAddress: "[email protected]",
leadingConfiguration: CPMessageListItemLeadingConfiguration(
leadingItem: .image(UIImage(named: "jane_photo")!),
leadingImage: UIImage(named: "jane_photo")!,
unreadIndicator: false
),
trailingConfiguration: trailingConfig,
detailText: "Thanks for the update!",
trailingText: "Yesterday"
)
// Create list template with messages
let section = CPListSection(items: [message1, message2])
let listTemplate = CPListTemplate(title: "Messages", sections: [section])
interfaceController.setRootTemplate(listTemplate, animated: true)
リスト画像行アイテム
// Create images for grid display
let image1 = UIImage(named: "album_cover_1")!
let image2 = UIImage(named: "album_cover_2")!
let image3 = UIImage(named: "album_cover_3")!
let image4 = UIImage(named: "album_cover_4")!
// Create list image row item with titles
let imageRowItem = CPListImageRowItem(
text: "Recently Played",
images: [image1, image2, image3, image4],
imageTitles: ["Rock Hits", "Chill Vibes", "Workout Mix", "Road Trip"]
)
// Handle image selection
imageRowItem.listImageRowHandler = { item, index, completion in
// User selected image at index
print("Selected image \(index)")
self.playAlbum(at: index)
completion()
}
// Handle row selection
imageRowItem.handler = { item, completion in
// User tapped the entire row (not a specific image)
self.showRecentlyPlayed()
completion()
}
// Create list with image row
let section = CPListSection(items: [imageRowItem])
let listTemplate = CPListTemplate(title: "Music", sections: [section])
// Update images dynamically
let newImages = [image1, image2, image3, image4, image5]
imageRowItem.update(newImages)
CarPlay の通知
import UserNotifications
// Request CarPlay notification permission
UNUserNotificationCenter.current().requestAuthorization(
options: [.alert, .sound, .carPlay]
) { granted, error in
// Handle result
}
// Create a category that appears in CarPlay
let carPlayCategory = UNNotificationCategory(
identifier: "DELIVERY_UPDATE",
actions: [],
intentIdentifiers: [],
options: [.allowInCarPlay]
)
UNUserNotificationCenter.current().setNotificationCategories([carPlayCategory])
ナビゲーションのマルチタッチ(iOS 26+)
// Multitouch is automatically enabled on iOS 26+
// Handle zoom changes in your map delegate
extension CarPlaySceneDelegate: CPMapTemplateDelegate {
func mapTemplate(_ mapTemplate: CPMapTemplate,
panWith direction: CPMapTemplate.PanDirection) {
// Update map viewport for pan gestures
updateMapViewport(direction: direction)
}
func mapTemplateDidBeginPanGesture(_ mapTemplate: CPMapTemplate) {
// User started panning — pause auto-follow
}
func mapTemplate(_ mapTemplate: CPMapTemplate,
didEndPanGestureWithVelocity velocity: CGPoint) {
// Pan ended — optionally resume auto-follow
}
}
ナビゲーションの音声プロンプト
import AVFoundation
class NavigationVoiceManager {
private let synthesizer = AVSpeechSynthesizer()
func speak(_ instruction: String) {
let session = AVAudioSession.sharedInstance()
try? session.setCategory(
.playback,
mode: .voicePrompt,
options: [.duckOthers, .interruptSpokenAudioAndMixWithOthers]
)
try? session.setActive(true)
let utterance = AVSpeechUtterance(string: instruction)
utterance.voice = AVSpeechSynthesisVoice(language: "en-US")
synthesizer.speak(utterance)
}
func stop() {
synthesizer.stopSpeaking(at: .immediate)
try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
}
}
ナビゲーションのルート再設定(iOS 17.4+)
// When the driver deviates from the planned route
func handleReroute(session: CPNavigationSession, trip: CPTrip) {
// Calculate new route
calculateNewRoute(from: currentLocation, to: trip.destination) { newRouteChoice in
// Resume trip with new route
session.resumeTrip(for: trip, with: newRouteChoice)
// Update maneuvers for the new route
session.upcomingManeuvers = self.buildManeuvers(for: newRouteChoice)
}
}
インストルメントクラスター/HUD 用ナビゲーションメタデータ(iOS 17.4+)
// Enable metadata forwarding to vehicle HUD and instrument cluster
extension CarPlaySceneDelegate: CPMapTemplateDelegate {
func mapTemplateShouldProvideNavigationMetadata(
_ mapTemplate: CPMapTemplate
) -> Bool {
// Return true to forward maneuver data to vehicle displays
return true
}
}
// Provide route information for the instrument cluster
func updateInstrumentCluster(
controller: CPInstrumentClusterController,
maneuvers: [CPManeuver],
laneGuidances: [CPLaneGuidance],
trip: CPTrip,
estimates: [CPManeuver: CPTravelEstimates]
) {
let routeInfo = CPRouteInformation(
maneuvers: maneuvers,
laneGuidances: laneGuidances,
currentManeuvers: [maneuvers.first].compactMap { $0 },
currentLaneGuidance: laneGuidances.first,
trip: trip,
maneuverTravelEstimates: estimates
)
// Route info is automatically forwarded when metadata is enabled
}
CarPlay のウィジェット(iOS 26+)
import WidgetKit
import SwiftUI
struct MyCarPlayWidget: Widget {
let kind: String = "CarPlayWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
CarPlayWidgetView(entry: entry)
}
.configurationDisplayName("Quick Status")
.description("Shows status on CarPlay home screen.")
.supportedFamilies([.accessoryCircular, .accessoryRectangular])
}
}
struct CarPlayWidgetView: View {
var entry: Provider.Entry
var body: some View {
VStack {
Image(systemName: "car.fill")
Text(entry.statusText)
.font(.caption)
}
}
}
CarPlay のライブアクティビティ(iOS 26+)
import ActivityKit
struct DeliveryAttributes: ActivityAttributes {
public struct ContentState: Codable, Hashable {
var status: String
var eta: Date
}
var orderNumber: String
}
// Configure activity to appear in CarPlay
let content = ActivityContent(
state: DeliveryAttributes.ContentState(
status: "On the way",
eta: Date().addingTimeInterval(600)
),
staleDate: nil
)
let activity = try Activity<DeliveryAttributes>.request(
attributes: DeliveryAttributes(orderNumber: "12345"),
content: content,
pushType: .token
)
// In your widget configuration, add CarPlay support:
// .supplementalActivityFamilies([.carPlay])
その他のアプリの起動
// Open another app from CarPlay (e.g., hand off to phone for payment)
func openCompanionApp(scene: CPTemplateApplicationScene) {
guard let url = URL(string: "myapp://complete-order") else { return }
scene.open(url, options: nil) { success in
if success {
print("Opened companion app")
}
}
}
リファレンスドキュメント
API の詳細については、references/ ディレクトリにあるカテゴリ別ドキュメントをご覧ください。