<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>johnpatrickmorgan</title>
  
  
  <link href="https://johnpatrickmorgan.github.io/atom.xml" rel="self"/>
  
  <link href="https://johnpatrickmorgan.github.io/"/>
  <updated>2021-07-24T22:50:24.399Z</updated>
  <id>https://johnpatrickmorgan.github.io/</id>
  
  <author>
    <name>John Patrick Morgan</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Improving SwiftUI Navigation for the Coordinator Pattern</title>
    <link href="https://johnpatrickmorgan.github.io/2021/07/03/NStack/"/>
    <id>https://johnpatrickmorgan.github.io/2021/07/03/NStack/</id>
    <published>2021-07-03T20:10:39.000Z</published>
    <updated>2021-07-24T22:50:24.399Z</updated>
    
    <content type="html"><![CDATA[<p><strong>In this post, we’ll explore how to manage SwiftUI navigation state with a single array. This will make it much simpler to hoist that state into a high-level view, and reap the benefits of the coordinator pattern.</strong></p><span id="more"></span><p>The <a href="https://khanlou.com/2015/10/coordinators-redux/">coordinator pattern</a> has become very popular in the iOS community. It allows us to write isolated views that have zero knowledge of their context within an app. Navigation state is hoisted out of individual screens into a higher-level <em>coordinator</em> object, and this separation of concerns allows views to be more re-usable and testable.</p><h3 id="The-problem"><a href="#The-problem" class="headerlink" title="The problem"></a>The problem</h3><p>On the other hand, SwiftUI gives us <code>NavigationLink</code>, which encourages the fragmentation of navigation state throughout various views in a navigation flow. <code>NavigationLink</code> also assumes that a particular screen in an app knows about the screens that follow it in the navigation flow: how to create them, when to push them onto the stack, and whether they should be presented or pushed. Further, <code>NavigationLink</code> assumes that any changes to the navigation stack are triggered by a user interaction. In reality, navigation state may need to change as a result of an API call, a deeplink, state restoration or a timer. All of this makes it difficult to implement the coordinator pattern in SwiftUI. </p><h3 id="The-goal"><a href="#The-goal" class="headerlink" title="The goal"></a>The goal</h3><p><strong>It would be ideal if we could manage navigation state in one place, as easily as managing an array - adding and removing views to trigger pushes and pops. Let’s explore what that might look like…</strong></p><p>The first question is what type the array should be. If we want it to accept absolutely any type of view, it would have to be an array of <code>AnyView</code>s, but using <code>AnyView</code> is <a href="https://www.swiftbysundell.com/articles/avoiding-anyview-in-swiftui/">problematic</a>: it impedes SwiftUI’s ability to track what’s changed and make efficient updates. Instead, let’s manage an array of screen identifiers, that we can use to create the corresponding views when we need them, e.g.:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">enum</span> <span class="title">Screen</span> </span>&#123;</span><br><span class="line">    <span class="keyword">case</span> homeView</span><br><span class="line">    <span class="keyword">case</span> numberListView</span><br><span class="line">    <span class="keyword">case</span> numberDetailView(<span class="type">Int</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>With those defined, we can initialize an array of screens in our coordinator with <code>@State var stack: [Screen] = [.homeView]</code>. This array of screens will represent the screens in our navigation stack. Appending a screen will trigger a push, and dropping screens will trigger a pop. </p><p>We are going to need a translation layer to translate any changes we make to this <code>stack</code> array into something SwiftUI will understand - a hierarchy of views and <code>NavigationLink</code>s - and to update our stack whenever the user pops back. Let’s imagine we have a view that handles that translation, and we’ll call it <code>NStack</code>. It will need to accept a binding to the <code>stack</code> and a <code>ViewBuilder</code> closure that takes a <code>Screen</code> and returns the corresponding view. With that in place our coordinator might look like this:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">AppCoordinator</span>: <span class="title">View</span> </span>&#123;</span><br><span class="line">    <span class="meta">@State</span> <span class="keyword">var</span> stack: [<span class="type">Screen</span>] <span class="operator">=</span> [.homeView]</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> &#123;</span><br><span class="line">        <span class="type">NavigationView</span> &#123;</span><br><span class="line">            <span class="type">NStack</span>(stack: <span class="variable">$stack</span>) &#123; screen <span class="keyword">in</span></span><br><span class="line">                <span class="keyword">switch</span> screen &#123;</span><br><span class="line">                <span class="keyword">case</span> .homeView:</span><br><span class="line">                    <span class="type">HomeView</span>(pickNumberTapped: showNumbers)</span><br><span class="line">                <span class="keyword">case</span> .numberListView:</span><br><span class="line">                    <span class="type">NumberListView</span>(numberSelected: showNumber)</span><br><span class="line">                <span class="keyword">case</span> .numberDetailView(<span class="keyword">let</span> number):</span><br><span class="line">                    <span class="type">NumberDetailView</span>(number: number, cancel: pop)</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="function"><span class="keyword">func</span> <span class="title">showNumbers</span>()</span> &#123;</span><br><span class="line">        stack.append(.numberListView)</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="function"><span class="keyword">func</span> <span class="title">showNumber</span>(<span class="keyword">_</span> <span class="params">number</span>: <span class="type">Int</span>)</span> &#123;</span><br><span class="line">        stack.append(.numberDetailView(number))</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="function"><span class="keyword">func</span> <span class="title">pop</span>()</span> &#123;</span><br><span class="line">        stack <span class="operator">=</span> stack.dropLast()</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>With this approach, navigation can be entirely driven by a single piece of state, managed by our coordinator. And each screen’s view can invoke a closure when a particular UI action is taken, requiring no knowledge of other screens in the flow. </p><h3 id="The-implementation"><a href="#The-implementation" class="headerlink" title="The implementation"></a>The implementation</h3><p>Now let’s try to implement it. We have an array of <code>Screen</code>s, but we need to translate that to a SwiftUI navigation stack: where the first view contains a link to the second, and the second view contains a link to the third etc. Described that way, SwiftUI’s navigation stack sounds a little like a <em>Linked List</em>, so let’s try and represent it that way:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">indirect</span> <span class="class"><span class="keyword">enum</span> <span class="title">NavigationNode</span>&lt;<span class="title">ScreenView</span>: <span class="title">View</span>&gt; </span>&#123;</span><br><span class="line">    <span class="keyword">case</span> view(<span class="type">ScreenView</span>, pushing: <span class="type">NavigationNode</span>&lt;<span class="type">ScreenView</span>&gt;)</span><br><span class="line">    <span class="keyword">case</span> end</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Each navigation node is either a view that pushes another node, or the end of our list. The <code>view</code> case contains another <code>NavigationNode</code> as an associated value, so the list of nodes can continue for as long as we need. Because of this recursive definition, we need to mark the enum as <code>indirect</code>. Otherwise, it’s not clear how much memory a <code>NavigationNode</code> should take up, because it can grow to any size.</p><p>Notice that all of the views in the list have the same type <code>ScreenView</code>. That might seem like a problem, since we want to be able to push <em>different</em> screen views. But <code>ScreenView</code> in this case is the result of the <code>ViewBuilder</code> closure passed to the <code>NStack</code>. Since this will typically be the result of a switch statement, <code>ScreenView</code> will be a conditional view type that could be any of the screen types we need to support. So <code>NavigationNode</code> is capable of representing a list of any number of views, each of which is one of the screens we support. This matches exactly what we can describe with our array of <code>Screen</code>s.</p><p>Now that we have our <code>NavigationNode</code>, let’s make it a <code>View</code>. It might seem unusual to make a SwiftUI view from an enum, as we’re used to using structs for that purpose, but an enum can work just as well:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">indirect</span> <span class="class"><span class="keyword">enum</span> <span class="title">NavigationNode</span>&lt;<span class="title">ScreenView</span>: <span class="title">View</span>&gt;: <span class="title">View</span> </span>&#123;</span><br><span class="line">    <span class="keyword">case</span> view(<span class="type">ScreenView</span>, pushing: <span class="type">NavigationNode</span>&lt;<span class="type">ScreenView</span>&gt;)</span><br><span class="line">    <span class="keyword">case</span> end</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">case</span> .view(<span class="keyword">let</span> view, <span class="keyword">let</span> pushedNode) <span class="operator">=</span> <span class="keyword">self</span> &#123;</span><br><span class="line">            view</span><br><span class="line">                .background(</span><br><span class="line">                    <span class="type">NavigationLink</span>(</span><br><span class="line">                        destination: pushedNode, </span><br><span class="line">                        isActive: .constant(<span class="literal">true</span>), <span class="comment">// We&#x27;ll need to revisit this.</span></span><br><span class="line">                        label: <span class="type">EmptyView</span>.<span class="keyword">init</span></span><br><span class="line">                    ).hidden()</span><br><span class="line">                )</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="type">EmptyView</span>()</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>If the node is the <code>end</code>, we return an <code>EmptyView</code>. This should only be seen if our stack is empty. If the node is a view, we return the view, along with a <code>NavigationLink</code> in the background, which pushes the next node. The <code>NavigationLink</code> should be invisible so its label is an <code>EmptyView</code>, and it’s set to <code>hidden()</code>. The <code>isActive</code> binding is a little trickier, so we’ll come back to that.</p><p>Next, let’s see if we can transform our array of screens into a <code>NavigationNode</code> representing the full stack. This is the job of our <code>NStack</code>, which we’ve already decided should take a binding to an array of <code>Screen</code>s and a <code>ViewBuilder</code> closure to build a view for a given screen:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">NStack</span>&lt;<span class="title">Screen</span>, <span class="title">ScreenView</span>: <span class="title">View</span>&gt; </span>&#123;</span><br><span class="line">    <span class="meta">@Binding</span> <span class="keyword">var</span> stack: [<span class="type">Screen</span>]</span><br><span class="line">    <span class="meta">@ViewBuilder</span> <span class="keyword">var</span> buildView: (<span class="type">Screen</span>) -&gt; <span class="type">ScreenView</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Now let’s make this <code>NStack</code> a view. We need to transform the array of screens into a <code>NavigationNode</code>. We’ll do so by starting with a <code>NavigationNode.end</code> and working backwards, with each new node pushing the node created by previous iterations:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">NStack</span>&lt;<span class="title">Screen</span>, <span class="title">ScreenView</span>: <span class="title">View</span>&gt;: <span class="title">View</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Binding</span> <span class="keyword">var</span> stack: [<span class="type">Screen</span>]</span><br><span class="line">    <span class="meta">@ViewBuilder</span> <span class="keyword">var</span> buildView: (<span class="type">Screen</span>) -&gt; <span class="type">ScreenView</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> &#123;</span><br><span class="line">        stack</span><br><span class="line">            .reversed()</span><br><span class="line">            .reduce(<span class="type">NavigationNode</span>&lt;<span class="type">ScreenView</span>&gt;.end) &#123; pushedNode, screen <span class="keyword">in</span></span><br><span class="line">                <span class="keyword">return</span> <span class="type">NavigationNode</span>&lt;<span class="type">ScreenView</span>&gt;.view(</span><br><span class="line">                    buildView(screen),</span><br><span class="line">                    pushing: pushedNode</span><br><span class="line">                )</span><br><span class="line">            &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Great. Now it’s time to turn our attention back to the <code>NavigationNode</code> and the <code>isActive</code> binding on its <code>NavigationLink</code>. This binding will be responsible for deciding if the next view should be pushed, and for updating the stack when the pushed view is popped back (e.g. if the user taps or swipes back). We’ll need a couple of extra parameters in order to do that, so we’ll amend the code above to additionally pass the stack binding and the node’s index when we create one:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">NStack</span>&lt;<span class="title">Screen</span>, <span class="title">ScreenView</span>: <span class="title">View</span>&gt;: <span class="title">View</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Binding</span> <span class="keyword">var</span> stack: [<span class="type">Screen</span>]</span><br><span class="line">    <span class="meta">@ViewBuilder</span> <span class="keyword">var</span> buildView: (<span class="type">Screen</span>) -&gt; <span class="type">ScreenView</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> &#123;</span><br><span class="line">        stack</span><br><span class="line">            .enumerated()</span><br><span class="line">            .reversed()</span><br><span class="line">            .reduce(<span class="type">NavigationNode</span>&lt;<span class="type">Screen</span>, <span class="type">ScreenView</span>&gt;.end) &#123; pushedNode, new <span class="keyword">in</span></span><br><span class="line">                <span class="keyword">let</span> (index, screen) <span class="operator">=</span> new</span><br><span class="line">                <span class="keyword">return</span> <span class="type">NavigationNode</span>&lt;<span class="type">Screen</span>, <span class="type">ScreenView</span>&gt;.view(</span><br><span class="line">                    buildView(screen),</span><br><span class="line">                    pushing: pushedNode,</span><br><span class="line">                    stack: <span class="variable">$stack</span>,</span><br><span class="line">                    index: index</span><br><span class="line">                )</span><br><span class="line">            &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Now that the node has those values, we can use them to create the <code>isActive</code> binding for the <code>NavigationLink</code>. In the getter, we want to check that the stack’s count is higher than our index - if so, the view is being pushed, so we can return <code>true</code>. We also need to check that the <code>pushedNode</code> is not an <code>end</code> node, as we never want to push one of those. The setter is important, as that’s how we’ll be notified that the user has tapped the back button or swiped to go back. In the setter, we want to check if the new value is false. If so, then the user is navigating back. At this point we can truncate the stack so that the pushing view is at the top of the stack:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Binding</span>(</span><br><span class="line">   get: &#123;</span><br><span class="line">       <span class="keyword">if</span> <span class="keyword">case</span> .end <span class="operator">=</span> pushedNode &#123;</span><br><span class="line">           <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">       &#125;</span><br><span class="line">       <span class="keyword">return</span> stack.wrappedValue.count <span class="operator">&gt;</span> index <span class="operator">+</span> <span class="number">1</span></span><br><span class="line">   &#125;,</span><br><span class="line">   set: &#123; isPushed <span class="keyword">in</span></span><br><span class="line">       <span class="keyword">guard</span> <span class="operator">!</span>isPushed <span class="keyword">else</span> &#123; <span class="keyword">return</span> &#125;</span><br><span class="line">       stack.wrappedValue <span class="operator">=</span> <span class="type">Array</span>(stack.wrappedValue.prefix(index <span class="operator">+</span> <span class="number">1</span>))</span><br><span class="line">   &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>That completes the translation layer between our desired API and what SwiftUI gives us: the coordinator code above now works as intended. All in all, it’s less than 50 lines of code. Here’s the implementation in full:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">NStack</span>&lt;<span class="title">Screen</span>, <span class="title">ScreenView</span>: <span class="title">View</span>&gt;: <span class="title">View</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Binding</span> <span class="keyword">var</span> stack: [<span class="type">Screen</span>]</span><br><span class="line">    <span class="meta">@ViewBuilder</span> <span class="keyword">var</span> buildView: (<span class="type">Screen</span>) -&gt; <span class="type">ScreenView</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> &#123;</span><br><span class="line">        stack</span><br><span class="line">            .enumerated()</span><br><span class="line">            .reversed()</span><br><span class="line">            .reduce(<span class="type">NavigationNode</span>&lt;<span class="type">Screen</span>, <span class="type">ScreenView</span>&gt;.end) &#123; pushedNode, new <span class="keyword">in</span></span><br><span class="line">                <span class="keyword">let</span> (index, screen) <span class="operator">=</span> new</span><br><span class="line">                <span class="keyword">return</span> <span class="type">NavigationNode</span>&lt;<span class="type">Screen</span>, <span class="type">ScreenView</span>&gt;.view(</span><br><span class="line">                    buildView(screen),</span><br><span class="line">                    pushing: pushedNode,</span><br><span class="line">                    stack: <span class="variable">$stack</span>,</span><br><span class="line">                    index: index</span><br><span class="line">                )</span><br><span class="line">            &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">indirect</span> <span class="class"><span class="keyword">enum</span> <span class="title">NavigationNode</span>&lt;<span class="title">Screen</span>, <span class="title">ScreenView</span>: <span class="title">View</span>&gt;: <span class="title">View</span> </span>&#123;</span><br><span class="line">    <span class="keyword">case</span> view(<span class="type">ScreenView</span>, pushing: <span class="type">NavigationNode</span>&lt;<span class="type">Screen</span>, <span class="type">ScreenView</span>&gt;, stack: <span class="type">Binding</span>&lt;[<span class="type">Screen</span>]&gt;, index: <span class="type">Int</span>)</span><br><span class="line">    <span class="keyword">case</span> end</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">case</span> .view(<span class="keyword">let</span> view, <span class="keyword">let</span> pushedNode, <span class="keyword">let</span> stack, <span class="keyword">let</span> index) <span class="operator">=</span> <span class="keyword">self</span> &#123;</span><br><span class="line">            view.background(</span><br><span class="line">                <span class="type">NavigationLink</span>(</span><br><span class="line">                    destination: pushedNode,</span><br><span class="line">                    isActive: <span class="type">Binding</span>(</span><br><span class="line">                        get: &#123;</span><br><span class="line">                            <span class="keyword">if</span> <span class="keyword">case</span> .end <span class="operator">=</span> pushedNode &#123;</span><br><span class="line">                                <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">                            &#125;</span><br><span class="line">                            <span class="keyword">return</span> stack.wrappedValue.count <span class="operator">&gt;</span> index <span class="operator">+</span> <span class="number">1</span></span><br><span class="line">                        &#125;,</span><br><span class="line">                        set: &#123; isPushed <span class="keyword">in</span></span><br><span class="line">                            <span class="keyword">guard</span> <span class="operator">!</span>isPushed <span class="keyword">else</span> &#123; <span class="keyword">return</span> &#125;</span><br><span class="line">                            stack.wrappedValue <span class="operator">=</span> <span class="type">Array</span>(stack.wrappedValue.prefix(index <span class="operator">+</span> <span class="number">1</span>))</span><br><span class="line">                        &#125;),</span><br><span class="line">                    label: <span class="type">EmptyView</span>.<span class="keyword">init</span></span><br><span class="line">                ).hidden()</span><br><span class="line">            )</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="type">EmptyView</span>()</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Evaluating-the-solution"><a href="#Evaluating-the-solution" class="headerlink" title="Evaluating the solution"></a>Evaluating the solution</h3><p>We can now manage navigation with a single piece of state, rather than various pieces of state distributed among our views. Pushing new screens is as simple as <code>stack.append(.newScreen)</code>, and if the user taps or swipes back, or uses the long press gesture to go further back, the navigation state will automatically get updated to reflect the change. Programmatically popping is as simple as <code>stack = stack.dropLast()</code>, and you can easily pop back to the root or to a specific screen, because the navigation stack can be examined at runtime.</p><p>The screen views themselves no longer need to have any knowledge of any other screens in the navigation flow - they can simply invoke a closure, e.g. with a <code>Button</code>, and leave the coordinator to decide what view, if any, should be pushed or presented.</p><p>Not only does this make the coordinator pattern more at home in SwiftUI, it even has some advantages over the coordinator pattern in UIKit. With this approach, coordinators are just views, which means they can be composed and configured in all the normal ways views can. You can present a coordinator, add it to a <code>TabView</code>, or even push a child coordinator onto the navigation stack of a parent coordinator, just as you would a view. Note that <code>NStack</code> does not wrap its content in a <code>NavigationView</code> - that way, multiple coordinators can be nested within a single <code>NavigationView</code>. </p><p>In UIKit, child coordinators are a useful pattern, but some extra work is often required: e.g. to keep a strong reference to the child coordinator, pass it a <code>UINavigationController</code>, tell it to <code>start()</code> or to listen for the user tapping back. None of that extra work is necessary in the SwiftUI version. The one caveat is that the child coordinator should only ever be the top view in its parent’s stack - as the parent passes navigation responsibilities to the child when it is pushed. </p><p><code>NStack</code> is available as part of <code>FlowStacks</code> on <a href="https://github.com/johnpatrickmorgan/FlowStacks">GitHub</a>. The library also includes a <code>PStack</code> object for presenting and dismissing views in the same way. It also provides ‘flows’ (<code>NFlow</code> and <code>PFlow</code>), each of which is a thin wrapper around an array, adding some convenience methods, e.g. for pushing views, popping to the root view or popping to a particular screen. You can see it in action, along with an example using view models, in the <a href="https://github.com/johnpatrickmorgan/FlowStacks#using-view-models">README</a>. </p><p>I’d love to hear your thoughts on whether this approach is useful and if it has any downsides I haven’t considered.</p><p>&nbsp;</p><div class="alert info"><p>At the moment, SwiftUI does not support increasing the navigation stack by more than one in a single update. I’ve opened <code>FB9200490</code> in the hope that this will be resolved.</p></div><p>&nbsp;</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;strong&gt;In this post, we’ll explore how to manage SwiftUI navigation state with a single array. This will make it much simpler to hoist that state into a high-level view, and reap the benefits of the coordinator pattern.&lt;/strong&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="Swift" scheme="https://johnpatrickmorgan.github.io/categories/Swift/"/>
    
    
    <category term="Swift" scheme="https://johnpatrickmorgan.github.io/tags/Swift/"/>
    
    <category term="SwiftUI" scheme="https://johnpatrickmorgan.github.io/tags/SwiftUI/"/>
    
    <category term="Navigation" scheme="https://johnpatrickmorgan.github.io/tags/Navigation/"/>
    
    <category term="Coordinator" scheme="https://johnpatrickmorgan.github.io/tags/Coordinator/"/>
    
  </entry>
  
  <entry>
    <title>URL Pattern Matching</title>
    <link href="https://johnpatrickmorgan.github.io/2016/05/11/URLPatternMatching/"/>
    <id>https://johnpatrickmorgan.github.io/2016/05/11/URLPatternMatching/</id>
    <published>2016-05-11T20:10:39.000Z</published>
    <updated>2021-07-05T20:48:40.755Z</updated>
    
    <content type="html"><![CDATA[<p><strong>When implementing deeplinks for an iOS app recently, I wondered if Swift’s pattern matching could make a viable alternative to a URL routing library. This is an account of the different approaches I tried, and the one I finally settled on.</strong></p><span id="more"></span><p>The majority of URL routing libraries use a pattern syntax that can match each element of a URL’s path in one of three ways:<br>&nbsp;</p><ul><li><strong>Equality</strong>: Path element must match the pattern expression</li><li><strong>Value-binding</strong>: Path element can be anything at all, and will be made available in a parameters dictionary</li><li><strong>Wildcard</strong>: Path element can be anything at all, and will be discarded</li></ul><p>For example, it might look something like this:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Router</span>.register(<span class="string">&quot;user/:userId/*&quot;</span>) &#123; parameters <span class="keyword">in</span></span><br><span class="line">   <span class="keyword">let</span> userId <span class="operator">=</span> parameters[<span class="string">&quot;userId&quot;</span>]<span class="operator">!</span></span><br><span class="line">   <span class="comment">// Show user&#x27;s profile...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>This would match a URL such as <code>scheme://host/user/john_morgan/profilev2</code>, invoking the closure with a <code>userId</code> of ‘john_morgan’. There are a few reasons I don’t much like this approach:<br>&nbsp;</p><ul><li>The pattern matching is abstracted away using special syntax.</li><li>The parameter name <code>userId</code> is repeated and <em>stringly</em> typed, so it’s susceptible to typos.</li><li><code>parameters[&quot;userId&quot;]</code> should never be nil, but the compiler doesn’t know that, so we must force unwrap or add a <code>guard</code> statement.</li></ul><p>As it happens Swift’s built-in pattern matching can be used for each of the three pattern types. Here’s an example of all three:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> example <span class="operator">=</span> (<span class="string">&quot;expression-pattern&quot;</span>, <span class="string">&quot;value-binding-pattern&quot;</span>, <span class="string">&quot;wildcard-pattern&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> <span class="keyword">case</span> <span class="keyword">let</span> (<span class="string">&quot;expression-pattern&quot;</span>, value, <span class="keyword">_</span>) <span class="operator">=</span> example &#123;</span><br><span class="line">   <span class="comment">// use value</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>In fact, the expression pattern is more powerful than a simple equality test, as we can define our own matching logic using the pattern matching operator (more of which later). The wildcard pattern is really a special case of the value-binding pattern, so I will refer to them collectively as <strong>value-binding</strong> patterns from here on in.</p><p>Swift’s pattern-matching would seem a natural fit for matching URLs, and Swift’s switch statement would suit the purpose too, so I decided to investigate a URL routing approach based on the two.</p><p>NSURL exposes a <code>pathComponents</code> as an <code>Array&lt;String&gt;</code>, e.g., <code>https://myhost.com/user/14253/profilev2</code> would give <code>[&quot;/&quot;, &quot;user&quot;, &quot;14253&quot;, &quot;profilev2&quot;]</code>. Let’s assume we remove the initial backslash and call the resulting array <code>pathElements</code>. In pseudo-Swift, I’d like to be able to switch on the array a bit like this:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">switch</span> pathElements &#123;</span><br><span class="line"><span class="keyword">case</span> [<span class="string">&quot;user&quot;</span>, <span class="keyword">let</span> userId, <span class="keyword">_</span>]:</span><br><span class="line">    <span class="comment">// Go to profile for userId</span></span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">    <span class="keyword">break</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>However, there is no built-in pattern matching for Arrays and their elements in Swift, so we need to add it somehow…</p><h2 id="Approach-1-Pattern-Matching-Operator"><a href="#Approach-1-Pattern-Matching-Operator" class="headerlink" title="Approach 1: Pattern Matching Operator"></a>Approach 1: Pattern Matching Operator</h2><p>My first thought was to use the pattern-matching operator (<code>~=</code>) to match Arrays of equatable elements based on all elements being equal:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">~=&lt;</span></span><span class="type">T</span>: <span class="type">Equatable</span><span class="operator">&gt;</span>(pattern: [<span class="type">T</span>], value: [<span class="type">T</span>]) -&gt; <span class="type">Bool</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> pattern <span class="operator">==</span> value</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>This would allow us to match simple patterns in a switch statement:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">switch</span> pathElements &#123;</span><br><span class="line"><span class="keyword">case</span> [<span class="string">&quot;lobby&quot;</span>, <span class="string">&quot;main&quot;</span>]:</span><br><span class="line">    <span class="comment">// Go to lobby</span></span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">    <span class="keyword">break</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>However, the pattern-matching operator can only be used for <strong>expression</strong> patterns. It cannot be used for adding custom <strong>value-binding</strong> patterns, so this is a dead end. We need to convert the array into another type that already supports <strong>value-binding</strong> patterns for its elements.</p><h2 id="Approach-2-Tuples"><a href="#Approach-2-Tuples" class="headerlink" title="Approach 2: Tuples"></a>Approach 2: Tuples</h2><p>This led me to think about tuples, as tuples support <strong>value-binding</strong> patterns for their elements. To convert the <code>pathElements</code> array into a tuple with the same number of elements, perhaps a <code>decompose()</code> method could be overloaded for element counts up to some sensible limit:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">extension</span> <span class="title">Array</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">func</span> <span class="title">decompose</span>()</span> -&gt; <span class="type">Element</span>? &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">guard</span> count <span class="operator">==</span> <span class="number">1</span> <span class="keyword">else</span> &#123; <span class="keyword">return</span> <span class="literal">nil</span> &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">self</span>[<span class="number">0</span>]</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">func</span> <span class="title">decompose</span>()</span> -&gt; (<span class="type">Element</span>, <span class="type">Element</span>)<span class="operator">?</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">guard</span> count <span class="operator">==</span> <span class="number">2</span> <span class="keyword">else</span> &#123; <span class="keyword">return</span> <span class="literal">nil</span> &#125;</span><br><span class="line">        <span class="keyword">return</span> (<span class="keyword">self</span>[<span class="number">0</span>], <span class="keyword">self</span>[<span class="number">1</span>])</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">func</span> <span class="title">decompose</span>()</span> -&gt; (<span class="type">Element</span>, <span class="type">Element</span>, <span class="type">Element</span>)<span class="operator">?</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">guard</span> count <span class="operator">==</span> <span class="number">3</span> <span class="keyword">else</span> &#123; <span class="keyword">return</span> <span class="literal">nil</span> &#125;</span><br><span class="line">        <span class="keyword">return</span> (<span class="keyword">self</span>[<span class="number">0</span>], <span class="keyword">self</span>[<span class="number">1</span>], <span class="keyword">self</span>[<span class="number">2</span>])</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>This would enable pattern-matching like so:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> <span class="keyword">case</span> (<span class="string">&quot;user&quot;</span>, <span class="keyword">let</span> userId, <span class="keyword">_</span>)<span class="operator">?</span>: (<span class="type">String</span>, <span class="type">String</span>, <span class="type">String</span>)<span class="operator">?</span> <span class="operator">=</span> pathElements.decompose() &#123;</span><br><span class="line">    <span class="comment">// Open profile for userId</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Unfortunately the compiler can’t infer which <code>decompose()</code> method to invoke, which necessitates the explicit typing after the colon above. Abandoning the overloaded <code>decompose()</code> in favour of unique method names <code>decompose1()</code>, <code>decompose2()</code>, <code>decompose3()</code> etc. helps to clean things up:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> <span class="keyword">case</span> (<span class="string">&quot;user&quot;</span>, <span class="keyword">let</span> userId, <span class="keyword">_</span>)<span class="operator">?</span> <span class="operator">=</span> pathElements.decompose3() &#123;</span><br><span class="line">    <span class="comment">// Open profile for userId</span></span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> <span class="keyword">case</span> (<span class="string">&quot;lobby&quot;</span>, <span class="string">&quot;home&quot;</span>)<span class="operator">?</span> <span class="operator">=</span> pathElements.decompose2() &#123;</span><br><span class="line">    <span class="comment">// Open lobby</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>However, this serves to highlight a limitation: we can’t use this approach to match multiple patterns within a single switch statement, unless those patterns happen to have the same element count. In the example above, what would we switch on - <code>decompose2()</code> or <code>decompose3()</code>? Instead, we need a structure that can represent different element counts within the same type…</p><h2 id="Approach-3-Linked-List"><a href="#Approach-3-Linked-List" class="headerlink" title="Approach 3: Linked List"></a>Approach 3: Linked List</h2><p>This led me to try using an enum type, as enums also support <strong>value-binding</strong> patterns for their associated values. A linked list (here’s a nice <a href="https://airspeedvelocity.net/2015/07/26/linked-lists-enums-value-types-and-identity/">implementation by Airspeed Velocity</a>) seemed promising because it’s built out of enums and can represent an arbitrary number of elements. Here’s what it would look like:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> <span class="keyword">case</span> .<span class="type">Node</span>(<span class="string">&quot;user&quot;</span>, .<span class="type">Node</span>(<span class="keyword">let</span> userId, .<span class="type">Node</span>(<span class="keyword">_</span>, .<span class="type">End</span>))) <span class="operator">=</span> <span class="type">List</span>(pathElements) &#123;</span><br><span class="line">    <span class="comment">// Open profile for userId</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Unlike the previous approach, it can also be used to pattern-match lists of any size within a single switch statement:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">switch</span> <span class="type">List</span>(pathElements) &#123;</span><br><span class="line"></span><br><span class="line"><span class="keyword">case</span> .<span class="type">End</span>, .<span class="type">Node</span>(<span class="string">&quot;&quot;</span>, .<span class="type">End</span>):</span><br><span class="line">    <span class="comment">// Open home</span></span><br><span class="line"><span class="keyword">case</span> .<span class="type">Node</span>(<span class="string">&quot;lobby&quot;</span>, .<span class="type">Node</span>(<span class="string">&quot;main&quot;</span>, .<span class="type">End</span>)):</span><br><span class="line">    <span class="comment">// Open lobby</span></span><br><span class="line"><span class="keyword">case</span> .<span class="type">Node</span>(<span class="string">&quot;user&quot;</span>, .<span class="type">Node</span>(<span class="keyword">let</span> userId, .<span class="type">Node</span>(<span class="keyword">_</span>, .<span class="type">End</span>))):</span><br><span class="line">    <span class="comment">// Open profile for userId</span></span><br><span class="line"><span class="keyword">case</span> .<span class="type">Node</span>(<span class="string">&quot;user&quot;</span>, .<span class="type">Node</span>(<span class="keyword">_</span>, .<span class="type">Node</span>(<span class="string">&quot;login&quot;</span>, .<span class="type">End</span>))):</span><br><span class="line">    <span class="comment">// Open login</span></span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">    <span class="keyword">break</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The trouble is, well, it’s ugly. All those parentheses and repeated <code>.Node</code>s make it very difficult to read. <code>.Node</code> could be shortened to a single character but nesting multiple enums still generates a confusing amount of parentheses.</p><h2 id="Approach-4-Counted"><a href="#Approach-4-Counted" class="headerlink" title="Approach 4: Counted"></a>Approach 4: Counted</h2><p>My final approach was a compromise between approaches 2 and 3. What was needed was an enum that could represent arbitrary numbers of elements without needing too many layers of nesting. Enter <code>Counted</code>:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">enum</span> <span class="title">Counted</span>&lt;<span class="title">E</span>&gt; </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">case</span> <span class="type">N0</span></span><br><span class="line">    <span class="keyword">case</span> <span class="type">N1</span>(<span class="type">E</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="type">N2</span>(<span class="type">E</span>, <span class="type">E</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="type">N3</span>(<span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="type">N4</span>(<span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="type">N5</span>(<span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="type">N6</span>(<span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="type">N7</span>(<span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="type">N8</span>(<span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="type">N9</span>(<span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">indirect</span> <span class="keyword">case</span> <span class="type">N10</span>(<span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, <span class="type">E</span>, plus: <span class="type">Counted</span>&lt;<span class="type">E</span>&gt;)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">init</span>(<span class="keyword">_</span> <span class="params">elements</span>: [<span class="type">E</span>])</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">let</span> e <span class="operator">=</span> elements</span><br><span class="line"></span><br><span class="line">        <span class="keyword">switch</span> e.count &#123;</span><br><span class="line">        <span class="keyword">case</span> <span class="number">0</span>:</span><br><span class="line">            <span class="keyword">self</span> <span class="operator">=</span> .<span class="type">N0</span></span><br><span class="line">        <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">            <span class="keyword">self</span> <span class="operator">=</span> .<span class="type">N1</span>(e[<span class="number">0</span>])</span><br><span class="line">        <span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line">            <span class="keyword">self</span> <span class="operator">=</span> .<span class="type">N2</span>(e[<span class="number">0</span>], e[<span class="number">1</span>])</span><br><span class="line">        <span class="keyword">case</span> <span class="number">3</span>:</span><br><span class="line">            <span class="keyword">self</span> <span class="operator">=</span> .<span class="type">N3</span>(e[<span class="number">0</span>], e[<span class="number">1</span>], e[<span class="number">2</span>])</span><br><span class="line">        <span class="keyword">case</span> <span class="number">4</span>:</span><br><span class="line">            <span class="keyword">self</span> <span class="operator">=</span> .<span class="type">N4</span>(e[<span class="number">0</span>], e[<span class="number">1</span>], e[<span class="number">2</span>], e[<span class="number">3</span>])</span><br><span class="line">        <span class="keyword">case</span> <span class="number">5</span>:</span><br><span class="line">            <span class="keyword">self</span> <span class="operator">=</span> .<span class="type">N5</span>(e[<span class="number">0</span>], e[<span class="number">1</span>], e[<span class="number">2</span>], e[<span class="number">3</span>], e[<span class="number">4</span>])</span><br><span class="line">        <span class="keyword">case</span> <span class="number">6</span>:</span><br><span class="line">            <span class="keyword">self</span> <span class="operator">=</span> .<span class="type">N6</span>(e[<span class="number">0</span>], e[<span class="number">1</span>], e[<span class="number">2</span>], e[<span class="number">3</span>], e[<span class="number">4</span>], e[<span class="number">5</span>])</span><br><span class="line">        <span class="keyword">case</span> <span class="number">7</span>:</span><br><span class="line">            <span class="keyword">self</span> <span class="operator">=</span> .<span class="type">N7</span>(e[<span class="number">0</span>], e[<span class="number">1</span>], e[<span class="number">2</span>], e[<span class="number">3</span>], e[<span class="number">4</span>], e[<span class="number">5</span>], e[<span class="number">6</span>])</span><br><span class="line">        <span class="keyword">case</span> <span class="number">8</span>:</span><br><span class="line">            <span class="keyword">self</span> <span class="operator">=</span> .<span class="type">N8</span>(e[<span class="number">0</span>], e[<span class="number">1</span>], e[<span class="number">2</span>], e[<span class="number">3</span>], e[<span class="number">4</span>], e[<span class="number">5</span>], e[<span class="number">6</span>], e[<span class="number">7</span>])</span><br><span class="line">        <span class="keyword">case</span> <span class="number">9</span>:</span><br><span class="line">            <span class="keyword">self</span> <span class="operator">=</span> .<span class="type">N9</span>(e[<span class="number">0</span>], e[<span class="number">1</span>], e[<span class="number">2</span>], e[<span class="number">3</span>], e[<span class="number">4</span>], e[<span class="number">5</span>], e[<span class="number">6</span>], e[<span class="number">7</span>], e[<span class="number">8</span>])</span><br><span class="line">        <span class="keyword">default</span>:</span><br><span class="line">            <span class="keyword">self</span> <span class="operator">=</span> .<span class="type">N10</span>(e[<span class="number">0</span>], e[<span class="number">1</span>], e[<span class="number">2</span>], e[<span class="number">3</span>], e[<span class="number">4</span>], e[<span class="number">5</span>], e[<span class="number">6</span>], e[<span class="number">7</span>], e[<span class="number">8</span>], e[<span class="number">9</span>], plus: <span class="type">Counted</span>(<span class="type">Array</span>(e[<span class="number">10</span><span class="operator">..&lt;</span>elements.endIndex])))</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Counted is an enum where each case has a different number of elements as associated values. It can be initialized with an Array, and just like <code>List</code>, there’s an indirect case that enables arbitrarily large arrays to be represented via nested <code>Counted</code> enums. Unlike <code>List</code>, a layer of nesting is only required for every 10 elements, which makes things easier to read. <code>Counted</code> enables us to pattern-match paths with any number of elements, and supports <strong>expression</strong> and <strong>value-binding</strong> patterns for its associated values. It can also be used in switch statements:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">switch</span> <span class="type">Counted</span>(pathElements) &#123;</span><br><span class="line"><span class="keyword">case</span> .<span class="type">N0</span>, .<span class="type">N1</span>(<span class="string">&quot;&quot;</span>):</span><br><span class="line">    <span class="comment">// Open home</span></span><br><span class="line"><span class="keyword">case</span> .<span class="type">N2</span>(<span class="string">&quot;lobby&quot;</span>, <span class="string">&quot;main&quot;</span>):</span><br><span class="line">    <span class="comment">// Open lobby</span></span><br><span class="line"><span class="keyword">case</span> .<span class="type">N3</span>(<span class="string">&quot;user&quot;</span>, <span class="keyword">let</span> userId, <span class="string">&quot;profile&quot;</span>):</span><br><span class="line">    <span class="comment">// Open profile for userId</span></span><br><span class="line"><span class="keyword">case</span> .<span class="type">N3</span>(<span class="string">&quot;user&quot;</span>, <span class="keyword">_</span>, <span class="string">&quot;login&quot;</span>):</span><br><span class="line">    <span class="comment">// Open login</span></span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">    <span class="keyword">break</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>This can be extended for even greater flexibility. I mentioned that the <strong>expression</strong> pattern can be used to match based on more than simple equality. For example, I created a <code>Regex</code> struct that can match Strings based on a regular expression, and implemented the pattern-matching operator like so:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">func</span> <span class="title">~=</span>(<span class="params">regex</span>: <span class="type">Regex</span>, <span class="params">string</span>: <span class="type">String</span>)</span> -&gt; <span class="type">Bool</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> regex.matches(string)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>As a result we can use <code>Regex</code> to match individual path elements within <code>Counted</code>. For example, the following case would match both <code>/pages/contact-us_gbr</code> and <code>/pages/contact-us_usa</code>:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">case</span> .<span class="type">N2</span>(<span class="string">&quot;pages&quot;</span>, <span class="type">Regex</span>(<span class="string">&quot;contact_.*&quot;</span>)):</span><br><span class="line">    <span class="comment">// Open contact page</span></span><br></pre></td></tr></table></figure><p>I added structs <code>Begins(...)</code> and <code>Ends(...)</code>, which use the <a href="https://github.com/johnpatrickmorgan/URLPatterns/blob/master/URLPatterns/Classes/CountedExpressionMatching.swift">pattern-matching operator</a> to match <code>Counted</code> instances based purely on a slice of the path elements. I also added extensions to <code>NSURL</code> and <code>NSURLComponents</code> to make a <code>Counted</code> list of path elements and a <code>Dictionary</code> of query arguments easily available. The code is available here: <a href="https://github.com/johnpatrickmorgan/URLPatterns">URLPatterns</a>.</p><h2 id="Deep-linking"><a href="#Deep-linking" class="headerlink" title="Deep-linking"></a>Deep-linking</h2><p>Now that I can do more idiomatic Swift pattern-matching for URL path elements, here’s how I use it for deep-linking. I define my app’s deep-link destinations as an enum:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">enum</span> <span class="title">DeepLink</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">case</span> <span class="type">Home</span>, <span class="type">History</span>, <span class="type">Settings</span>, <span class="type">Terms</span>, <span class="type">News</span>, <span class="type">Contact</span></span><br><span class="line">    <span class="keyword">case</span> <span class="type">Chat</span>(room: <span class="type">String</span>)</span><br><span class="line">    <span class="keyword">case</span> <span class="type">Profile</span>(userId: <span class="type">String</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>I then add a failable initializer to <code>DeepLink</code>, which takes an <code>NSURL</code>. This is where the pattern-matching happens:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">extension</span> <span class="title">DeepLink</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">init?</span>(<span class="params">url</span>: <span class="type">NSURL</span>)</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">guard</span> url.scheme <span class="operator">==</span> <span class="string">&quot;myscheme&quot;</span> <span class="keyword">else</span> &#123; <span class="keyword">return</span> <span class="literal">nil</span> &#125;</span><br><span class="line">        <span class="keyword">guard</span> url.host <span class="operator">==</span> <span class="string">&quot;myhost&quot;</span> <span class="keyword">else</span> &#123; <span class="keyword">return</span> <span class="literal">nil</span> &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">switch</span> url.countedPathComponents() &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">case</span> .<span class="type">N0</span>, .<span class="type">N1</span>(<span class="string">&quot;&quot;</span>):                          <span class="keyword">self</span> <span class="operator">=</span> .<span class="type">Home</span></span><br><span class="line">        <span class="keyword">case</span> .<span class="type">N1</span>(<span class="string">&quot;history&quot;</span>):                        <span class="keyword">self</span> <span class="operator">=</span> .<span class="type">History</span></span><br><span class="line">        <span class="keyword">case</span> .<span class="type">N2</span>(<span class="keyword">_</span>, <span class="string">&quot;settings&quot;</span>):                    <span class="keyword">self</span> <span class="operator">=</span> .<span class="type">Settings</span></span><br><span class="line">        <span class="keyword">case</span> .<span class="type">N2</span>(<span class="string">&quot;chat&quot;</span>, <span class="keyword">let</span> room):                 <span class="keyword">self</span> <span class="operator">=</span> .<span class="type">Chat</span>(room: room)</span><br><span class="line">        <span class="keyword">case</span> .<span class="type">N3</span>(<span class="string">&quot;users&quot;</span>, <span class="keyword">let</span> userId, <span class="string">&quot;profile&quot;</span>):   <span class="keyword">self</span> <span class="operator">=</span> .<span class="type">Profile</span>(userId: userId)</span><br><span class="line">        <span class="keyword">case</span> <span class="type">Begins</span>(<span class="string">&quot;news&quot;</span>, <span class="string">&quot;latest&quot;</span>):              <span class="keyword">self</span> <span class="operator">=</span> .<span class="type">News</span></span><br><span class="line">        <span class="keyword">case</span> <span class="type">Ends</span>(<span class="string">&quot;terms&quot;</span>):                         <span class="keyword">self</span> <span class="operator">=</span> .<span class="type">Terms</span></span><br><span class="line">        <span class="keyword">case</span> .<span class="type">N2</span>(<span class="string">&quot;pages&quot;</span>, <span class="type">Regex</span>(<span class="string">&quot;contact-us.*&quot;</span>))    <span class="keyword">self</span> <span class="operator">=</span> .<span class="type">Contact</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">default</span>:                                    <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Once the URL has been converted into a <code>DeepLink</code>, it can be passed to a <code>DeepLinker</code> for routing:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">DeepLinker</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="function"><span class="keyword">func</span> <span class="title">open</span>(<span class="params">link</span>: <span class="type">DeepLink</span>, <span class="params">animated</span>: <span class="type">Bool</span> <span class="operator">=</span> <span class="literal">true</span>)</span> -&gt; <span class="type">Bool</span> &#123;</span><br><span class="line">         <span class="comment">// switch on link, selecting tabs, pushing and presenting view controllers as appropriate</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>With that set up, opening a deeplink looks like this:</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> <span class="keyword">let</span> link <span class="operator">=</span> <span class="type">DeepLink</span>(url: url) &#123;</span><br><span class="line">    <span class="type">DeepLinker</span>.open(link)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>I prefer this approach to the approach taken by most URL routing libraries for a few reasons:</p><ul><li><p>It’s simple to bypass URLs and open a deeplink directly, e.g. by calling <code>DeepLinker.open(.Home)</code>.</p></li><li><p>The pattern-matching code is no longer in a third-party library, which makes it easier to debug.</p></li><li><p>The pattern-matching code leverages Swift’s built-in pattern-matching, which means it can be customized and extended.</p></li><li><p>The pattern-matching and routing processes are separated into two steps. This provides an override point if needed, e.g.:</p></li></ul><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> <span class="keyword">let</span> link <span class="operator">=</span> <span class="type">DeepLink</span>(url: url) <span class="keyword">where</span> <span class="operator">!</span>link.requiresLogin &#123;</span><br><span class="line">    <span class="type">DeepLinker</span>.open(link)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>What do you think? Do you like the ‘swiftier’ approach (damn, I nearly managed to avoid that word), or am I misrepresenting URL routing libraries?  </p>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;strong&gt;When implementing deeplinks for an iOS app recently, I wondered if Swift’s pattern matching could make a viable alternative to a URL routing library. This is an account of the different approaches I tried, and the one I finally settled on.&lt;/strong&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="Swift" scheme="https://johnpatrickmorgan.github.io/categories/Swift/"/>
    
    
    <category term="Swift" scheme="https://johnpatrickmorgan.github.io/tags/Swift/"/>
    
    <category term="Pattern Matching" scheme="https://johnpatrickmorgan.github.io/tags/Pattern-Matching/"/>
    
    <category term="URL Routing" scheme="https://johnpatrickmorgan.github.io/tags/URL-Routing/"/>
    
  </entry>
  
</feed>
