carplay
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
- Scene Configuration
- Templates Overview
- Navigation Apps
- Audio Apps
- Communication Apps
- Point of Interest Apps
- Testing with CarPlay Simulator
- Common Mistakes
- Review Checklist
- References
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
| Entitlement | Category |
|---|---|
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
- Update the App ID in the developer portal under Additional Capabilities.
- Generate a new provisioning profile for the updated App ID.
- In Xcode, disable automatic signing and import the CarPlay provisioning profile.
- Add an
Entitlements.plistwith the entitlement key set totrue. - Set Code Signing Entitlements build setting to the
Entitlements.plistpath.
Key Types
| Type | Role |
|---|---|
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
| Template | Purpose |
|---|---|
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
| Template | Category |
|---|---|
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
- Build and run in Xcode with the iOS simulator.
- 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
| Configuration | Pixels | Scale |
|---|---|---|
| Minimum | 748 x 456 | @2x |
| Portrait | 768 x 1024 | @2x |
| Standard | 800 x 480 | @2x |
| High-resolution | 1920 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 -
UIApplicationSupportsMultipleScenesset totrue -
CPTemplateApplicationSceneSessionRoleApplicationscene in Info.plist - Scene delegate class name matches
UISceneDelegateClassName - Correct delegate method used (with/without
CPWindow) - Root template set in
didConnectbefore returning - Interface controller and window references cleared on disconnect
-
CPTabBarTemplateonly used as root, never pushed -
CPNowPlayingTemplate.sharedused, not a new instance -
maximumItemCount/maximumSectionCountchecked before populating lists -
CPListItem.handlercalls completion in every path - Map-only content in
CPWindowroot 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
- Extended patterns (dashboard, instrument cluster, full nav flow, tab composition): references/carplay-patterns.md
- CarPlay framework
- CPTemplateApplicationSceneDelegate
- CPInterfaceController
- CPMapTemplate
- CPListTemplate
- CPNowPlayingTemplate
- CPPointOfInterestTemplate
- CPNavigationSession
- Requesting CarPlay Entitlements
- Displaying Content in CarPlay
- Using the CarPlay Simulator
- CarPlay HIG