carplay

carplay

熱門

使用 CarPlay 框架開發支援 CarPlay 的應用程式。適用於為車載螢幕打造導航、音訊、通訊、電動車充電、停車或快速點餐等 App;處理 CPTemplateApplicationScene、CPInterfaceController 模板階層、CPListTemplate、CPMapTemplate、CPNowPlayingTemplate;設定 CarPlay entitlements 權限;以及整合 CarPlay Simulator 進行測試等情境。

967星標
49分支
更新於 2026/7/31
SKILL.md
唯讀
名稱
carplay
描述

使用 CarPlay 框架開發支援 CarPlay 的應用程式。適用於為車載螢幕打造導航、音訊、通訊、電動車充電、停車或快速點餐等 App;處理 CPTemplateApplicationScene、CPInterfaceController 模板階層、CPListTemplate、CPMapTemplate、CPNowPlayingTemplate;設定 CarPlay entitlements 權限;以及整合 CarPlay Simulator 進行測試等情境。

CarPlay

為車載螢幕建構具備類別授權(Category-entitled)、基於模板的 CarPlay 應用程式。
適用範圍:Swift 6.3、iOS 26+。

完整導航會話(Navigation sessions)、儀表板場景(Dashboard scenes)與進階模板組合等延伸模式,請參閱 references/carplay-patterns.md

範圍界限:完整的 CarPlay 框架 App 會使用類別 Entitlements 權限、CPTemplateApplicationSceneCPTemplateApplicationSceneDelegateCPInterfaceController,以及系統的 CPTemplate 導航。可在 CarPlay 顯示的 WidgetKit 小工具與 ActivityKit 即時動態(Live Activities)屬於獨立的系統體驗;其實作請劃分至對應領域,本 Skill 僅保留 CarPlay 專屬的驗證邏輯。

目錄

Entitlements and Setup

前往 Apple 的 CarPlay 權限申請表單 申請指定類別的 Entitlement,同意附加條款後,即可在下方配置獲准的類別金鑰。

各類別的 Entitlement 金鑰

Entitlement 金鑰 應用類別
com.apple.developer.carplay-audio 音訊 (Audio)
com.apple.developer.carplay-communication 通訊 (Communication)
com.apple.developer.carplay-maps 導航 (Navigation)
com.apple.developer.carplay-charging 電動車充電 (EV Charging)
com.apple.developer.carplay-parking 停車 (Parking)
com.apple.developer.carplay-quick-ordering 快速點餐 (Quick Food Ordering)

專案設定步驟

  1. 前往 Developer Portal,在 App ID 的 Additional Capabilities 頁籤中勾選對應功能。
  2. 針對更新後的 App ID 重新產生 Provisioning Profile。
  3. 在 Xcode 中停用自動簽署(Automatic Signing),並匯入剛下載的 CarPlay Provisioning Profile。
  4. 新增 Entitlements.plist 檔案,並將對應的 Entitlement Key 設定為 true
  5. 將 Build Settings 中的 Code Signing Entitlements 指向 Entitlements.plist 的檔案路徑。

核心類型 (Key Types)

類型 角色與用途
CPTemplateApplicationScene 專為 CarPlay 螢幕設計的 UIScene 子類別
CPTemplateApplicationSceneDelegate 處理 Scene 連線與斷開的生命週期
CPInterfaceController 由 CarPlay 提供的控制器,負責設定根模板(Root Template)以及 Push、Present 或 Pop 模板
CPTemplate 所有 CarPlay 模板的抽象基底類別
CPSessionConfiguration 提供車載螢幕限制與內容樣式的設定資訊

Scene Configuration

Info.plist 中宣告 CarPlay Scene,並實作 CPTemplateApplicationSceneDelegate 以回應 CarPlay 的連線事件。

Info.plist Scene Manifest

<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>

Scene Delegate(非導航類)

非導航類 App 僅會收到介面控制器(Interface Controller),不會收到 Window。

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
    }
}

Scene Delegate(導航類)

導航類 App 會同時收到 Interface Controller 與 CPWindow
請將 Window 的 rootViewController 設定為繪製地圖內容的 Controller。

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)
}

Templates Overview

CarPlay 提供了一套固定的模板類型。App 負責提供數據內容,系統則負責在車載螢幕上渲染畫面。

通用模板 (General Purpose Templates)

模板 用途
CPTabBarTemplate 包含分頁(Tab)子模板的容器
CPListTemplate 可捲動的分組清單
CPGridTemplate 可點擊圖示按鈕構成的網格(最多 8 個按鈕)
CPInformationTemplate 鍵值對資訊展示,最多支援 3 個操作按鈕
CPAlertTemplate 模態警示框(Modal Alert),最多支援 2 個操作按鈕
CPActionSheetTemplate 模態動作選單(Modal Action Sheet)

類別專屬模板 (Category-Specific Templates)

模板 適用類別
CPMapTemplate 導航 -- 帶有導航列的地圖覆蓋層
CPSearchTemplate 導航 -- 目的地搜尋
CPNowPlayingTemplate 音訊 -- 共用的「正在播放」畫面
CPPointOfInterestTemplate 電動車充電 / 停車 / 餐飲 -- 興趣點(POI)地圖
CPContactTemplate 通訊 -- 聯絡人卡片

導航階層 (Navigation Hierarchy)

使用 pushTemplate(_:animated:completion:) 將模板推入堆疊。
使用 presentTemplate(_:animated:completion:) 以模態方式顯示(Modal)。
使用 popTemplate(animated:completion:) 返回上一頁。
CPTabBarTemplate 必須設為根模板(Root)-- 無法透過 push 或 present 方式顯示。

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])

Navigation Apps

導航類 App 使用 com.apple.developer.carplay-maps。這是唯一能接收 CPWindow 用於繪製地圖內容的 App 類別。根模板必須是 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()
    }
}

Audio Apps

音訊類 App 使用 com.apple.developer.carplay-audio。它們在清單中顯示可瀏覽的內容,並使用 CPNowPlayingTemplate 作為播放控制介面。取得音訊授權的 App 無法使用 CPInformationTemplate

正在播放模板 (Now Playing Template)

CPNowPlayingTemplate 是一個共用的單例物件(Singleton),會自動讀取來自 MPNowPlayingInfoCenter 的中繼資料(Metadata)。請勿自行建立新實例。

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 語音助理 Cell

支援 INPlayMediaIntent 的音訊類 App 可以顯示語音助理 Cell。
通訊類 App 則配合 INStartCallIntent 使用 .startCall

let config = CPAssistantCellConfiguration(
    position: .top, visibility: .always, assistantAction: .playMedia)
let listTemplate = CPListTemplate(
    title: "Playlists",
    sections: [CPListSection(items: items)],
    assistantCellConfiguration: config)

Communication Apps

通訊類 App 使用 com.apple.developer.carplay-communication
它們用於顯示訊息清單與聯絡人,並支援透過 Siri 發起通話的 INStartCallIntent
CPMessageListItem 沒有提供 App 自訂的選取處理常式(Selection handler)。當使用者點選時,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,
    trailingConfig