CarPlay
Build category-entitled, template-based CarPlay apps for the vehicle display. Scope: Swift 6.3, iOS 26+.
See references/carplay-patterns.md for extended patterns including full navigation sessions, dashboard scenes, and advanced template composition.
Scope boundary: full CarPlay framework apps use category entitlements,
CPTemplateApplicationScene, CPTemplateApplicationSceneDelegate,
CPInterfaceController, and system CPTemplate navigation. CarPlay-visible
WidgetKit widgets and ActivityKit Live Activities are separate system
experiences; route their implementation to those domains while keeping
CarPlay-specific validation here.
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
Request the category-specific entitlement from Apple's CarPlay entitlement form, accept the addendum, then provision the approved category key below.
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 | CarPlay-provided controller for setting the root template and pushing, presenting, or popping templates |
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
plist<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.
swiftimport 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.
swiftfunc 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
swiftlet 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
swiftlet 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
swiftlet 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
swiftextension 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
swiftlet zoomIn = CPMapButton { _ in self.mapViewController.zoomIn() } zoomIn.image = UIImage(systemName: "plus.magnifyingglass") mapTemplate.mapButtons = [zoomIn, zoomOut]
CPSearchTemplate
swiftextension 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.
CPInformationTemplate is not available to audio-entitled apps.
Now Playing Template
CPNowPlayingTemplate is a shared singleton. It reads metadata from
MPNowPlayingInfoCenter. Do not instantiate a new one.
swiftlet 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.
swiftlet 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.
CPMessageListItem has no app-provided selection handler. When selected,
CarPlay invokes Siri compose, read, or reply behavior based on the item's
phone/email, unread state, or existing conversation configuration.
swiftlet 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, trailingConfiguration: trailing, detailText: "Meeting at 3pm", trailingText: "2:45 PM") 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 displays a maximum of 12 points of interest.
CPPointOfInterestTemplate
swiftlet 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
swiftlet 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:
bashdefaults 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. Design primary CarPlay flows so they do not require iPhone input while CarPlay is active.
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: Add handlers to CPMessageListItem
CPMessageListItem is Siri-managed, unlike CPListItem. Do not set
message.handler; use the item configuration and userInfo for context.
DON'T: Treat widgets as CarPlay template apps
CarPlay-visible widgets and Live Activities belong to WidgetKit and ActivityKit. Use this skill for category-entitled CarPlay template app scenes and for validating those surfaces in the car context.
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 - Communication rows use
CPMessageListItemwithout custom handlers - WidgetKit/ActivityKit surfaces routed outside CarPlay template app code
-
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


