Loading...
Loading...
CarPlay framework for iOS in-car applications
npx skill4agent add ios-agent/iosagent.dev carplayCPInterfaceControllerCPWindowCarPlayCPContactCPMessageListItemCPSessionConfigurationCPTemplateApplicationSceneCPTemplateApplicationSceneDelegateCPManeuverCPNavigationSessionCPRouteChoiceCPTripCPPointOfInterestCPActionSheetTemplateCPAlertTemplateCPContactTemplateCPGridTemplateCPInformationTemplateCPListTemplateCPMapTemplateCPNowPlayingTemplateCPPointOfInterestTemplateCPSearchTemplateCPTabBarTemplateCPTemplateCPVoiceControlTemplateCPAlertActionCPBarButtonCPButtonCPGridButtonCPInformationItemCPListImageRowItemCPListItemCPListSectionCPTextButtonCPDashboardButtonCPDashboardControllerCPTemplateApplicationDashboardSceneCPTemplateApplicationDashboardSceneDelegateCPInstrumentClusterControllerCPTemplateApplicationInstrumentClusterSceneCPTemplateApplicationInstrumentClusterSceneDelegatecom.apple.developer.carplay-audiocom.apple.developer.carplay-communicationcom.apple.developer.carplay-mapscom.apple.developer.carplay-parkingcom.apple.developer.carplay-chargingcom.apple.developer.carplay-quick-foodreferences/references/api_reference.mdreferences/communication.mdreferences/dashboard.mdreferences/getting_started.mdreferences/navigation.mdreferences/poi.mdreferences/templates.mdreferences/ui_elements.mdCPTemplateApplicationSceneDelegateupdateSectionsupdateButtons// Create list items
let item1 = CPListItem(text: "Song Title", detailText: "Artist Name")
item1.handler = { item, completion in
// Handle selection
completion()
}
// Create section and template
let section = CPListSection(items: [item1, item2])
let listTemplate = CPListTemplate(title: "My Playlist", sections: [section])
// Set as root
interfaceController.setRootTemplate(listTemplate, animated: true)// Create map template
let mapTemplate = CPMapTemplate()
mapTemplate.mapDelegate = self
// Show trip preview
let trip = CPTrip(/* route data */)
mapTemplate.showTripPreviews([trip], textConfiguration: nil)
// Start navigation
let navigationSession = mapTemplate.startNavigationSession(for: trip)
navigationSession.upcomingManeuvers = [maneuver1, maneuver2]// Create root templates for each tab
let musicTab = CPListTemplate(title: "Music", sections: [musicSection])
musicTab.tabTitle = "Music"
musicTab.tabImage = UIImage(systemName: "music.note")
let podcastsTab = CPListTemplate(title: "Podcasts", sections: [podcastSection])
podcastsTab.tabTitle = "Podcasts"
podcastsTab.tabImage = UIImage(systemName: "mic")
// Create tab bar
let tabBarTemplate = CPTabBarTemplate(templates: [musicTab, podcastsTab])
interfaceController.setRootTemplate(tabBarTemplate, animated: false)// Configure Now Playing buttons
let playButton = CPNowPlayingPlaybackRateButton()
let skipButton = CPNowPlayingSkipButton(handler: { _ in
// Skip to next track
})
CPNowPlayingTemplate.shared.updateNowPlayingButtons([playButton, skipButton])
CPNowPlayingTemplate.shared.isUpNextButtonEnabled = true// Create team logos
let homeTeamLogo = CPNowPlayingSportsTeamLogo(teamLogo: UIImage(named: "home_logo")!)
let awayTeamLogo = CPNowPlayingSportsTeamLogo(teamInitials: "VIS")
// Create teams with scores and possession
let homeTeam = CPNowPlayingSportsTeam(
name: "Home Team",
logo: homeTeamLogo,
teamStandings: "1st Place",
eventScore: "21",
possessionIndicator: .solid, // Home team has possession
favorite: true
)
let awayTeam = CPNowPlayingSportsTeam(
name: "Away Team",
logo: awayTeamLogo,
teamStandings: "2nd Place",
eventScore: "17",
possessionIndicator: nil, // No possession
favorite: false
)
// Create game clock (counting down from 15:00)
let gameClock = CPNowPlayingSportsClock(
timeRemaining: 15 * 60, // 15 minutes in seconds
paused: false
)
// Create event status with status text and clock
let eventStatus = CPNowPlayingSportsEventStatus(
eventStatusText: ["3rd Quarter", "4th Down", "Goal Line"],
eventStatusImage: UIImage(named: "field_position"),
eventClock: gameClock
)
// Create sports mode
let sportsMode = CPNowPlayingModeSports(
leftTeam: homeTeam,
rightTeam: awayTeam,
eventStatus: eventStatus,
backgroundArtwork: UIImage(named: "stadium_background")
)
// Set as active now playing mode
CPNowPlayingTemplate.shared.nowPlayingMode = sportsMode
// Update clock during game (e.g., every second)
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
// Update time remaining
let newClock = CPNowPlayingSportsClock(
timeRemaining: getCurrentGameTime(),
paused: isGamePaused()
)
// Update event status with new clock
let updatedStatus = CPNowPlayingSportsEventStatus(
eventStatusText: eventStatus.eventStatusText,
eventStatusImage: eventStatus.eventStatusImage,
eventClock: newClock
)
// Refresh sports mode
let updatedMode = CPNowPlayingModeSports(
leftTeam: homeTeam,
rightTeam: awayTeam,
eventStatus: updatedStatus,
backgroundArtwork: sportsMode.backgroundArtwork
)
CPNowPlayingTemplate.shared.nowPlayingMode = updatedMode
}// Create maneuvers for turn-by-turn guidance
let maneuver = CPManeuver()
maneuver.instructionVariants = ["Turn right onto Main Street"]
maneuver.symbolImage = UIImage(named: "turn-right")
maneuver.initialTravelEstimates = CPTravelEstimates(
distanceRemaining: Measurement(value: 500, unit: UnitLength.meters),
timeRemaining: 60
)
// Add lane guidance
let lane1 = CPLane(angles: [.left, .straight])
lane1.status = .recommended
let lane2 = CPLane(angles: [.straight, .right])
lane2.status = .preferred
maneuver.linkedLaneGuidance = CPLaneGuidance(lanes: [lane1, lane2], instructionVariants: ["Use right lane"])
// Add maneuver to navigation session
navigationSession.upcomingManeuvers = [maneuver]// Create attributed instruction with embedded image
let instruction = NSMutableAttributedString(string: "Turn right on Apple Park Way")
// Attach an image icon
let image = UIImage(systemName: "arrow.turn.up.right")!
let attachment = NSTextAttachment(image: image)
let imageString = NSAttributedString(attachment: attachment)
instruction.append(NSAttributedString(string: " "))
instruction.append(imageString)
// Create maneuver with attributed instruction
let maneuver = CPManeuver()
maneuver.attributedInstructionVariants = [instruction]
// Also set dashboard-specific attributed instructions
let dashboardInstruction = NSMutableAttributedString(string: "Turn Right ")
let dashboardImage = UIImage(systemName: "arrow.turn.up.right.circle.fill")!
let dashboardAttachment = NSTextAttachment(image: dashboardImage)
dashboardInstruction.append(NSAttributedString(attachment: dashboardAttachment))
dashboardInstruction.append(NSAttributedString(string: " Apple Park Way"))
maneuver.dashboardAttributedInstructionVariants = [dashboardInstruction]
// Set symbol image for main display
maneuver.symbolImage = UIImage(named: "turn-right")
// Add to navigation session
navigationSession.upcomingManeuvers = [maneuver]// Create route choices for trip preview
let fastRoute = CPRouteChoice(
summaryVariants: ["Via Highway - 25 min"],
additionalInformationVariants: ["Fastest route"],
selectionSummaryVariants: ["Take the highway"]
)
let scenicRoute = CPRouteChoice(
summaryVariants: ["Via Scenic Road - 35 min"],
additionalInformationVariants: ["No tolls"],
selectionSummaryVariants: ["Take the scenic route"]
)
let trip = CPTrip(
origin: originMapItem,
destination: destinationMapItem,
routeChoices: [fastRoute, scenicRoute]
)
mapTemplate.showTripPreviews([trip], textConfiguration: nil)// Configure dashboard scene delegate
func templateApplicationDashboardScene(
_ dashboardScene: CPTemplateApplicationDashboardScene,
didConnect dashboardController: CPDashboardController,
to window: UIWindow
) {
// Create dashboard shortcut buttons
let homeButton = CPDashboardButton(
titleVariants: ["Home"],
subtitleVariants: ["123 Main St"],
image: UIImage(systemName: "house.fill")!
) { button in
// Navigate to home
}
let workButton = CPDashboardButton(
titleVariants: ["Work"],
subtitleVariants: ["456 Office Blvd"],
image: UIImage(systemName: "building.2.fill")!
) { button in
// Navigate to work
}
// Set dashboard shortcut buttons
dashboardController.shortcutButtons = [homeButton, workButton]
}// Configure instrument cluster scene delegate
func templateApplicationInstrumentClusterScene(
_ instrumentClusterScene: CPTemplateApplicationInstrumentClusterScene,
didConnect instrumentClusterController: CPInstrumentClusterController
) {
// Configure instrument cluster display
instrumentClusterController.inactiveDescriptionVariants = [
"No active navigation"
]
// Set compass display
instrumentClusterController.compassSetting = .automatic
// Update with route information (iOS 17.4+)
let routeInfo = CPRouteInformation(
maneuvers: upcomingManeuvers,
laneGuidances: lanGuidances,
currentManeuvers: [currentManeuver],
currentLaneGuidance: currentLaneGuidance,
trip: currentTrip,
maneuverTravelEstimates: travelEstimates
)
// Display on instrument cluster
instrumentClusterController.compassSetting = .showAlways
}// Create grid buttons with icons
let homeButton = CPGridButton(
titleVariants: ["Home"],
image: UIImage(systemName: "house.fill")!
) { button in
// Navigate to home
}
let workButton = CPGridButton(
titleVariants: ["Work"],
image: UIImage(systemName: "building.2.fill")!
) { button in
// Navigate to work
}
let favoritesButton = CPGridButton(
titleVariants: ["Favorites"],
image: UIImage(systemName: "star.fill")!
) { button in
// Show favorites
}
// Create grid template with up to 8 buttons
let gridTemplate = CPGridTemplate(
title: "Quick Actions",
gridButtons: [homeButton, workButton, favoritesButton]
)
interfaceController.setRootTemplate(gridTemplate, animated: true)
// Update buttons dynamically
let newButtons = [homeButton, workButton, favoritesButton, newButton]
gridTemplate.updateGridButtons(newButtons)// Create search template
let searchTemplate = CPSearchTemplate()
searchTemplate.delegate = self
// Set as root
interfaceController.setRootTemplate(searchTemplate, animated: true)
// Implement CPSearchTemplateDelegate
func searchTemplate(
_ searchTemplate: CPSearchTemplate,
updatedSearchText searchText: String,
completionHandler: @escaping ([CPListItem]) -> Void
) {
// Perform search
let results = performSearch(query: searchText)
// Convert to list items
let listItems = results.map { result in
let item = CPListItem(text: result.name, detailText: result.address)
item.handler = { item, completion in
// Handle selection
self.showResultDetails(result)
completion()
}
return item
}
// Return results
completionHandler(listItems)
}
func searchTemplateSearchButtonPressed(_ searchTemplate: CPSearchTemplate) {
// User tapped search button
print("Search initiated")
}// Create points of interest
let poi1 = CPPointOfInterest(
location: MKMapItem(placemark: MKPlacemark(coordinate: coordinate1)),
title: "Apple Park",
subtitle: "1 Apple Park Way",
summary: "Apple headquarters",
detailTitle: "Visitor Center",
detailSubtitle: "Open 9 AM - 5 PM",
detailSummary: "Tours available",
pinImage: UIImage(systemName: "applelogo")
)
let poi2 = CPPointOfInterest(
location: MKMapItem(placemark: MKPlacemark(coordinate: coordinate2)),
title: "Golden Gate Bridge",
subtitle: "San Francisco",
summary: "Iconic landmark",
detailTitle: "Vista Point",
detailSubtitle: "Always Open",
detailSummary: "Great views",
pinImage: UIImage(systemName: "bridge")
)
// Create POI template
let poiTemplate = CPPointOfInterestTemplate(
title: "Nearby Attractions",
pointsOfInterest: [poi1, poi2],
selectedIndex: 0
)
poiTemplate.pointOfInterestDelegate = self
// Implement delegate
func pointOfInterestTemplate(
_ template: CPPointOfInterestTemplate,
didSelectPointOfInterest poi: CPPointOfInterest
) {
// User selected a POI
print("Selected: \(poi.title)")
// Show navigation options
showNavigationOptions(for: poi)
}
func pointOfInterestTemplate(
_ template: CPPointOfInterestTemplate,
didChangeMapRegion region: MKCoordinateRegion
) {
// Map region changed (user panned/zoomed)
fetchNearbyPOIs(in: region)
}// Create information items
let addressItem = CPInformationItem(
title: "Address",
detail: "1 Apple Park Way, Cupertino, CA"
)
let hoursItem = CPInformationItem(
title: "Hours",
detail: "9:00 AM - 9:00 PM"
)
let phoneItem = CPInformationItem(
title: "Phone",
detail: "(408) 996-1010"
)
// Create action buttons
let callButton = CPTextButton(text: "Call", textStyle: .normal) { button in
// Make phone call
makePhoneCall()
}
let directionsButton = CPTextButton(text: "Directions", textStyle: .confirm) { button in
// Start navigation
startNavigation()
}
// Create information template
let infoTemplate = CPInformationTemplate(
title: "Apple Store",
layout: .leading, // or .twoColumn
items: [addressItem, hoursItem, phoneItem],
actions: [callButton, directionsButton]
)
interfaceController.pushTemplate(infoTemplate, animated: true)// Create actions for action sheet
let confirmAction = CPAlertAction(title: "Confirm", style: .default) { action in
// User confirmed
self.performAction()
}
let cancelAction = CPAlertAction(title: "Cancel", style: .cancel) { action in
// User cancelled
print("Action cancelled")
}
let destructiveAction = CPAlertAction(title: "Delete", style: .destructive) { action in
// User chose destructive action
self.deleteItem()
}
// Create action sheet
let actionSheet = CPActionSheetTemplate(
title: "Choose Action",
message: "What would you like to do?",
actions: [confirmAction, destructiveAction, cancelAction]
)
// Present modally
interfaceController.presentTemplate(actionSheet, animated: true, completion: nil)// Create alert actions
let okAction = CPAlertAction(title: "OK", style: .default) { action in
// User tapped OK
print("User acknowledged")
}
let retryAction = CPAlertAction(title: "Retry", style: .default) { action in
// User wants to retry
self.retryOperation()
}
let cancelAction = CPAlertAction(title: "Cancel", style: .cancel) { action in
// User cancelled
}
// Create alert with multiple title variants for different screen sizes
let alert = CPAlertTemplate(
titleVariants: [
"Connection Lost",
"Lost Connection",
"No Network"
],
actions: [retryAction, cancelAction]
)
// Present alert
interfaceController.presentTemplate(alert, animated: true, completion: nil)
// Auto-dismiss after 5 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
interfaceController.dismissTemplate(animated: true, completion: nil)
}// Create voice control states
let listeningState = CPVoiceControlState(
identifier: "listening",
titleVariants: ["Listening...", "Say a command"],
image: UIImage(systemName: "waveform")!,
repeats: true
)
let processingState = CPVoiceControlState(
identifier: "processing",
titleVariants: ["Processing...", "Working on it"],
image: UIImage(systemName: "gear")!,
repeats: false
)
let successState = CPVoiceControlState(
identifier: "success",
titleVariants: ["Done!", "Complete"],
image: UIImage(systemName: "checkmark.circle.fill")!,
repeats: false
)
// Create voice control template
let voiceTemplate = CPVoiceControlTemplate(
voiceControlStates: [listeningState, processingState, successState]
)
// Present voice control
interfaceController.presentTemplate(voiceTemplate, animated: true) {
// Start listening
self.startVoiceRecognition()
}
// Update state based on recognition progress
voiceTemplate.activateVoiceControlState(withIdentifier: "processing")
// Show success when done
voiceTemplate.activateVoiceControlState(withIdentifier: "success")
// Dismiss after completion
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
interfaceController.dismissTemplate(animated: true, completion: nil)
}// Create contact
let contact = CPContact(
name: "John Appleseed",
image: UIImage(named: "contact_photo")
)
contact.subtitle = "Friend"
contact.informativeText = "Last contact: Today"
// Create contact action buttons
let callButton = CPContactCallButton(handler: { button in
// Initiate phone call
self.makePhoneCall(to: contact)
})
let messageButton = CPContactMessageButton(phoneOrEmail: "john@example.com")
let directionsButton = CPContactDirectionsButton(handler: { button in
// Navigate to contact's location
self.navigateToContact(contact)
})
contact.actions = [callButton, messageButton, directionsButton]
// Create contact template
let contactTemplate = CPContactTemplate(contact: contact)
// Present contact card
interfaceController.pushTemplate(contactTemplate, animated: true)// Create leading configurations (profile images)
let profileImage = UIImage(named: "profile")!
let leadingConfig = CPMessageListItemLeadingConfiguration(
leadingItem: .image(profileImage),
leadingImage: profileImage,
unreadIndicator: true
)
// Create trailing configurations (timestamps/badges)
let trailingConfig = CPMessageListItemTrailingConfiguration(
trailingItem: .image(UIImage(systemName: "chevron.right")!),
trailingImage: UIImage(systemName: "paperclip")
)
// Create message list items
let message1 = CPMessageListItem(
conversationIdentifier: "conv_1",
text: "John Appleseed",
leadingConfiguration: leadingConfig,
trailingConfiguration: trailingConfig,
detailText: "Hey, are you free for lunch?",
trailingText: "12:30 PM"
)
message1.handler = { item, completion in
// Open conversation
self.openConversation(id: "conv_1")
completion()
}
let message2 = CPMessageListItem(
fullName: "Jane Smith",
phoneOrEmailAddress: "jane@example.com",
leadingConfiguration: CPMessageListItemLeadingConfiguration(
leadingItem: .image(UIImage(named: "jane_photo")!),
leadingImage: UIImage(named: "jane_photo")!,
unreadIndicator: false
),
trailingConfiguration: trailingConfig,
detailText: "Thanks for the update!",
trailingText: "Yesterday"
)
// Create list template with messages
let section = CPListSection(items: [message1, message2])
let listTemplate = CPListTemplate(title: "Messages", sections: [section])
interfaceController.setRootTemplate(listTemplate, animated: true)// Create images for grid display
let image1 = UIImage(named: "album_cover_1")!
let image2 = UIImage(named: "album_cover_2")!
let image3 = UIImage(named: "album_cover_3")!
let image4 = UIImage(named: "album_cover_4")!
// Create list image row item with titles
let imageRowItem = CPListImageRowItem(
text: "Recently Played",
images: [image1, image2, image3, image4],
imageTitles: ["Rock Hits", "Chill Vibes", "Workout Mix", "Road Trip"]
)
// Handle image selection
imageRowItem.listImageRowHandler = { item, index, completion in
// User selected image at index
print("Selected image \(index)")
self.playAlbum(at: index)
completion()
}
// Handle row selection
imageRowItem.handler = { item, completion in
// User tapped the entire row (not a specific image)
self.showRecentlyPlayed()
completion()
}
// Create list with image row
let section = CPListSection(items: [imageRowItem])
let listTemplate = CPListTemplate(title: "Music", sections: [section])
// Update images dynamically
let newImages = [image1, image2, image3, image4, image5]
imageRowItem.update(newImages)references/