mcpField
C
carplay

carplay

github.com/ios-agent開発1277 回インストール35 週間前に更新
リポジトリ

carplay について

carplay は、iOS 向け CarPlay アプリを開発する人のためのリファレンススキルです。10 種類のアプリカテゴリ、フレームワーク、テンプレートに加え、権限、通知、ナビゲーション、ウィジェット、シミュレーターでのテストを扱います。

主な機能

  1. 0110 種類の CarPlay アプリカテゴリを網羅
  2. 02テンプレート、UI、ナビゲーション API を参照
  3. 03カテゴリごとの権限とテンプレート制限を説明
  4. 04通知、ウィジェット、Live Activities を解説
  5. 05CarPlay Simulator でのテストを案内

活用シーン

  1. 01CarPlay の音声、ポッドキャスト、音楽アプリを開発
  2. 02ナビゲーション、通信、駐車、充電アプリを構築
  3. 03CarPlay に通知、ウィジェット、Live Activities を追加
  4. 04シミュレーターで複数の車載画面サイズをテスト

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/ios-agent/iosagent.dev --skill carplay

インストール後、Claude Code で/carplayを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
api_reference.md
audio.md
communication.md
dashboard.md
developer_guide.md
getting_started.md
index.md
navigation.md
poi.md
templates.md
ui_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 リファレンス
  • CPInterfaceController
  • CPWindow
  • CarPlay
コミュニケーション
  • CPContact
  • CPMessageListItem
はじめに
  • CPSessionConfiguration
  • CPTemplateApplicationScene
  • CPTemplateApplicationSceneDelegate
  • CPTemplateApplicationScene.open(_:options:completionHandler:)
ナビゲーション
  • CPManeuver
  • CPNavigationSession
  • CPRouteChoice
  • CPRouteInformation
  • CPTrip
  • CPManeuverType
  • CPJunctionType
  • CPTrafficSide
  • CPLaneStatus
POI
  • CPPointOfInterest
テンプレート
  • CPActionSheetTemplate
  • CPAlertTemplate
  • CPContactTemplate
  • CPGridTemplate
  • CPInformationTemplate
  • CPListTemplate
  • CPMapTemplate
  • CPNowPlayingTemplate
  • CPPointOfInterestTemplate
  • CPSearchTemplate
  • CPTabBarTemplate
  • CPTemplate
  • CPVoiceControlTemplate
UI 要素
  • CPAlertAction
  • CPBarButton
  • CPButton
  • CPGridButton
  • CPInformationItem
  • CPListImageRowItem
  • CPListItem
  • CPListSection
  • CPTextButton
ダッシュボード(iOS 13.4 以降)
  • CPDashboardButton
  • CPDashboardController
  • CPTemplateApplicationDashboardScene
  • CPTemplateApplicationDashboardSceneDelegate
インストルメントクラスター(iOS 15.4 以降)
  • CPInstrumentClusterController
  • CPTemplateApplicationInstrumentClusterScene
  • CPTemplateApplicationInstrumentClusterSceneDelegate
通知
  • 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 として使用します。

エンタイトルメントの申請方法:

  1. https://developer.apple.com/contact/carplay/ にアクセスします
  2. アプリの機能とカテゴリを説明します
  3. Apple の審査と承認を待ちます(通常 1~2 週間)
  4. 承認されたエンタイトルメントを 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:) を使用して他のアプリを開けます。これは、電話ベースのワークフロー(購入手続きの完了など)に引き継ぐ場合に便利です。切り替えの前に、システムがユーザーに確認します。

使用ガイドライン

  1. アプリのカテゴリに適したテンプレートを使用します
  2. コンテンツをシンプルに保ちます - ドライバーの安全のため、項目とテキストを制限します
  3. 見やすい大きな画像を用意します
  4. 接続と切断を適切に処理します
  5. 車両でテストする前に CarPlay Simulator でテストします
  6. 注意散漫を防ぐためのガイドラインに従います - 運転中は動画、アニメーション、広告を表示しません
CarPlay シミュレータでのテスト

CarPlay をテストする方法は 2 つあります。

オプション 1:Xcode シミュレータウインドウ

  1. CarPlay アプリのプロジェクトを Xcode で開きます
  2. 実行先として iOS シミュレータを選択します
  3. シミュレータのメニューで、I/O > External Displays > CarPlay を選択します
  4. iOS シミュレータと並んで CarPlay ディスプレイのウインドウが表示されます

オプション 2:スタンドアロン CarPlay シミュレータ(Mac アプリ)

  1. developer.apple.com から Additional Tools for Xcode をダウンロードします
  2. CarPlay Simulator アプリをインストールします
  3. 物理的な iPhone を Mac に接続します
  4. 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 - POI
  • references/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/ ディレクトリにあるカテゴリ別ドキュメントをご覧ください。

コメント