Swift-ios-skills carplay

Build CarPlay-enabled apps using the CarPlay framework. Use when creating navigation, audio, communication, EV charging, parking, or food ordering apps for the car display, working with CPTemplateApplicationScene, CPListTemplate, CPMapTemplate, CPNowPlayingTemplate, configuring CarPlay entitlements, or integrating with CarPlay Simulator for testing.

install
source · Clone the upstream repo
git clone https://github.com/dpearson2699/swift-ios-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/dpearson2699/swift-ios-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/carplay" ~/.claude/skills/dpearson2699-swift-ios-skills-carplay && rm -rf "$T"
manifest: skills/carplay/SKILL.md
source content

CarPlay

Build apps that display on the vehicle's CarPlay screen using the CarPlay framework's template-based UI system. Covers scene lifecycle, template types, navigation guidance, audio playback, communication, point-of-interest categories, entitlement setup, and simulator testing. Targets Swift 6.3 / iOS 26+.

See references/carplay-patterns.md for extended patterns including full navigation sessions, dashboard scenes, and advanced template composition.

Contents

Entitlements and Setup

CarPlay requires a category-specific entitlement granted by Apple. Request it at developer.apple.com/contact/carplay and agree to the CarPlay Entitlement Addendum.

Entitlement Keys by Category

EntitlementCategory
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

Project Configuration

  1. Update the App ID in the developer portal under Additional Capabilities.
  2. Generate a new provisioning profile for the updated App ID.
  3. In Xcode, disable automatic signing and import the CarPlay provisioning profile.
  4. Add an
    Entitlements.plist
    with the entitlement key set to
    true
    .
  5. Set Code Signing Entitlements build setting to the
    Entitlements.plist
    path.

Key Types

TypeRole
CPTemplateApplicationScene
UIScene subclass for the CarPlay display
CPTemplateApplicationSceneDelegate
Scene connect/disconnect lifecycle
CPInterfaceController
Manages the template navigation hierarchy
CPTemplate
Abstract base for all CarPlay templates
CPSessionConfiguration
Vehicle display limits and content style

Scene Configuration

Declare the CarPlay scene in

Info.plist
and implement
CPTemplateApplicationSceneDelegate
to respond when CarPlay connects.

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 (Non-Navigation)

Non-navigation apps receive an interface controller only. No 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 (Navigation)

Navigation apps receive both an interface controller and a

CPWindow
. Set the window's root view controller to draw map content.

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 provides a fixed set of template types. The app supplies content; the system renders it on the vehicle display.

General Purpose Templates

TemplatePurpose
CPTabBarTemplate
Container with tabbed child templates
CPListTemplate
Scrollable sectioned list
CPGridTemplate
Grid of tappable icon buttons (max 8)
CPInformationTemplate
Key-value info with up to 3 actions
CPAlertTemplate
Modal alert with up to 2 actions
CPActionSheetTemplate
Modal action sheet

Category-Specific Templates

TemplateCategory
CPMapTemplate
Navigation -- map overlay with nav bar
CPSearchTemplate
Navigation -- destination search
CPNowPlayingTemplate
Audio -- shared Now Playing screen
CPPointOfInterestTemplate
EV Charging / Parking / Food -- POI map
CPContactTemplate
Communication -- contact card

Navigation Hierarchy

Use

pushTemplate(_:animated:completion:)
to add templates to the stack. Use
presentTemplate(_:animated:completion:)
for modal display. Use
popTemplate(animated:completion:)
to go back.
CPTabBarTemplate
must be set as root -- it cannot be pushed or presented.

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

Navigation apps use

com.apple.developer.carplay-maps
. They are the only category that receives a
CPWindow
for drawing map content. The root template must be a
CPMapTemplate
.

Trip Preview and Route Selection

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)

Starting a Navigation Session

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

Map Buttons

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

Audio apps use

com.apple.developer.carplay-audio
. They display browsable content in lists and use
CPNowPlayingTemplate
for playback controls.

Now Playing Template

CPNowPlayingTemplate
is a shared singleton. It reads metadata from
MPNowPlayingInfoCenter
. Do not instantiate a new one.

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

Audio apps supporting

INPlayMediaIntent
can show an assistant cell. Communication apps use
INStartCallIntent
with
.startCall
.

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

Communication Apps

Communication apps use

com.apple.developer.carplay-communication
. They display message lists and contacts, and support
INStartCallIntent
for Siri-initiated calls.

let message = CPMessageListItem(
    conversationIdentifier: "conv-123",
    text: "Meeting at 3pm",
    leadingConfiguration: CPMessageListItem.LeadingConfiguration(
        leadingItem: .init(text: "Jane", textStyle: .abbreviated),
        unread: true),
    trailingConfiguration: CPMessageListItem.TrailingConfiguration(
        trailingItem: .init(text: "2:45 PM")),
    trailingText: nil, trailingImage: nil)

let messageList = CPListTemplate(title: "Messages",
                                 sections: [CPListSection(items: [message])])

Point of Interest Apps

EV charging, parking, and food ordering apps use

CPPointOfInterestTemplate
and
CPInformationTemplate
to display locations and details.

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

Testing with CarPlay Simulator

  1. Build and run in Xcode with the iOS simulator.
  2. Choose I/O > External Displays > CarPlay.

Default window: 800x480 at @2x. Enable extra options for navigation apps:

defaults write com.apple.iphonesimulator CarPlayExtraOptions -bool YES

Recommended Test Configurations

ConfigurationPixelsScale
Minimum748 x 456@2x
Portrait768 x 1024@2x
Standard800 x 480@2x
High-resolution1920 x 720@3x

Simulator cannot test locked-iPhone behavior, Siri, audio coexistence with car radio, or physical input hardware (knobs, touch pads). Test on a real CarPlay-capable vehicle or aftermarket head unit when possible.

Common Mistakes

DON'T: Use the wrong scene delegate method

Navigation apps must implement

templateApplicationScene(_:didConnect:to:)
(with
CPWindow
). Non-navigation apps use
templateApplicationScene(_:didConnect:)
(no window). Using the wrong variant produces no CarPlay UI.

DON'T: Draw custom UI in the navigation window

CPWindow
is exclusively for map content. All overlays, alerts, and controls must use CarPlay templates.

DON'T: Push or present CPTabBarTemplate

CPTabBarTemplate
can only be set as root. Pushing or presenting it fails. Use
setRootTemplate(_:animated:completion:)
.

DON'T: Instantiate CPNowPlayingTemplate

Use

CPNowPlayingTemplate.shared
. Creating a new instance causes issues.

DON'T: Ignore vehicle display limits

Check

CPSessionConfiguration.limitedUserInterfaces
and respect
maximumItemCount
/
maximumSectionCount
on list templates.

DON'T: Forget to call the completion handler

CPListItem.handler
must call its completion handler in every code path. Failure leaves the list in a loading state.

Review Checklist

  • Correct CarPlay entitlement key in
    Entitlements.plist
  • UIApplicationSupportsMultipleScenes
    set to
    true
  • CPTemplateApplicationSceneSessionRoleApplication
    scene in Info.plist
  • Scene delegate class name matches
    UISceneDelegateClassName
  • Correct delegate method used (with/without
    CPWindow
    )
  • Root template set in
    didConnect
    before returning
  • Interface controller and window references cleared on disconnect
  • CPTabBarTemplate
    only used as root, never pushed
  • CPNowPlayingTemplate.shared
    used, not a new instance
  • maximumItemCount
    /
    maximumSectionCount
    checked before populating lists
  • CPListItem.handler
    calls completion in every path
  • Map-only content in
    CPWindow
    root view controller (navigation apps)
  • App functions while iPhone is locked
  • Tested at minimum, standard, and high-resolution simulator sizes
  • Audio session deactivated when not actively playing

References