Adding CarPlay support to Swift Radio (iOS 14+)
CarPlay support is a great way to bring your app’s audio experience into the car, making it safer and more convenient for users on the road. In this guide, I’ll walk you through the key pieces you need to set up: updating your Info.plist, adding the right entitlements, creating a CarPlay scene delegate, and keeping your app’s UI in sync with the CarPlay interface.
1. Add CarPlay scenes to Info.plist
First step is updating your Info.plist to include CarPlay scenes. In SwiftRadio/Info-CarPlay.plist, you’ll add a UIApplicationSceneManifest that connects your app scene with the CarPlay scene delegate:
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
</dict>
</array>
<key>CPTemplateApplicationSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>CPTemplateApplicationScene</string>
<key>UISceneConfigurationName</key>
<string>CarPlay Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).CarPlaySceneDelegate</string>
</dict>
</array>
</dict>
</dict>
You’ll also want to add these CarPlay-specific entries:
UIBrowsableContentSupportsSectionedBrowsing = trueUISupportedExternalAccessoryProtocols = ["com.apple.carplay"]NSCarPlayAudioUsageDescriptionwith a brief explanation of why your app needs audio access
2. Add the CarPlay entitlements
Next, make sure your entitlements file includes the necessary permissions for CarPlay. In SwiftRadio/SwiftRadio.entitlements, add:
<dict>
<key>com.apple.developer.playable-content</key>
<true/>
<key>com.apple.developer.carplay-audio</key>
<true/>
</dict>
These entitlements let your app play audio and integrate properly with CarPlay.
3. Build the CarPlay scene delegate
The CarPlay scene delegate manages the CarPlay interface lifecycle. When the CarPlay interface connects, it sets up a list of radio stations, updates it as needed, and responds to user selections. Here’s how that looks in code:
final class CarPlaySceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate {
private var interfaceController: CPInterfaceController?
func templateApplicationScene(
_ scene: CPTemplateApplicationScene,
didConnect interfaceController: CPInterfaceController
) {
self.interfaceController = interfaceController
let template = CPListTemplate(title: "Radio Stations", sections: [])
interfaceController.setRootTemplate(template, animated: false, completion: nil)
if StationsManager.shared.stations.isEmpty {
StationsManager.shared.fetch { [weak self] _ in
self?.reloadStations(on: template)
}
} else {
reloadStations(on: template)
}
StationsManager.shared.addObserver(self)
}
private func reloadStations(on template: CPListTemplate) {
let items = StationsManager.shared.stations.map { station -> CPListItem in
let item = CPListItem(text: station.name, detailText: station.desc)
station.getImage { image in item.setImage(image) }
item.handler = { _, completion in
StationsManager.shared.set(station: station)
FRadioPlayer.shared.play()
completion()
}
return item
}
template.updateSections([CPListSection(items: items)])
}
}
extension CarPlaySceneDelegate: StationsManagerObserver {
func stationsManager(
_ manager: StationsManager,
stationsDidUpdate stations: [RadioStation]
) {
guard let template = interfaceController?.rootTemplate as? CPListTemplate else { return }
DispatchQueue.main.async { self.reloadStations(on: template) }
}
}
This delegate leverages the shared StationsManager and FRadioPlayer singleton, so when users pick a station on CarPlay, it seamlessly updates the app’s data sources.
4. Keep the App UI aligned with CarPlay
Finally, don’t forget to keep your app’s UI in sync with what’s happening in CarPlay. In your StationsViewController, initialize the now-playing bar when the app launches from a CarPlay session like this:
// StationsViewController.viewDidLoad
updateNowPlayingButton(station: manager.currentStation)
updateHandoffUserActivity(userActivity, station: manager.currentStation)
startNowPlayingAnimation(player.isPlaying)
This way, your app’s state stays consistent with the station selected on the CarPlay dashboard.
I’ll drop a CarPlay screenshot here once I have a good one to share.