Add CarPlay support to SwiftRadio [archived]
Deprecated: For the scene-based CarPlay setup, read Adding CarPlay support to Swift Radio (iOS 14+).
CarPlay turns SwiftRadio into a truly in-car experience. In this guide, we’ll
add the right entitlements, hook into MPPlayableContentManager, and exercise
our setup using the Simulator so you can ship the feature with confidence.
Setting up the project
-
Clone the SwiftRadio fork that includes the CarPlay target:
git clone https://github.com/analogcode/Swift-Radio-Pro -
Run the app in the iOS Simulator and confirm Hardware → External Displays → CarPlay appears.
If the menu is missing, enable it manually:
defaults write com.apple.iphonesimulator CarPlay -bool YES -
Add the CarPlay entitlement so iOS will expose playable content.
Toggling Push Notifications briefly nudges Xcode to create the
SwiftRadio.entitlementsfile withcom.apple.developer.playable-content = YES.
-
Relaunch the app—you should now see the CarPlay shell.
Add playableContentManager
Start by declaring a shared MPPlayableContentManager on the app delegate:
// AppDelegate.swift
import UIKit
import MediaPlayer
import FRadioPlayer
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
// CarPlay
var playableContentManager: MPPlayableContentManager?
// ...
}
Wire delegates and the StationsManager observer
Keep CarPlay logic in an extension so it stays out of the main app bootstrap:
// AppDelegate+CarPlay.swift
import Foundation
import MediaPlayer
extension AppDelegate {
func setupCarPlay() {
playableContentManager = MPPlayableContentManager.shared()
playableContentManager?.delegate = self
playableContentManager?.dataSource = self
StationsManager.shared.addObserver(self)
}
}
Hook up the delegate protocols:
// AppDelegate+CarPlay.swift
extension AppDelegate: MPPlayableContentDelegate {
func playableContentManager(
_ contentManager: MPPlayableContentManager,
initiatePlaybackOfContentItemAt indexPath: IndexPath,
completionHandler: @escaping (Error?) -> Void
) {
DispatchQueue.main.async {
if indexPath.count == 2 {
let station = StationsManager.shared.stations[indexPath[1]]
StationsManager.shared.set(station: station)
contentManager.nowPlayingIdentifiers = [station.name]
}
completionHandler(nil)
}
}
func beginLoadingChildItems(
at indexPath: IndexPath,
completionHandler: @escaping (Error?) -> Void
) {
StationsManager.shared.fetch {
completionHandler(nil)
}
}
}
// AppDelegate+CarPlay.swift
extension AppDelegate: MPPlayableContentDataSource {
func numberOfChildItems(at indexPath: IndexPath) -> Int {
if indexPath.count == 0 {
return 1
}
return StationsManager.shared.stations.count
}
func contentItem(at indexPath: IndexPath) -> MPContentItem? {
if indexPath.count == 1 {
let item = MPContentItem(identifier: "Stations")
item.title = "Stations"
item.isContainer = true
item.isPlayable = false
return item
}
guard
indexPath.count == 2,
indexPath.item < StationsManager.shared.stations.count
else {
return nil
}
let station = StationsManager.shared.stations[indexPath.item]
let item = MPContentItem(identifier: station.name)
item.title = station.name
item.subtitle = station.desc
item.isPlayable = true
item.isStreamingContent = true
station.getImage { image in
item.artwork = MPMediaItemArtwork(boundsSize: image.size) { _ in image }
}
return item
}
}
// AppDelegate+CarPlay.swift
extension AppDelegate: StationsManagerObserver {
func stationsManager(
_ manager: StationsManager,
stationsDidUpdate stations: [RadioStation]
) {
playableContentManager?.reloadData()
}
func stationsManager(
_ manager: StationsManager,
stationDidChange station: RadioStation?
) {
guard let station else {
playableContentManager?.nowPlayingIdentifiers = []
return
}
playableContentManager?.nowPlayingIdentifiers = [station.name]
}
}
Add the
UIBrowsableContentSupportsSectionedBrowsing = YESkey toInfo.plistso CarPlay treats your playlists as sections.
Run it in the Simulator
Register CarPlay when the app launches:
// AppDelegate.swift
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// ...
setupCarPlay()
return true
}
The CarPlay template now lists stations and highlights the active stream.
The first launch in the Simulator may display the play/pause button out of sync—toggle the transport once to reset it.
Test on real hardware with CarPlay Simulator
-
Request the CarPlay entitlement from Apple using the CarPlay contact form. Once approved, generate a provisioning profile that includes it.
-
Download the Additional Tools for Xcode package from Apple’s developer downloads and launch the CarPlay Simulator app found in the
Hardwarefolder.
-
Connect your iPhone and run the SwiftRadio target to experience the full CarPlay workflow right on your Mac.