Jekyll2022-11-14T03:37:52+00:00https://blog.fethica.com/feed.xmlFethi’s BlogiOS developer with a keen eye for design.Fethi El HassasnaAdd CarPlay support to Swift Radio2019-02-10T14:45:06+00:002022-11-12T14:45:06+00:00https://blog.fethica.com/add-carplay-support-to-swiftradio<p>In this tutorial, we will add <strong>CarPlay</strong> support to the open-source radio app <a href="https://github.com/analogcode/Swift-Radio-Pro">SwiftRadio</a> and test it using the <strong>Simulator</strong> / <strong>CarPlay Simulator</strong>.</p>
<h1 id="setting-up-the-project">Setting up the project</h1>
<p>First let’s start by cloning the project, or downloading it directly from <a href="https://github.com/analogcode/Swift-Radio-Pro">GitHub</a>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/analogcode/Swift-Radio-Pro
</code></pre></div></div>
<p>After running the project using the <strong>Simulator</strong>, we want to make sure that we have the <strong>CarPlay</strong> menu available under <strong>Hardware > External Displays > CarPlay</strong>:</p>
<p><img src="/assets/images/20190210/1.carplay.menu.jpg" alt="Hardware > External Displays > CarPlay" class="align-center" /></p>
<p>If you can’t find the CarPlay menu, open the <strong>Terminal</strong> and run the following command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>defaults write com.apple.iphonesimulator CarPlay <span class="nt">-bool</span> YES
</code></pre></div></div>
<p>Now we need to add an entitlement file to the app and add the two following entry:</p>
<p><code class="language-plaintext highlighter-rouge">com.apple.developer.playable-content</code> as a <code class="language-plaintext highlighter-rouge">Boolean/YES</code> value.</p>
<p>To generate the file automatically we can toggle the <strong>PushNotification</strong> in our app <strong>target > capabilities</strong> and turn it off later:</p>
<p><img src="/assets/images/20190210/2.entitlement.file.1.jpg" alt="target > capabilities" class="align-center" /></p>
<p>The <code class="language-plaintext highlighter-rouge">SwiftRadio.entitlements</code> file should look like this:</p>
<p><img src="/assets/images/20190210/3.entitlement.file.2.jpg" alt="target > capabilities" class="align-center" /></p>
<p>When we run the app again, we’ll be able to see our <strong>CarPlay</strong> app:</p>
<p><img src="/assets/images/20190210/4.carplay.empty.list.jpg" alt="target > capabilities" class="align-center" /></p>
<h1 id="add-playablecontentmanager">Add <code class="language-plaintext highlighter-rouge">playableContentManager</code></h1>
<p>Next, let’s jump to the project and start adding some code.</p>
<p>First, we need to import the <strong>MediaPlayer</strong> framework in our <strong>AppDelegate</strong> class, and add a new property <code class="language-plaintext highlighter-rouge">playableContentManager</code>:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// AppDelegate.swift</span>
<span class="kd">import</span> <span class="kt">UIKit</span>
<span class="kd">import</span> <span class="kt">MediaPlayer</span>
<span class="kd">import</span> <span class="kt">FRadioPlayer</span>
<span class="kd">@UIApplicationMain</span>
<span class="kd">class</span> <span class="kt">AppDelegate</span><span class="p">:</span> <span class="kt">UIResponder</span><span class="p">,</span> <span class="kt">UIApplicationDelegate</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">window</span><span class="p">:</span> <span class="kt">UIWindow</span><span class="p">?</span>
<span class="c1">// CarPlay</span>
<span class="k">var</span> <span class="nv">playableContentManager</span><span class="p">:</span> <span class="kt">MPPlayableContentManager</span><span class="p">?</span>
<span class="c1">// ... </span>
<span class="p">}</span>
</code></pre></div></div>
<h1 id="add-delegates-and-stationsmanager-observer">Add delegates and <code class="language-plaintext highlighter-rouge">StationsManager</code> observer</h1>
<p>To separate <strong>CarPlay</strong> logic, we can create a new file extension for the <code class="language-plaintext highlighter-rouge">AppDelegate</code> class, and name it <code class="language-plaintext highlighter-rouge">AppDelegate+CarPlay.swift</code>.</p>
<p>First, let’s add a setup function to initiate our <code class="language-plaintext highlighter-rouge">playableContentManager</code> property, set both the <code class="language-plaintext highlighter-rouge">delegate</code> and <code class="language-plaintext highlighter-rouge">dataSource</code> properties to <code class="language-plaintext highlighter-rouge">self</code> (<code class="language-plaintext highlighter-rouge">AppDelegate</code>), and also add it as a <code class="language-plaintext highlighter-rouge">StationsManager</code> observer:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// AppDelegate+CarPlay.swift</span>
<span class="kd">import</span> <span class="kt">Foundation</span>
<span class="kd">import</span> <span class="kt">MediaPlayer</span>
<span class="kd">extension</span> <span class="kt">AppDelegate</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">setupCarPlay</span><span class="p">()</span> <span class="p">{</span>
<span class="n">playableContentManager</span> <span class="o">=</span> <span class="kt">MPPlayableContentManager</span><span class="o">.</span><span class="nf">shared</span><span class="p">()</span>
<span class="n">playableContentManager</span><span class="p">?</span><span class="o">.</span><span class="n">delegate</span> <span class="o">=</span> <span class="k">self</span>
<span class="n">playableContentManager</span><span class="p">?</span><span class="o">.</span><span class="n">dataSource</span> <span class="o">=</span> <span class="k">self</span>
<span class="kt">StationsManager</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">addObserver</span><span class="p">(</span><span class="k">self</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Next, we add an extension for the <code class="language-plaintext highlighter-rouge">delegate</code> protocol:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// AppDelegate+CarPlay.swift</span>
<span class="kd">extension</span> <span class="kt">AppDelegate</span><span class="p">:</span> <span class="kt">MPPlayableContentDelegate</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">playableContentManager</span><span class="p">(</span><span class="n">_</span> <span class="nv">contentManager</span><span class="p">:</span> <span class="kt">MPPlayableContentManager</span><span class="p">,</span> <span class="n">initiatePlaybackOfContentItemAt</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">,</span> <span class="nv">completionHandler</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="p">(</span><span class="kt">Error</span><span class="p">?)</span> <span class="o">-></span> <span class="kt">Void</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">completionHandler</span><span class="p">(</span><span class="kc">nil</span><span class="p">)</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">beginLoadingChildItems</span><span class="p">(</span><span class="n">at</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">,</span> <span class="nv">completionHandler</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="p">(</span><span class="kt">Error</span><span class="p">?)</span> <span class="o">-></span> <span class="kt">Void</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">completionHandler</span><span class="p">(</span><span class="kc">nil</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Then, add the <code class="language-plaintext highlighter-rouge">MPPlayableContentDataSource</code> required function into another extension:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// AppDelegate+CarPlay.swift</span>
<span class="kd">extension</span> <span class="kt">AppDelegate</span><span class="p">:</span> <span class="kt">MPPlayableContentDataSource</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">numberOfChildItems</span><span class="p">(</span><span class="n">at</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Int</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">0</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">contentItem</span><span class="p">(</span><span class="n">at</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">)</span> <span class="o">-></span> <span class="kt">MPContentItem</span><span class="p">?</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">nil</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Finally, we add an extension for <code class="language-plaintext highlighter-rouge">StationsManagerObserver</code>:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// AppDelegate+CarPlay.swift</span>
<span class="kd">extension</span> <span class="kt">AppDelegate</span><span class="p">:</span> <span class="kt">StationsManagerObserver</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">stationsManager</span><span class="p">(</span><span class="n">_</span> <span class="nv">manager</span><span class="p">:</span> <span class="kt">StationsManager</span><span class="p">,</span> <span class="n">stationsDidUpdate</span> <span class="nv">stations</span><span class="p">:</span> <span class="p">[</span><span class="kt">RadioStation</span><span class="p">])</span> <span class="p">{</span>
<span class="c1">// code here</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">stationsManager</span><span class="p">(</span><span class="n">_</span> <span class="nv">manager</span><span class="p">:</span> <span class="kt">StationsManager</span><span class="p">,</span> <span class="n">stationDidChange</span> <span class="nv">station</span><span class="p">:</span> <span class="kt">RadioStation</span><span class="p">?)</span> <span class="p">{</span>
<span class="c1">// code here</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="implement-playablecontentmanager-delegate">Implement <code class="language-plaintext highlighter-rouge">playableContentManager</code> delegate</h2>
<p>Let’s start by implementing <code class="language-plaintext highlighter-rouge">initiatePlaybackOfContentItemAt</code> delegate method, this function will be triggered when the user selects a station from the <strong>CarPlay</strong> list:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// MPPlayableContentDelegate</span>
<span class="kd">func</span> <span class="nf">playableContentManager</span><span class="p">(</span><span class="n">_</span> <span class="nv">contentManager</span><span class="p">:</span> <span class="kt">MPPlayableContentManager</span><span class="p">,</span> <span class="n">initiatePlaybackOfContentItemAt</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">,</span> <span class="nv">completionHandler</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="p">(</span><span class="kt">Error</span><span class="p">?)</span> <span class="o">-></span> <span class="kt">Void</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">DispatchQueue</span><span class="o">.</span><span class="n">main</span><span class="o">.</span><span class="n">async</span> <span class="p">{</span>
<span class="c1">// Check if the user tapped the second section (first section will be reserved for the list tab name) </span>
<span class="k">if</span> <span class="n">indexPath</span><span class="o">.</span><span class="n">count</span> <span class="o">==</span> <span class="mi">2</span> <span class="p">{</span>
<span class="c1">// Getting the selected station</span>
<span class="k">let</span> <span class="nv">station</span> <span class="o">=</span> <span class="kt">StationsManager</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="n">stations</span><span class="p">[</span><span class="n">indexPath</span><span class="p">[</span><span class="mi">1</span><span class="p">]]</span>
<span class="c1">// Setting the station, this will trigger the player playback</span>
<span class="kt">StationsManager</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">set</span><span class="p">(</span><span class="nv">station</span><span class="p">:</span> <span class="n">station</span><span class="p">)</span>
<span class="c1">// Tell the `contentManager` the playing identifier, we are using the station name as an ID here.</span>
<span class="n">contentManager</span><span class="o">.</span><span class="n">nowPlayingIdentifiers</span> <span class="o">=</span> <span class="p">[</span><span class="n">station</span><span class="o">.</span><span class="n">name</span><span class="p">]</span>
<span class="p">}</span>
<span class="nf">completionHandler</span><span class="p">(</span><span class="kc">nil</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The next function in the delegate section is <code class="language-plaintext highlighter-rouge">beginLoadingChildItems</code>:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// MPPlayableContentDelegate</span>
<span class="kd">func</span> <span class="nf">beginLoadingChildItems</span><span class="p">(</span><span class="n">at</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">,</span> <span class="nv">completionHandler</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="p">(</span><span class="kt">Error</span><span class="p">?)</span> <span class="o">-></span> <span class="kt">Void</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// We call the stations' manager fetch function to get the station list</span>
<span class="kt">StationsManager</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="n">fetch</span> <span class="p">{</span>
<span class="nf">completionHandler</span><span class="p">(</span><span class="kc">nil</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="implement-playablecontentmanager-datasource">Implement <code class="language-plaintext highlighter-rouge">playableContentManager</code> datasource:</h2>
<p>For the <code class="language-plaintext highlighter-rouge">datasource</code> we can create a single tab to represent the stations’ list, to do so we need to add the following <code class="language-plaintext highlighter-rouge">UIBrowsableContentSupportsSectionedBrowsing</code> <code class="language-plaintext highlighter-rouge">Boolean / YES</code> value to our app’s <code class="language-plaintext highlighter-rouge">info.plist</code>:</p>
<p><img src="/assets/images/20190210/5.info.plist.jpg" alt="target > capabilities" class="align-center" /></p>
<p>Now, let’s add the data needed in the <code class="language-plaintext highlighter-rouge">datasource</code>, for <code class="language-plaintext highlighter-rouge">numberOfChildItems</code>, the number of tabs is 1, and the number of items is the <code class="language-plaintext highlighter-rouge">StationsManager.shared.stations</code> <code class="language-plaintext highlighter-rouge">count</code>:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// MPPlayableContentDataSource</span>
<span class="kd">func</span> <span class="nf">numberOfChildItems</span><span class="p">(</span><span class="n">at</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Int</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">indexPath</span><span class="o">.</span><span class="n">indices</span><span class="o">.</span><span class="n">count</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">1</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kt">StationsManager</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="n">stations</span><span class="o">.</span><span class="n">count</span>
<span class="p">}</span>
</code></pre></div></div>
<p>In the <code class="language-plaintext highlighter-rouge">contentItem(at indexPath: IndexPath) -> MPContentItem?</code> function we create an item of type <code class="language-plaintext highlighter-rouge">MPContentItem</code> for each section (tab and list), for the tab we add:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// MPPlayableContentDataSource</span>
<span class="kd">func</span> <span class="nf">contentItem</span><span class="p">(</span><span class="n">at</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">)</span> <span class="o">-></span> <span class="kt">MPContentItem</span><span class="p">?</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">indexPath</span><span class="o">.</span><span class="n">count</span> <span class="o">==</span> <span class="mi">1</span> <span class="p">{</span>
<span class="c1">// Tab section</span>
<span class="k">let</span> <span class="nv">item</span> <span class="o">=</span> <span class="kt">MPContentItem</span><span class="p">(</span><span class="nv">identifier</span><span class="p">:</span> <span class="s">"Stations"</span><span class="p">)</span>
<span class="n">item</span><span class="o">.</span><span class="n">title</span> <span class="o">=</span> <span class="s">"Stations"</span>
<span class="n">item</span><span class="o">.</span><span class="n">isContainer</span> <span class="o">=</span> <span class="kc">true</span>
<span class="n">item</span><span class="o">.</span><span class="n">isPlayable</span> <span class="o">=</span> <span class="kc">false</span>
<span class="n">item</span><span class="o">.</span><span class="n">artwork</span> <span class="o">=</span> <span class="kt">MPMediaItemArtwork</span><span class="p">(</span><span class="nv">boundsSize</span><span class="p">:</span> <span class="err">#</span><span class="nf">imageLiteral</span><span class="p">(</span><span class="nv">resourceName</span><span class="p">:</span> <span class="s">"carPlayTab"</span><span class="p">)</span><span class="o">.</span><span class="n">size</span><span class="p">,</span> <span class="nv">requestHandler</span><span class="p">:</span> <span class="p">{</span> <span class="n">_</span> <span class="o">-></span> <span class="kt">UIImage</span> <span class="k">in</span>
<span class="k">return</span> <span class="err">#</span><span class="nf">imageLiteral</span><span class="p">(</span><span class="nv">resourceName</span><span class="p">:</span> <span class="s">"carPlayTab"</span><span class="p">)</span>
<span class="p">})</span>
<span class="k">return</span> <span class="n">item</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kc">nil</span>
<span class="p">}</span>
</code></pre></div></div>
<p class="notice--info">You can download the <code class="language-plaintext highlighter-rouge">carPlayTab</code> icon images from <a href="/assets/zips/carPlayTabIcon.zip">here</a> and add them to the project’s <strong>Images.xcassets</strong>.</p>
<p>For the stations’ list:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// MPPlayableContentDataSource</span>
<span class="kd">func</span> <span class="nf">contentItem</span><span class="p">(</span><span class="n">at</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">)</span> <span class="o">-></span> <span class="kt">MPContentItem</span><span class="p">?</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">indexPath</span><span class="o">.</span><span class="n">count</span> <span class="o">==</span> <span class="mi">1</span> <span class="p">{</span>
<span class="c1">// Tab section</span>
<span class="c1">// ... Code added in the previous section</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">indexPath</span><span class="o">.</span><span class="n">count</span> <span class="o">==</span> <span class="mi">2</span><span class="p">,</span> <span class="n">indexPath</span><span class="o">.</span><span class="n">item</span> <span class="o"><</span> <span class="kt">StationsManager</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="n">stations</span><span class="o">.</span><span class="n">count</span> <span class="p">{</span>
<span class="c1">// Stations section</span>
<span class="k">let</span> <span class="nv">station</span> <span class="o">=</span> <span class="kt">StationsManager</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="n">stations</span><span class="p">[</span><span class="n">indexPath</span><span class="o">.</span><span class="n">item</span><span class="p">]</span>
<span class="k">let</span> <span class="nv">item</span> <span class="o">=</span> <span class="kt">MPContentItem</span><span class="p">(</span><span class="nv">identifier</span><span class="p">:</span> <span class="s">"</span><span class="se">\(</span><span class="n">station</span><span class="o">.</span><span class="n">name</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
<span class="n">item</span><span class="o">.</span><span class="n">title</span> <span class="o">=</span> <span class="n">station</span><span class="o">.</span><span class="n">name</span>
<span class="n">item</span><span class="o">.</span><span class="n">subtitle</span> <span class="o">=</span> <span class="n">station</span><span class="o">.</span><span class="n">desc</span>
<span class="n">item</span><span class="o">.</span><span class="n">isPlayable</span> <span class="o">=</span> <span class="kc">true</span>
<span class="n">item</span><span class="o">.</span><span class="n">isStreamingContent</span> <span class="o">=</span> <span class="kc">true</span>
<span class="n">station</span><span class="o">.</span><span class="n">getImage</span> <span class="p">{</span> <span class="n">image</span> <span class="k">in</span>
<span class="n">item</span><span class="o">.</span><span class="n">artwork</span> <span class="o">=</span> <span class="kt">MPMediaItemArtwork</span><span class="p">(</span><span class="nv">boundsSize</span><span class="p">:</span> <span class="n">image</span><span class="o">.</span><span class="n">size</span><span class="p">)</span> <span class="p">{</span> <span class="n">_</span> <span class="o">-></span> <span class="kt">UIImage</span> <span class="k">in</span>
<span class="k">return</span> <span class="n">image</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">item</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">nil</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="implement-staionsmanager-observers">Implement <code class="language-plaintext highlighter-rouge">StaionsManager</code> observers</h2>
<p>For the <code class="language-plaintext highlighter-rouge">StationsManagerObserver</code> we need to implement both functions:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// StationsManagerObserver</span>
<span class="kd">func</span> <span class="nf">stationsManager</span><span class="p">(</span><span class="n">_</span> <span class="nv">manager</span><span class="p">:</span> <span class="kt">StationsManager</span><span class="p">,</span> <span class="n">stationsDidUpdate</span> <span class="nv">stations</span><span class="p">:</span> <span class="p">[</span><span class="kt">RadioStation</span><span class="p">])</span> <span class="p">{</span>
<span class="c1">// Reload playableContentManager when there is a stations update</span>
<span class="n">playableContentManager</span><span class="p">?</span><span class="o">.</span><span class="nf">reloadData</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// StationsManagerObserver</span>
<span class="kd">func</span> <span class="nf">stationsManager</span><span class="p">(</span><span class="n">_</span> <span class="nv">manager</span><span class="p">:</span> <span class="kt">StationsManager</span><span class="p">,</span> <span class="n">stationDidChange</span> <span class="nv">station</span><span class="p">:</span> <span class="kt">RadioStation</span><span class="p">?)</span> <span class="p">{</span>
<span class="c1">// Check if the new station is not nil and update the `nowPlayingIdentifiers`</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">station</span> <span class="o">=</span> <span class="n">station</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">playableContentManager</span><span class="p">?</span><span class="o">.</span><span class="n">nowPlayingIdentifiers</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="n">playableContentManager</span><span class="p">?</span><span class="o">.</span><span class="n">nowPlayingIdentifiers</span> <span class="o">=</span> <span class="p">[</span><span class="n">station</span><span class="o">.</span><span class="n">name</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div>
<h1 id="run-using-the-ios-simulator">Run using the iOS simulator</h1>
<p>Finally, we need to call our <code class="language-plaintext highlighter-rouge">setup</code> function in the <code class="language-plaintext highlighter-rouge">AppDelegate</code>:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// AppDelegate.swift</span>
<span class="kd">func</span> <span class="nf">application</span><span class="p">(</span><span class="n">_</span> <span class="nv">application</span><span class="p">:</span> <span class="kt">UIApplication</span><span class="p">,</span> <span class="n">didFinishLaunchingWithOptions</span> <span class="nv">launchOptions</span><span class="p">:</span> <span class="p">[</span><span class="kt">UIApplication</span><span class="o">.</span><span class="kt">LaunchOptionsKey</span><span class="p">:</span> <span class="kt">Any</span><span class="p">]?)</span> <span class="o">-></span> <span class="kt">Bool</span> <span class="p">{</span>
<span class="c1">// ...</span>
<span class="nf">setupCarPlay</span><span class="p">()</span>
<span class="k">return</span> <span class="kc">true</span>
<span class="p">}</span>
</code></pre></div></div>
<p>And run our app again, and we’ll be able to see the list in our <strong>CarPlay</strong> display:</p>
<p><img src="/assets/images/20190210/6.carplay.list.jpg" alt="CarPlay List" class="align-center" /></p>
<p><img src="/assets/images/20190210/7.carplay.nowplaying.jpg" alt="CarPlay Now Playing" class="align-center" /></p>
<p class="notice--info">There is a small bug on the simulator causing the pause / play button to show out of sync in the first launch.</p>
<h1 id="testing-on-a-device-with-the-carplay-simulator">Testing on a device with the CarPlay Simulator</h1>
<p>To run the app on an actual device or publish it to the App Store, you need to ask for a <strong>CarPlay</strong> entitlement from Apple using <a href="https://developer.apple.com/contact/carplay/">this form</a>.</p>
<p>Thanks to <a href="https://github.com/urayoanm">@urayoanm</a> for sharing his experience on this Github <a href="https://github.com/analogcode/Swift-Radio-Pro/issues/104#issuecomment-433407949">issue</a>.</p>
<p>Once your request gets approved you will receive an email saying that the <strong>CarPlay</strong> entitlement has been added to your account, and you will be able to generate an explicit provisioning file with the <strong>CarPlay</strong> entitlement for your app.</p>
<p>Once this is done, you can add it to the project, and run the app on your device:</p>
<p><img src="/assets/images/20190210/10.xcode.provisioning.file.jpg" alt="CarPlay provisioning" class="align-center" /></p>
<p>Now, we need to get the CarPlay simulator from Apple’s <a href="https://developer.apple.com/carplay/">developer website</a> by downloading the <a href="https://developer.apple.com/download/all/">Additional Tools for Xcode</a>.</p>
<p>In the <code class="language-plaintext highlighter-rouge">Hardware</code> folder, we can launch the <code class="language-plaintext highlighter-rouge">CarPlay Simulator</code> app, and connect our iPhone, we’ll get a full CarPlay experience on our Mac:</p>
<p><img src="/assets/images/20190210/8.carplay.sim.jpg" alt="CarPlay simulator" class="align-center" /></p>
<p><img src="/assets/images/20190210/9.carplay.sim.nowplaying.jpg" alt="CarPlay simulator now playing" class="align-center" /></p>
<h1 id="end">@end</h1>
<p>All this code has been added to the <strong>SwiftRadio</strong> <a href="https://github.com/analogcode/Swift-Radio-Pro/">project</a> in Xcode <strong>SwiftRadio-CarPlay</strong> target.</p>
<p>More infos about <strong>CarPlay</strong>:</p>
<ul>
<li>
<p><a href="https://developer.apple.com/videos/play/wwdc2018/213/">CarPlay Audio and Navigation Apps - WWDC 2018</a></p>
</li>
<li>
<p><a href="https://developer.apple.com/videos/play/wwdc2017/719/">Enabling Your App for CarPlay - WWDC 2017</a></p>
</li>
<li>
<p><a href="https://developer.apple.com/design/human-interface-guidelines/carplay/overview/audio-apps/">CarPlay - Human Interface Guidelines: Audio Apps</a></p>
</li>
</ul>Fethi El HassasnaHow to add **CarPlay** support to the open source radio app [**SwiftRadio**,](https://github.com/analogcode/Swift-Radio-Pro) and test it using the Simulator.