Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.
71
89%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
Overflow reference for the carplay skill. Contains advanced patterns that
exceed the main skill file's scope.
Navigation apps can present maps, upcoming maneuvers, and shortcut buttons
in the CarPlay Dashboard. Add CPSupportsDashboardNavigationScene and a
dashboard scene configuration to Info.plist alongside the main scene.
<key>CPSupportsDashboardNavigationScene</key>
<true/>
<key>CPTemplateApplicationDashboardSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>CPTemplateApplicationDashboardScene</string>
<key>UISceneConfigurationName</key>
<string>CarPlayDashboardConfiguration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).DashboardSceneDelegate</string>
</dict>
</array>import CarPlay
final class DashboardSceneDelegate: UIResponder,
CPTemplateApplicationDashboardSceneDelegate {
var dashboardWindow: UIWindow?
var dashboardController: CPDashboardController?
func templateApplicationDashboardScene(
_ scene: CPTemplateApplicationDashboardScene,
didConnect dashboardController: CPDashboardController,
to window: UIWindow
) {
self.dashboardController = dashboardController
self.dashboardWindow = window
window.rootViewController = DashboardMapViewController()
dashboardController.shortcutButtons = [
CPDashboardButton(
titleVariants: ["Home"], subtitleVariants: ["25 min"],
image: UIImage(systemName: "house.fill")!) { _ in },
CPDashboardButton(
titleVariants: ["Work"], subtitleVariants: ["35 min"],
image: UIImage(systemName: "building.2.fill")!) { _ in }
]
}
func templateApplicationDashboardScene(
_ scene: CPTemplateApplicationDashboardScene,
didDisconnect dashboardController: CPDashboardController,
from window: UIWindow
) {
self.dashboardController = nil
self.dashboardWindow = nil
}
}Navigation apps can display turn-by-turn guidance in the vehicle's
instrument cluster using CPTemplateApplicationInstrumentClusterScene.
final class InstrumentClusterDelegate: UIResponder,
CPTemplateApplicationInstrumentClusterSceneDelegate {
var instrumentClusterController: CPInstrumentClusterController?
func templateApplicationInstrumentClusterScene(
_ scene: CPTemplateApplicationInstrumentClusterScene,
didConnect instrumentClusterController: CPInstrumentClusterController
) {
self.instrumentClusterController = instrumentClusterController
instrumentClusterController.delegate = self
}
}
extension InstrumentClusterDelegate: CPInstrumentClusterControllerDelegate {
func instrumentClusterControllerDidConnect(
_ controller: CPInstrumentClusterController) { }
func instrumentClusterController(
_ controller: CPInstrumentClusterController,
didChangeCompassSetting setting: CPInstrumentClusterSetting) { }
func instrumentClusterController(
_ controller: CPInstrumentClusterController,
didChangeSpeedLimitSetting setting: CPInstrumentClusterSetting) { }
}A complete navigation session flow from trip creation through completion.
import CarPlay
import MapKit
final class NavigationManager: @unchecked Sendable {
var navigationSession: CPNavigationSession?
var mapTemplate: CPMapTemplate?
func createTrip(to destination: CLLocationCoordinate2D,
name: String) -> CPTrip {
let origin = MKMapItem.forCurrentLocation()
let destItem = MKMapItem(placemark: MKPlacemark(coordinate: destination))
destItem.name = name
return CPTrip(origin: origin, destination: destItem, routeChoices: [
CPRouteChoice(summaryVariants: ["Fastest"],
additionalInformationVariants: ["Via Highway"],
selectionSummaryVariants: ["25 min"]),
CPRouteChoice(summaryVariants: ["Shortest"],
additionalInformationVariants: ["Local Roads"],
selectionSummaryVariants: ["30 min"])
])
}
func startNavigation(for trip: CPTrip, routeChoice: CPRouteChoice) {
guard let mapTemplate else { return }
let session = mapTemplate.startNavigationSession(for: trip)
navigationSession = session
session.pauseTrip(for: .loading, description: "Calculating route...")
Task {
let maneuvers = await calculateManeuvers()
session.upcomingManeuvers = maneuvers
if let first = maneuvers.first {
session.updateEstimates(
CPTravelEstimates(
distanceRemaining: Measurement(value: 12.5, unit: .miles),
timeRemaining: 1500),
for: first)
}
}
}
func updateManeuver(instruction: String, symbolName: String,
distanceMiles: Double, timeSeconds: TimeInterval) {
guard let session = navigationSession else { return }
let maneuver = CPManeuver()
maneuver.instructionVariants = [instruction]
maneuver.symbolImage = UIImage(systemName: symbolName)
session.upcomingManeuvers = [maneuver]
session.updateEstimates(
CPTravelEstimates(
distanceRemaining: Measurement(value: distanceMiles, unit: .miles),
timeRemaining: timeSeconds),
for: maneuver)
}
func updateCurrentRoad(_ roadName: String) {
navigationSession?.currentRoadNameVariants = [roadName]
}
func reroute() {
navigationSession?.pauseTrip(for: .rerouting, description: "Rerouting...")
Task {
let maneuvers = await calculateManeuvers()
navigationSession?.upcomingManeuvers = maneuvers
}
}
func finishNavigation() {
navigationSession?.finishTrip()
navigationSession = nil
mapTemplate?.hideTripPreviews()
}
func cancelNavigation() {
navigationSession?.cancelTrip()
navigationSession = nil
}
private func calculateManeuvers() async -> [CPManeuver] {
let turn = CPManeuver()
turn.instructionVariants = ["Turn right onto Main St", "Right on Main"]
turn.symbolImage = UIImage(systemName: "arrow.turn.up.right")
let arrive = CPManeuver()
arrive.instructionVariants = ["Arrive at destination", "Arrive"]
arrive.symbolImage = UIImage(systemName: "mappin.circle.fill")
return [turn, arrive]
}
}Provide lane guidance during active navigation to display lane arrows.
func provideLaneGuidance(for session: CPNavigationSession) {
let guidance = CPLaneGuidance()
guidance.instructionVariants = ["Use left two lanes"]
let left = CPLane(); left.status = .preferred
let middle = CPLane(); middle.status = .good
let right = CPLane(); right.status = .notGood
guidance.lanes = [left, middle, right]
session.add([guidance])
session.currentLaneGuidance = guidance
}Display time-sensitive alerts on the map template for incidents or closures.
func showNavigationAlert(on mapTemplate: CPMapTemplate) {
let alert = CPNavigationAlert(
titleVariants: ["Road Closure Ahead", "Road Closed"],
subtitleVariants: ["Main St closed at 5th Ave"],
image: UIImage(systemName: "exclamationmark.triangle.fill"),
primaryAction: CPAlertAction(title: "Reroute", style: .default) { _ in
mapTemplate.dismissNavigationAlert(animated: true, completion: nil)
},
secondaryAction: CPAlertAction(title: "Dismiss", style: .cancel) { _ in
mapTemplate.dismissNavigationAlert(animated: true, completion: nil)
},
duration: 10.0)
mapTemplate.present(navigationAlert: alert, animated: true)
}Handle user-initiated map panning via touchscreen or rotary controller.
extension CarPlaySceneDelegate: CPMapTemplateDelegate {
func mapTemplateDidShowPanningInterface(_ mapTemplate: CPMapTemplate) {
// User entered panning mode
}
func mapTemplateDidDismissPanningInterface(_ mapTemplate: CPMapTemplate) {
// User exited panning mode
}
func mapTemplate(_ mapTemplate: CPMapTemplate,
panWith direction: CPMapTemplate.PanDirection) {
switch direction {
case .up: mapViewController.panUp()
case .down: mapViewController.panDown()
case .left: mapViewController.panLeft()
case .right: mapViewController.panRight()
@unknown default: break
}
}
}let listTemplate = CPListTemplate(
title: "Browse",
sections: [CPListSection(items: items)],
assistantCellConfiguration: nil,
headerGridButtons: [
CPGridButton(titleVariants: ["Favorites"],
image: UIImage(systemName: "heart.fill")!) { _ in },
CPGridButton(titleVariants: ["Recents"],
image: UIImage(systemName: "clock.fill")!) { _ in }
])Use transactional APIs to update list content without rebuilding templates.
template.updateSections([
CPListSection(items: newItems, header: "Results", sectionIndexTitle: nil)
])listTemplate.emptyViewTitleVariants = ["No Results Found"]
listTemplate.emptyViewSubtitleVariants = ["Try a different search"]
listTemplate.showsSpinnerWhileEmpty = true
// After loading:
listTemplate.showsSpinnerWhileEmpty = false
listTemplate.updateSections([CPListSection(items: loadedItems)])var templates = tabBar.templates
templates[0].showsTabBadge = true
tabBar.updateTemplates(templates)import CarPlay
import MediaPlayer
final class AudioCarPlaySceneDelegate: UIResponder,
CPTemplateApplicationSceneDelegate, CPNowPlayingTemplateObserver {
var interfaceController: CPInterfaceController?
func templateApplicationScene(
_ scene: CPTemplateApplicationScene,
didConnect interfaceController: CPInterfaceController
) {
self.interfaceController = interfaceController
configureNowPlaying()
interfaceController.setRootTemplate(buildRootTemplate(),
animated: true, completion: nil)
}
func templateApplicationScene(
_ scene: CPTemplateApplicationScene,
didDisconnectInterfaceController ic: CPInterfaceController
) { self.interfaceController = nil }
private func buildRootTemplate() -> CPTabBarTemplate {
let items = MusicLibrary.shared.playlists.map { playlist in
let item = CPListItem(text: playlist.name,
detailText: "\(playlist.count) songs",
image: playlist.artwork)
item.handler = { [weak self] _, completion in
MusicLibrary.shared.play(playlist)
self?.interfaceController?.pushTemplate(
CPNowPlayingTemplate.shared, animated: true,
completion: nil)
completion()
}
return item
}
let tab = CPListTemplate(title: "Library",
sections: [CPListSection(items: items)])
tab.tabImage = UIImage(systemName: "music.note.list")
return CPTabBarTemplate(templates: [tab])
}
private func configureNowPlaying() {
let np = CPNowPlayingTemplate.shared
np.isAlbumArtistButtonEnabled = true
np.isUpNextButtonEnabled = true
np.updateNowPlayingButtons([
CPNowPlayingShuffleButton { _ in MusicLibrary.shared.toggleShuffle() },
CPNowPlayingRepeatButton { _ in MusicLibrary.shared.toggleRepeat() }
])
np.add(self)
}
func nowPlayingTemplateUpNextButtonTapped(_ t: CPNowPlayingTemplate) {
let items = MusicLibrary.shared.upNext.map {
CPListItem(text: $0.title, detailText: $0.artist)
}
interfaceController?.pushTemplate(
CPListTemplate(title: "Up Next",
sections: [CPListSection(items: items)]),
animated: true, completion: nil)
}
func nowPlayingTemplateAlbumArtistButtonTapped(_ t: CPNowPlayingTemplate) { }
}let contact = CPContact(
name: PersonNameComponents(givenName: "Jane", familyName: "Doe"),
image: UIImage(systemName: "person.circle.fill")!)
contact.actions = [
CPButton(image: UIImage(systemName: "phone.fill")!) { _ in },
CPButton(image: UIImage(systemName: "message.fill")!) { _ in }
]
let contactTemplate = CPContactTemplate(contact: contact)
interfaceController?.pushTemplate(contactTemplate, animated: true,
completion: nil)let config = CPAssistantCellConfiguration(
position: .top, visibility: .always, assistantAction: .startCall)
let messageList = CPListTemplate(
title: "Messages",
sections: [CPListSection(items: messageItems)],
assistantCellConfiguration: config)Food ordering apps must not exceed two levels of list hierarchy.
// Step 1: POI template showing nearby restaurants
func showRestaurants(interfaceController: CPInterfaceController) {
let pois = fetchNearbyRestaurants().map { r -> CPPointOfInterest in
let poi = CPPointOfInterest(
location: r.mapItem, title: r.name, subtitle: r.cuisine,
summary: r.rating, detailTitle: r.name,
detailSubtitle: r.priceRange,
detailSummary: "Open until \(r.closingTime)",
pinImage: UIImage(systemName: "fork.knife"))
poi.primaryButton = CPTextButton(title: "Order",
textStyle: .confirm) { _ in
self.showMenu(for: r, interfaceController: interfaceController)
}
return poi
}
let template = CPPointOfInterestTemplate(
title: "Restaurants", pointsOfInterest: pois, selectedIndex: 0)
template.pointOfInterestDelegate = self
interfaceController.pushTemplate(template, animated: true, completion: nil)
}
// Step 2: Menu list
func showMenu(for restaurant: Restaurant,
interfaceController: CPInterfaceController) {
let items = restaurant.menuItems.map { item in
let li = CPListItem(text: item.name, detailText: item.formattedPrice)
li.handler = { _, completion in
self.addToOrder(item)
self.showOrderSummary(interfaceController: interfaceController)
completion()
}
return li
}
interfaceController.pushTemplate(
CPListTemplate(title: restaurant.name,
sections: [CPListSection(items: items)]),
animated: true, completion: nil)
}
// Step 3: Order confirmation via CPInformationTemplate
func showOrderSummary(interfaceController: CPInterfaceController) {
let info = CPInformationTemplate(
title: "Order Summary", layout: .leading,
items: currentOrder.items.map {
CPInformationItem(title: $0.name, detail: $0.formattedPrice)
},
actions: [
CPTextButton(title: "Place Order", textStyle: .confirm) { _ in
self.placeOrder() },
CPTextButton(title: "Cancel", textStyle: .cancel) { _ in
interfaceController.popToRootTemplate(animated: true,
completion: nil) }
])
interfaceController.pushTemplate(info, animated: true, completion: nil)
}Query CPSessionConfiguration to adapt content to vehicle constraints.
func configureForVehicle(sessionConfig: CPSessionConfiguration) {
let limits = sessionConfig.limitedUserInterfaces
if limits.contains(.keyboard) {
// Vehicle does not support keyboard -- hide search
}
// Always respect template maximums
let maxItems = CPListTemplate.maximumItemCount
let maxSections = CPListTemplate.maximumSectionCount
}An app can have both a phone scene and a CarPlay scene active at the same
time. Share state via an @Observable singleton or similar pattern.
@Observable
final class AppState {
static let shared = AppState()
var currentPlaylist: Playlist?
var isPlaying = false
}
// Both PhoneSceneDelegate and CarPlaySceneDelegate read/write AppState.shared.
// All CarPlay template classes are @MainActor -- ensure template mutations
// happen on the main actor when updating from background threads.skills
accessorysetupkit
references
activitykit
references
adattributionkit
references
alarmkit
references
app-clips
app-intents
references
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
references
audioaccessorykit
references
authentication
references
avkit
references
background-processing
references
browserenginekit
references
callkit
references
carplay
references
cloudkit
references
contacts-framework
references
core-bluetooth
references
core-data
core-motion
references
core-nfc
references
coreml
references
cryptokit
references
cryptotokenkit
references
debugging-instruments
device-integrity
references
dockkit
references
energykit
references
eventkit
references
financekit
references
focus-engine
gamekit
references
healthkit
references
homekit
references
ios-accessibility
ios-localization
ios-networking
ios-simulator
references
mapkit
metrickit
references
musickit
references
natural-language
references
paperkit
references
passkit
references
pdfkit
references
pencilkit
references
permissionkit
references
photokit
push-notifications
realitykit
references
relevancekit
references
scenekit
references
sensorkit
references
speech-recognition
spritekit
references
storekit
swift-api-design-guidelines
swift-architecture
swift-charts
references
swift-codable
swift-concurrency
swift-formatstyle
swift-language
swift-security
references
swift-testing
swiftdata
swiftlint
swiftui-animation
swiftui-gestures
references
swiftui-layout-components
swiftui-liquid-glass
references
swiftui-patterns
swiftui-performance
swiftui-uikit-interop
swiftui-webkit
tabletopkit
references
tipkit
references
vision-framework
weatherkit
references
widgetkit
references