<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://jp4mobile.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://jp4mobile.com/" rel="alternate" type="text/html" /><updated>2025-07-07T21:13:09+00:00</updated><id>https://jp4mobile.com/feed.xml</id><title type="html">Jp4Mobile</title><subtitle>Jp4Mobile is mainly about iOS, Swift, and SwiftUI.</subtitle><author><name>Jp</name></author><entry><title type="html">Testing the Different Architectures for SwiftUI Projects</title><link href="https://jp4mobile.com/coding/2025/07/07/ArchitectureTesting.html" rel="alternate" type="text/html" title="Testing the Different Architectures for SwiftUI Projects" /><published>2025-07-07T14:00:01+00:00</published><updated>2025-07-07T14:00:01+00:00</updated><id>https://jp4mobile.com/coding/2025/07/07/ArchitectureTesting</id><content type="html" xml:base="https://jp4mobile.com/coding/2025/07/07/ArchitectureTesting.html"><![CDATA[<p>Continuing with our discussion of the two common architectures from our last post <strong>MVVM</strong> and <strong>TCA</strong>, we’re going to talk about testing with the different architectures, as well as some of the debugging lessons learned from coding up the sample code.</p>

<p>All of the code for this blog post is in this <a href="https://github.com/Jp4Mobile/SampleCode/tree/main/posts/projects/TestingArchitect-2025-07-06">sample code repo</a>.</p>

<!--more-->

<h1 id="testing">Testing</h1>

<p>When testing your app, just check business logic. If you’re testing how code that you haven’t written works, you’re doing things incorrectly. For example, just using some out of the box Swift functionality, if you’re writing code to verify the way that, say, <code class="language-plaintext highlighter-rouge">UserDefaults</code> work, you’re wasting your time.</p>

<p>If you’re creating a library, by all means, verify that your library works as it is supposed to. But if you’re using a library, just verify your business logic around the library calls. That’s where your time is better spent.</p>

<h2 id="mvvm">MVVM</h2>

<p>One of the things that I really liked about working with MVVM is how easy it was to work with and to test. There wasn’t anything that wasn’t standard Swift involved.</p>

<p>Our <a href="https://github.com/Jp4Mobile/SampleCode/tree/main/posts/projects/TestingArchitect-2025-07-06/MVVM/TaskManager">sample code</a> shows our snippets in greater detail.</p>

<p>We’ll test the <code class="language-plaintext highlighter-rouge">TagView</code> code that we’ve been using in our previous examples:</p>

<h3 id="tagview-testing">TagView Testing</h3>

<p>Our initializer, defines the <code class="language-plaintext highlighter-rouge">text</code> property from the <code class="language-plaintext highlighter-rouge">tag</code> property, so that should be tested in our business logic.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// MARK: Initializer</span>
<span class="nf">init</span><span class="p">(</span><span class="n">_</span> <span class="nv">tag</span><span class="p">:</span> <span class="kt">Tag</span><span class="p">,</span> <span class="nv">editMode</span><span class="p">:</span> <span class="kt">EditMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">inactive</span><span class="p">)</span> <span class="p">{</span>
   <span class="k">self</span><span class="o">.</span><span class="n">editMode</span> <span class="o">=</span> <span class="n">editMode</span>
   <span class="k">self</span><span class="o">.</span><span class="n">tag</span> <span class="o">=</span> <span class="n">tag</span>
   <span class="k">self</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">tag</span><span class="o">.</span><span class="n">toString</span>
<span class="p">}</span></code></pre></figure>

<p>So we add testing for that:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// MARK: - Initializer</span>
<span class="kd">func</span> <span class="nf">test_init_initializesThingsProperly</span><span class="p">()</span> <span class="p">{</span>
   <span class="k">let</span> <span class="nv">expectedPayload</span> <span class="o">=</span> <span class="s">"2025-07-06"</span>
   <span class="k">let</span> <span class="nv">expectedTag</span> <span class="o">=</span> <span class="kt">Tag</span><span class="p">(</span><span class="o">.</span><span class="n">due</span><span class="p">,</span> <span class="nv">payload</span><span class="p">:</span> <span class="n">expectedPayload</span><span class="p">)</span>
   <span class="n">sut</span> <span class="o">=</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="kt">Tag</span><span class="p">(</span><span class="o">.</span><span class="n">due</span><span class="p">,</span> <span class="nv">payload</span><span class="p">:</span> <span class="n">expectedPayload</span><span class="p">),</span>
            <span class="nv">editMode</span><span class="p">:</span> <span class="o">.</span><span class="n">inactive</span><span class="p">)</span>

   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">tag</span><span class="p">,</span> <span class="n">expectedTag</span><span class="p">)</span>
   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">text</span><span class="p">,</span> <span class="n">expectedTag</span><span class="o">.</span><span class="n">toString</span><span class="p">)</span>
   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">editMode</span><span class="p">,</span> <span class="o">.</span><span class="n">inactive</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<p>The bulk of our business logic is our view model’s <code class="language-plaintext highlighter-rouge">convertTagIfValid(from:)</code> function.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// MARK: Helper Function</span>
<span class="c1">/// Converts into a tag, if valid</span>
<span class="c1">/// - parameter from: the text to attempt to convert</span>
<span class="kd">func</span> <span class="nf">convertTagIfValid</span><span class="p">(</span><span class="n">from</span> <span class="nv">string</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
   <span class="k">guard</span> <span class="k">let</span> <span class="nv">convertedTag</span> <span class="o">=</span> <span class="n">string</span><span class="o">.</span><span class="nf">toTag</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
      <span class="k">self</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">tag</span><span class="o">.</span><span class="n">toString</span>
      <span class="k">return</span>
   <span class="p">}</span>
   <span class="k">self</span><span class="o">.</span><span class="n">tag</span> <span class="o">=</span> <span class="n">convertedTag</span>
   <span class="k">self</span><span class="o">.</span><span class="n">editMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">inactive</span>
<span class="p">}</span></code></pre></figure>

<p>We’ll need to test both paths through this code:</p>

<ul>
  <li>Our <em>happy path</em>, where the string converts successfully into a <code class="language-plaintext highlighter-rouge">Tag</code> entity.</li>
  <li>And our <em>unhappy path</em>, where the string doesn’t convert and the <code class="language-plaintext highlighter-rouge">textTag</code> property is reset.</li>
</ul>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Our happy path test</span>
<span class="kd">func</span> <span class="nf">test_convertTag_whenSuccessful_setsEverythingProperly</span><span class="p">()</span> <span class="p">{</span>
   <span class="k">let</span> <span class="nv">tag</span> <span class="o">=</span> <span class="kt">Tag</span><span class="p">(</span><span class="s">"tag"</span><span class="p">)</span>
   <span class="k">let</span> <span class="nv">expectedTag</span> <span class="o">=</span> <span class="kt">Tag</span><span class="p">(</span><span class="o">.</span><span class="n">due</span><span class="p">,</span> <span class="nv">payload</span><span class="p">:</span> <span class="s">"2025-07-06"</span><span class="p">)</span>

   <span class="n">sut</span> <span class="o">=</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="n">tag</span><span class="p">)</span>
   <span class="n">sut</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">expectedTag</span><span class="o">.</span><span class="n">toString</span>
   <span class="n">sut</span><span class="o">.</span><span class="n">editMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">active</span>

   <span class="n">sut</span><span class="o">.</span><span class="nf">convertTagIfValid</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="n">expectedTag</span><span class="o">.</span><span class="n">toString</span><span class="p">)</span>

   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">tag</span><span class="p">,</span> <span class="n">expectedTag</span><span class="p">)</span>
   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">text</span><span class="p">,</span> <span class="n">expectedTag</span><span class="o">.</span><span class="n">toString</span><span class="p">)</span>
   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">editMode</span><span class="p">,</span> <span class="o">.</span><span class="n">inactive</span><span class="p">)</span>
   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">isEditing</span><span class="p">,</span> <span class="kc">false</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// Our unhappy path test</span>
<span class="kd">func</span> <span class="nf">func_convertTag_whenUnsuccessful_resetsBackToPreviousTagText</span><span class="p">()</span> <span class="p">{</span>
   <span class="k">let</span> <span class="nv">tag</span> <span class="o">=</span> <span class="kt">Tag</span><span class="p">(</span><span class="s">"tag"</span><span class="p">)</span>
   <span class="n">sut</span> <span class="o">=</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="n">tag</span><span class="p">)</span>

   <span class="c1">// Invalid conversion</span>
   <span class="k">let</span> <span class="nv">invalidText</span> <span class="o">=</span> <span class="s">"invalid"</span>
   <span class="n">sut</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">invalidText</span>
   <span class="n">sut</span><span class="o">.</span><span class="n">editMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">active</span>
   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">text</span><span class="p">,</span> <span class="n">invalidText</span><span class="p">)</span>
   <span class="n">sut</span><span class="o">.</span><span class="nf">convertTagIfValid</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="n">invalidText</span><span class="p">)</span>

   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">tag</span><span class="p">,</span> <span class="n">tag</span><span class="p">)</span>
   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">text</span><span class="p">,</span> <span class="n">tag</span><span class="o">.</span><span class="n">toString</span><span class="p">)</span>
   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">editMode</span><span class="p">,</span> <span class="o">.</span><span class="n">active</span><span class="p">)</span>
   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">isEditing</span><span class="p">,</span> <span class="kc">true</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<p>This still leverages the older XCTest model, rather than the new Swift testing model. But the big thing is still the old standbys for testing:</p>

<ul>
  <li>Set up your test environment’s state.</li>
  <li>Optionally, verify the initial state.</li>
  <li>Call the function that you want to test.</li>
  <li>Verify the expectations of how it should change the state.</li>
</ul>

<p>There are currently two functional tabs in our <code class="language-plaintext highlighter-rouge">TaskPaper</code> app: the <code class="language-plaintext highlighter-rouge">Tasks</code> and <code class="language-plaintext highlighter-rouge">Edit</code> functionality.</p>

<h3 id="textview-testing">TextView Testing</h3>

<p>The main functionality of the <code class="language-plaintext highlighter-rouge">TextView</code> view model is similar to the <code class="language-plaintext highlighter-rouge">TagView</code>. The user enters text into <code class="language-plaintext highlighter-rouge">TextEditor</code> SwiftUI via the <code class="language-plaintext highlighter-rouge">updatedText(text:)</code> function. Similar to what we saw in the <code class="language-plaintext highlighter-rouge">TagView</code>, our testing will need to verify both the happy path and unhappy path. Here, we have an error message added to the UI to indicate to the user when there’s a problem.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// MARK: - Updated Text Conversion Tests</span>
<span class="kd">func</span> <span class="nf">test_updatedText_whenEmpty_setsExpectedError</span><span class="p">()</span> <span class="p">{</span>
   <span class="n">sut</span> <span class="o">=</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="kt">TMType</span><span class="o">.</span><span class="kt">Mock</span><span class="o">.</span><span class="kt">TopLevel</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>

   <span class="n">sut</span><span class="o">.</span><span class="nf">updatedText</span><span class="p">(</span><span class="nv">text</span><span class="p">:</span> <span class="s">""</span><span class="p">)</span>

   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">type</span><span class="p">,</span> <span class="kt">TMType</span><span class="o">.</span><span class="kt">Mock</span><span class="o">.</span><span class="kt">TopLevel</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">text</span><span class="p">,</span> <span class="kt">TMType</span><span class="o">.</span><span class="kt">Mock</span><span class="o">.</span><span class="kt">TopLevel</span><span class="o">.</span><span class="n">text</span><span class="o">.</span><span class="n">toString</span><span class="p">)</span>
   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">errorMessage</span><span class="p">,</span> <span class="s">"Unable to convert &lt;&gt;"</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<p>An empty string is flagged as something that doesn’t convert.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">test_updatedText_whenMultiple_setsExpectedError</span><span class="p">()</span> <span class="p">{</span>
   <span class="n">sut</span> <span class="o">=</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="kt">TMType</span><span class="o">.</span><span class="kt">Mock</span><span class="o">.</span><span class="kt">TopLevel</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>

   <span class="n">sut</span><span class="o">.</span><span class="nf">updatedText</span><span class="p">(</span><span class="nv">text</span><span class="p">:</span> <span class="kt">TMType</span><span class="o">.</span><span class="kt">Mock</span><span class="o">.</span><span class="kt">TopLevel</span><span class="o">.</span><span class="n">project</span><span class="o">.</span><span class="n">toString</span> <span class="o">+</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span> <span class="o">+</span>
                   <span class="kt">TMType</span><span class="o">.</span><span class="kt">Mock</span><span class="o">.</span><span class="kt">Projects</span><span class="o">.</span><span class="n">projectWithTasks</span><span class="o">.</span><span class="n">toString</span><span class="p">)</span>

   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">type</span><span class="p">,</span> <span class="kt">TMType</span><span class="o">.</span><span class="kt">Mock</span><span class="o">.</span><span class="kt">TopLevel</span><span class="o">.</span><span class="n">project</span><span class="p">)</span>
   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">text</span><span class="p">,</span> <span class="kt">TMType</span><span class="o">.</span><span class="kt">Mock</span><span class="o">.</span><span class="kt">TopLevel</span><span class="o">.</span><span class="n">project</span><span class="o">.</span><span class="n">toString</span><span class="p">)</span>
   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">errorMessage</span><span class="p">,</span> 
                  <span class="s">"Only the first converted type model is saved. You may need to "</span> <span class="o">+</span>
                  <span class="s">"change indentation to keep them under the proper project."</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<p>Our logic is such that only a single <code class="language-plaintext highlighter-rouge">TMType</code> model is supported. When more than one is entered, only the first is used, but an error message indicates to the user where the problem might be and how to fix it.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">test_updatedText_whenConverted_setsTextProperly</span><span class="p">()</span> <span class="p">{</span>
   <span class="n">sut</span> <span class="o">=</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="kt">TMType</span><span class="o">.</span><span class="kt">Mock</span><span class="o">.</span><span class="kt">TopLevel</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
   <span class="n">sut</span><span class="o">.</span><span class="n">errorMessage</span> <span class="o">=</span> <span class="s">"Invalid"</span>

   <span class="n">sut</span><span class="o">.</span><span class="nf">updatedText</span><span class="p">(</span><span class="nv">text</span><span class="p">:</span> <span class="kt">TMType</span><span class="o">.</span><span class="kt">Mock</span><span class="o">.</span><span class="kt">Projects</span><span class="o">.</span><span class="n">projectWithTasks</span><span class="o">.</span><span class="n">toString</span><span class="p">)</span>

   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">type</span><span class="p">,</span> <span class="kt">TMType</span><span class="o">.</span><span class="kt">Mock</span><span class="o">.</span><span class="kt">Projects</span><span class="o">.</span><span class="n">projectWithTasks</span><span class="p">)</span>
   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">text</span><span class="p">,</span> <span class="kt">TMType</span><span class="o">.</span><span class="kt">Mock</span><span class="o">.</span><span class="kt">Projects</span><span class="o">.</span><span class="n">projectWithTasks</span><span class="o">.</span><span class="n">toString</span><span class="p">)</span>
   <span class="kt">XCTAssertNil</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">errorMessage</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<p>And then lastly, the happy path, where the <code class="language-plaintext highlighter-rouge">tag</code> converts properly, the <code class="language-plaintext highlighter-rouge">text</code> properties is properly set, and any lingering <code class="language-plaintext highlighter-rouge">errorMessage</code> is properly cleared.</p>

<p>The <code class="language-plaintext highlighter-rouge">TaskMasterAndDetailView.ViewModel</code> tests are a little more challenging, but the logic is straight-forward. We have the business logic wrapped in a handful of view model functions:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">addItem(from:)</code> which adds an initial item with an optional <code class="language-plaintext highlighter-rouge">String?</code> value, which allows use to easily convert from <code class="language-plaintext highlighter-rouge">nil</code> or an empty string to a set value, such as <code class="language-plaintext highlighter-rouge">"New Item"</code> or the like.</li>
  <li><code class="language-plaintext highlighter-rouge">select(item:)</code> which sets up the state of the view model when the user selects an item from the list.</li>
  <li><code class="language-plaintext highlighter-rouge">process(_:)</code> which handles the business logic to verify and test the various responses from the <code class="language-plaintext highlighter-rouge">TaskDetailView</code> such as <em>cancel</em>, <em>delete</em>, or <em>save</em>.</li>
</ul>

<p>As the view model also holds the state of the <code class="language-plaintext highlighter-rouge">Task</code> feature functionality, it makes it even easier to test. A function is called and then the state of the view model is verified against expectations.</p>

<p>As the <code class="language-plaintext highlighter-rouge">TaskDetailView</code> has their own local view model that triggers the changes the the <code class="language-plaintext highlighter-rouge">TaskMasterAndDetailView.ViewModel</code>’s <code class="language-plaintext highlighter-rouge">responseType</code> property, that needs to be tested as well.</p>

<h2 id="mvvm-combine">MVVM (Combine)</h2>

<p>The MVVM (Combine) code is slightly different, due to leveraging the <code class="language-plaintext highlighter-rouge">StateBindingViewModel</code>.</p>

<p>Similar to our architecture post, we aren’t going to talk in depth about the MVVM (Combine) but the sample code is present for people that wonder about it. The main changes shift things based upon the <code class="language-plaintext highlighter-rouge">state</code> objects and how the <code class="language-plaintext highlighter-rouge">StateBindingViewModel</code> requires functionality to leverage it’s binding and access functionality, when setting up tests.</p>

<p>Our <a href="https://github.com/Jp4Mobile/SampleCode/tree/main/posts/projects/TestingArchitect-2025-07-06/MVVM-Combine/TaskManager">sample code</a> illustrates all of these for people that are interested.</p>

<h2 id="tca">TCA</h2>

<p>Testing with TCA is a bit more challenging. We need to test things by verifying elements of the TCA flow, either in an <em>exhaustive</em> or <em>inexhaustive</em> flow. With the exhaustive flow, the user tests everything, every element of the flow, as it goes from beginning to end. The inexhaustive flow just tests certain interactions to verify that these parts of the whole, in isolation, work as expected.</p>

<p>Let’s look at things in a little more depth.</p>

<p>Our <a href="https://github.com/Jp4Mobile/SampleCode/tree/main/posts/projects/TestingArchitect-2025-07-06/TCA/TaskManager">sample code</a> shows our snippets in greater detail.</p>

<h3 id="tagconverter-and-tcatagview-testing">TagConverter and TCATagView Testing</h3>

<p>The code we’ll be testing is in <code class="language-plaintext highlighter-rouge">TCATagFeatureFinalPass.swift</code> which hosts the reducer (<code class="language-plaintext highlighter-rouge">TagConverter</code> and the view <code class="language-plaintext highlighter-rouge">TCATagView</code>).</p>

<p>Exhaustive tests test and verify each change to the TCA state from an initial state to the end of a specific flow through the app. We current have two flows through the <code class="language-plaintext highlighter-rouge">TCATagFeatureFinalPass</code>: successful and not successful.</p>

<p>We could test both a negative flow and a positive flow in the same test, but I’ve always felt a proper unit test will test a specific thing and that alone.</p>

<h4 id="exhaustive-unsuccessful-path">Exhaustive Unsuccessful Path</h4>

<p>This will test the flow from beginning to end.</p>

<ul>
  <li>The user initiating editing by tapping on the static view.</li>
  <li>Once edit mode was initiated, the user entered text.</li>
  <li>When the text is submitted and it does not successfully convert into a <code class="language-plaintext highlighter-rouge">Tag</code>, an error message is presented.</li>
</ul>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">   <span class="kd">@Test</span>
   <span class="kd">func</span> <span class="nf">test_exhaustive_unsuccessfulEditFlow</span><span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="p">{</span>
      <span class="k">let</span> <span class="nv">expectedText</span> <span class="o">=</span> <span class="s">"tag(payload"</span>
      <span class="c1">// Set up</span>
      <span class="k">let</span> <span class="nv">store</span> <span class="o">=</span> <span class="k">await</span> <span class="kt">TestStore</span><span class="p">(</span><span class="nv">initialState</span><span class="p">:</span>
                              <span class="kt">TagConverter</span><span class="o">.</span><span class="kt">State</span><span class="p">(</span><span class="nv">tag</span><span class="p">:</span> <span class="kt">Constants</span><span class="o">.</span><span class="kt">MockTag</span><span class="o">.</span><span class="n">test</span><span class="p">)</span>
      <span class="p">)</span> <span class="p">{</span>
         <span class="kt">TagConverter</span><span class="p">()</span>
      <span class="p">}</span>
   
      <span class="c1">// Walk through the flow</span>
      <span class="k">await</span> <span class="n">store</span><span class="o">.</span><span class="nf">send</span><span class="p">(</span><span class="o">.</span><span class="n">tapped</span><span class="p">)</span> <span class="p">{</span>
         <span class="nv">$0</span><span class="o">.</span><span class="n">editMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">active</span>
      <span class="p">}</span>
      <span class="k">await</span> <span class="n">store</span><span class="o">.</span><span class="nf">send</span><span class="p">(</span><span class="o">.</span><span class="nf">entered</span><span class="p">(</span><span class="n">expectedText</span><span class="p">))</span> <span class="p">{</span>
         <span class="nv">$0</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">expectedText</span>
      <span class="p">}</span>
      <span class="c1">// Invalid text, when submitted, triggers the error flow</span>
      <span class="k">await</span> <span class="n">store</span><span class="o">.</span><span class="nf">send</span><span class="p">(</span><span class="o">.</span><span class="n">submitted</span><span class="p">)</span> <span class="p">{</span>
         <span class="nv">$0</span><span class="o">.</span><span class="n">errorMessage</span> <span class="o">=</span> <span class="s">"Unable to convert &lt;</span><span class="se">\(</span><span class="n">expectedText</span><span class="se">)</span><span class="s">&gt; into a tag."</span>
      <span class="p">}</span>
   <span class="p">}</span></code></pre></figure>

<h4 id="exhaustive-successful-path">Exhaustive Successful Path</h4>

<p>This is another beginning to end test, but in this case, it handles a successful text to <code class="language-plaintext highlighter-rouge">Tag</code> conversion.</p>

<ul>
  <li>The user initiating editing by tapping on the static view.</li>
  <li>Once edit mode was initiated, the user entered text.</li>
  <li>When the text is submitted and successfully converted into a <code class="language-plaintext highlighter-rouge">Tag</code>, the <code class="language-plaintext highlighter-rouge">editMode</code>, <code class="language-plaintext highlighter-rouge">tag</code>, and <code class="language-plaintext highlighter-rouge">text</code> properties are replaced.</li>
</ul>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">   <span class="kd">@Test</span>
   <span class="kd">func</span> <span class="nf">test_exhaustive_successfulEditFlow</span><span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="p">{</span>
      <span class="k">let</span> <span class="nv">expectedTag</span> <span class="o">=</span> <span class="k">try</span> <span class="kt">XCTUnwrap</span><span class="p">(</span><span class="s">"@tag"</span><span class="o">.</span><span class="nf">toTag</span><span class="p">())</span>
      <span class="k">let</span> <span class="nv">expectedText</span> <span class="o">=</span> <span class="s">"@tag()"</span>
      <span class="c1">// Set up</span>
      <span class="k">let</span> <span class="nv">store</span> <span class="o">=</span> <span class="kt">TestStore</span><span class="p">(</span><span class="nv">initialState</span><span class="p">:</span>
                        <span class="kt">TagConverter</span><span class="o">.</span><span class="kt">State</span><span class="p">(</span><span class="nv">tag</span><span class="p">:</span> <span class="kt">Constants</span><span class="o">.</span><span class="kt">MockTag</span><span class="o">.</span><span class="n">test</span><span class="p">)</span>
      <span class="p">)</span> <span class="p">{</span>
         <span class="kt">TagConverter</span><span class="p">()</span>
      <span class="p">}</span>
   
      <span class="c1">// Walk through the flow</span>
      <span class="k">await</span> <span class="n">store</span><span class="o">.</span><span class="nf">send</span><span class="p">(</span><span class="o">.</span><span class="n">tapped</span><span class="p">)</span> <span class="p">{</span>
         <span class="nv">$0</span><span class="o">.</span><span class="n">editMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">active</span>
      <span class="p">}</span>
      <span class="k">await</span> <span class="n">store</span><span class="o">.</span><span class="nf">send</span><span class="p">(</span><span class="o">.</span><span class="nf">entered</span><span class="p">(</span><span class="n">expectedText</span><span class="p">))</span> <span class="p">{</span>
         <span class="nv">$0</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">expectedText</span>
      <span class="p">}</span>
      <span class="c1">// Valid text, when submitted, triggers the update flow</span>
      <span class="k">await</span> <span class="n">store</span><span class="o">.</span><span class="nf">send</span><span class="p">(</span><span class="o">.</span><span class="n">submitted</span><span class="p">)</span> <span class="p">{</span>
         <span class="nv">$0</span><span class="o">.</span><span class="n">editMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">inactive</span>
         <span class="nv">$0</span><span class="o">.</span><span class="n">tag</span> <span class="o">=</span> <span class="n">expectedTag</span>
         <span class="c1">// And verifies the normalized text (which is different than the entered text)</span>
         <span class="nv">$0</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">expectedTag</span><span class="o">.</span><span class="n">toString</span>
      <span class="p">}</span>
   <span class="p">}</span></code></pre></figure>

<p>But, unfortunately, that can be a lot of work depending on the feature. Happily, TCA offers inexhaustive tests, where an initial state can be set up and tests will test the change from that state.</p>

<p>In our case, we set the initial state as though the TCA view was already in edit mode, some text had already been entered, and then when submitted, presents an error.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">   <span class="kd">@Test</span>
   <span class="kd">func</span> <span class="nf">test_inexhaustive_invalidSubmission_generatesError</span><span class="p">()</span> <span class="k">async</span> <span class="p">{</span>
      <span class="k">let</span> <span class="nv">expectedText</span> <span class="o">=</span> <span class="s">"tag(payload"</span>
      <span class="c1">// Set up</span>
      <span class="k">let</span> <span class="nv">store</span> <span class="o">=</span> <span class="kt">TestStore</span><span class="p">(</span><span class="nv">initialState</span><span class="p">:</span>
                        <span class="kt">TagConverter</span><span class="o">.</span><span class="kt">State</span><span class="p">(</span><span class="kt">Constants</span><span class="o">.</span><span class="kt">MockTag</span><span class="o">.</span><span class="n">test</span><span class="p">,</span>
                                       <span class="nv">editMode</span><span class="p">:</span> <span class="o">.</span><span class="n">active</span><span class="p">,</span>
                                       <span class="nv">text</span><span class="p">:</span> <span class="n">expectedText</span><span class="p">)</span>
      <span class="p">)</span> <span class="p">{</span>
         <span class="kt">TagConverter</span><span class="p">()</span>
      <span class="p">}</span>
      <span class="n">store</span><span class="o">.</span><span class="n">exhaustivity</span> <span class="o">=</span> <span class="o">.</span><span class="n">off</span>
   
      <span class="c1">// Verify the submission error flow</span>
      <span class="k">await</span> <span class="n">store</span><span class="o">.</span><span class="nf">send</span><span class="p">(</span><span class="o">.</span><span class="n">submitted</span><span class="p">)</span> <span class="p">{</span>
         <span class="nv">$0</span><span class="o">.</span><span class="n">errorMessage</span> <span class="o">=</span> <span class="s">"Unable to convert &lt;</span><span class="se">\(</span><span class="n">expectedText</span><span class="se">)</span><span class="s">&gt; into a tag."</span>
      <span class="p">}</span>
   <span class="p">}</span></code></pre></figure>

<h3 id="testing-with-dependencies">Testing With Dependencies</h3>

<p>One of the more useful features of TCA testing is the ability to inject dependencies. In our <code class="language-plaintext highlighter-rouge">TCATaskFeature</code>, we could have done it this way:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">   <span class="c1">// MARK: Body</span>
   <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">ReducerOf</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;</span> <span class="p">{</span>
      <span class="kt">Reduce</span> <span class="p">{</span> <span class="n">state</span><span class="p">,</span> <span class="n">action</span> <span class="k">in</span>
   
         <span class="k">switch</span> <span class="n">action</span> <span class="p">{</span>
         <span class="k">case</span> <span class="o">.</span><span class="nv">addButtonTapped</span><span class="p">:</span>
            <span class="n">state</span><span class="o">.</span><span class="n">destination</span> <span class="o">=</span> <span class="o">.</span><span class="nf">addTask</span><span class="p">(</span>
               <span class="kt">TCAAddEditTaskFeature</span><span class="o">.</span><span class="kt">State</span><span class="p">(</span>
                  <span class="nv">mode</span><span class="p">:</span> <span class="o">.</span><span class="n">add</span><span class="p">,</span>
                  <span class="nv">task</span><span class="p">:</span> <span class="kt">TCATask</span><span class="p">(</span><span class="nv">id</span><span class="p">:</span> <span class="kt">UUID</span><span class="p">(),</span>
                             <span class="nv">task</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">type</span><span class="p">:</span> <span class="o">.</span><span class="nf">text</span><span class="p">(</span><span class="s">""</span><span class="p">)))</span>
               <span class="p">)</span>
            <span class="p">)</span>
   
            <span class="k">return</span> <span class="o">.</span><span class="k">none</span>
   <span class="c1">// ...</span></code></pre></figure>

<p>But if we had, we’d have no idea what the UUID value would be which would make testing difficult. Instead we can inject a dependency:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">   <span class="c1">// MARK: Body</span>
   <span class="kd">@Dependency</span><span class="p">(\</span><span class="o">.</span><span class="n">uuid</span><span class="p">)</span> <span class="k">var</span> <span class="nv">uuid</span>
   <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">ReducerOf</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;</span> <span class="p">{</span>
      <span class="kt">Reduce</span> <span class="p">{</span> <span class="n">state</span><span class="p">,</span> <span class="n">action</span> <span class="k">in</span>
   
         <span class="k">switch</span> <span class="n">action</span> <span class="p">{</span>
         <span class="k">case</span> <span class="o">.</span><span class="nv">addButtonTapped</span><span class="p">:</span>
            <span class="n">state</span><span class="o">.</span><span class="n">destination</span> <span class="o">=</span> <span class="o">.</span><span class="nf">addTask</span><span class="p">(</span>
               <span class="kt">TCAAddEditTaskFeature</span><span class="o">.</span><span class="kt">State</span><span class="p">(</span>
                  <span class="nv">mode</span><span class="p">:</span> <span class="o">.</span><span class="n">add</span><span class="p">,</span>
                  <span class="nv">task</span><span class="p">:</span> <span class="kt">TCATask</span><span class="p">(</span><span class="nv">id</span><span class="p">:</span> <span class="k">self</span><span class="o">.</span><span class="nf">uuid</span><span class="p">(),</span>
                             <span class="nv">task</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">type</span><span class="p">:</span> <span class="o">.</span><span class="nf">text</span><span class="p">(</span><span class="s">""</span><span class="p">)))</span>
               <span class="p">)</span>
            <span class="p">)</span>
   
            <span class="k">return</span> <span class="o">.</span><span class="k">none</span>
   <span class="c1">// ...</span></code></pre></figure>

<p>With this, our code still generates a UUID, but the method can change in our testing. In our test code, we can ensure incremented UUID generation, so that we will be able to have generated UUIDs that conform to our expectations.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">   <span class="kd">@Test</span>
   <span class="kd">func</span> <span class="nf">test_subsequentAddItems_haveDifferentIDs</span><span class="p">()</span> <span class="k">async</span> <span class="p">{</span>
      <span class="k">let</span> <span class="nv">initialTask</span><span class="p">:</span> <span class="kt">TCATask</span> <span class="o">=</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">id</span><span class="p">:</span> <span class="kt">UUID</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span> <span class="nv">task</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">type</span><span class="p">:</span> <span class="o">.</span><span class="nf">text</span><span class="p">(</span><span class="s">""</span><span class="p">)))</span>
      <span class="k">let</span> <span class="nv">addedTask</span><span class="p">:</span> <span class="kt">TCATask</span> <span class="o">=</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">id</span><span class="p">:</span> <span class="kt">UUID</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span> <span class="nv">task</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">type</span><span class="p">:</span> <span class="o">.</span><span class="nf">text</span><span class="p">(</span><span class="s">""</span><span class="p">)))</span>
   
      <span class="c1">// Set up</span>
      <span class="k">let</span> <span class="nv">state</span><span class="p">:</span> <span class="kt">TCATaskFeature</span><span class="o">.</span><span class="kt">State</span> <span class="o">=</span> <span class="o">.</span><span class="nf">init</span><span class="p">()</span>
      <span class="k">let</span> <span class="nv">store</span> <span class="o">=</span> <span class="kt">TestStore</span><span class="p">(</span><span class="nv">initialState</span><span class="p">:</span> <span class="n">state</span><span class="p">)</span> <span class="p">{</span>
         <span class="kt">TCATaskFeature</span><span class="p">()</span>
      <span class="p">}</span> <span class="nv">withDependencies</span><span class="p">:</span> <span class="p">{</span>
         <span class="nv">$0</span><span class="o">.</span><span class="n">uuid</span> <span class="o">=</span> <span class="o">.</span><span class="n">incrementing</span>
      <span class="p">}</span>
      <span class="n">store</span><span class="o">.</span><span class="n">exhaustivity</span> <span class="o">=</span> <span class="o">.</span><span class="n">off</span>
   
      <span class="c1">// Walk through the flow</span>
      <span class="k">await</span> <span class="n">store</span><span class="o">.</span><span class="nf">send</span><span class="p">(</span><span class="o">.</span><span class="n">addButtonTapped</span><span class="p">)</span> <span class="p">{</span>
         <span class="nv">$0</span><span class="o">.</span><span class="n">destination</span> <span class="o">=</span> <span class="o">.</span><span class="nf">addTask</span><span class="p">(</span><span class="kt">TCAAddEditTaskFeature</span><span class="o">.</span><span class="kt">State</span><span class="p">(</span>
            <span class="nv">mode</span><span class="p">:</span> <span class="o">.</span><span class="n">add</span><span class="p">,</span>
            <span class="nv">task</span><span class="p">:</span> <span class="n">initialTask</span>
         <span class="p">))</span>
      <span class="p">}</span>
      <span class="k">await</span> <span class="n">store</span><span class="o">.</span><span class="nf">send</span><span class="p">(</span><span class="o">.</span><span class="n">addButtonTapped</span><span class="p">)</span> <span class="p">{</span>
         <span class="nv">$0</span><span class="o">.</span><span class="n">destination</span> <span class="o">=</span> <span class="o">.</span><span class="nf">addTask</span><span class="p">(</span><span class="kt">TCAAddEditTaskFeature</span><span class="o">.</span><span class="kt">State</span><span class="p">(</span>
            <span class="nv">mode</span><span class="p">:</span> <span class="o">.</span><span class="n">add</span><span class="p">,</span>
            <span class="nv">task</span><span class="p">:</span> <span class="n">addedTask</span>
         <span class="p">))</span>
      <span class="p">}</span>
   <span class="p">}</span></code></pre></figure>

<h2 id="testing-challenges">Testing Challenges</h2>

<p>With any sufficiently complex code base, there could be challenges in your testing.</p>

<h3 id="inadvertent-side-effects">Inadvertent Side Effects</h3>

<p>One challenge found had to do with inadvertent state changes due to <code class="language-plaintext highlighter-rouge">didSet</code> property functionality. This also illustrates why the differences between <code class="language-plaintext highlighter-rouge">TCATagFeatureFirstPass</code> and <code class="language-plaintext highlighter-rouge">TCATagFeatureFinalPass</code> were important. The <code class="language-plaintext highlighter-rouge">didSet</code> functionality for state properties went against what would typically be expected from <strong>TCA</strong> code, so they were refactored away.</p>

<p>In our <code class="language-plaintext highlighter-rouge">TCATextFeature</code>, I left the <code class="language-plaintext highlighter-rouge">didSet</code> functionality in place, which introduced a bug in testing clearly illustrated in variant initializers:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">   <span class="c1">// MARK: State</span>
   <span class="kd">@ObservableState</span>
   <span class="kd">struct</span> <span class="kt">State</span><span class="p">:</span> <span class="kt">Equatable</span> <span class="p">{</span>
      <span class="k">var</span> <span class="nv">text</span><span class="p">:</span> <span class="kt">String</span>
      <span class="k">var</span> <span class="nv">task</span><span class="p">:</span> <span class="kt">TCATask</span> <span class="p">{</span>
         <span class="k">didSet</span> <span class="p">{</span>
            <span class="n">text</span> <span class="o">=</span> <span class="n">task</span><span class="o">.</span><span class="n">task</span><span class="o">.</span><span class="n">toString</span>
         <span class="p">}</span>
      <span class="p">}</span>
      <span class="k">var</span> <span class="nv">errorMessage</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span> <span class="p">{</span>
         <span class="k">didSet</span> <span class="p">{</span>
            <span class="k">guard</span> <span class="n">errorMessage</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
   
            <span class="n">text</span> <span class="o">=</span> <span class="n">task</span><span class="o">.</span><span class="n">task</span><span class="o">.</span><span class="n">toString</span>
         <span class="p">}</span>
      <span class="p">}</span>
      <span class="k">var</span> <span class="nv">hasError</span><span class="p">:</span> <span class="kt">Bool</span> <span class="p">{</span>
         <span class="n">errorMessage</span> <span class="o">!=</span> <span class="kc">nil</span>
      <span class="p">}</span>
   <span class="p">}</span></code></pre></figure>

<p>An initializer was added to set all of the fields, so that we can set up the state as we’d like it for testing:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">   <span class="nf">init</span><span class="p">(</span><span class="nv">task</span><span class="p">:</span> <span class="kt">TCATask</span><span class="p">,</span>
        <span class="nv">text</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span> <span class="o">=</span> <span class="kc">nil</span><span class="p">,</span>
        <span class="nv">errorMessage</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span> <span class="o">=</span> <span class="kc">nil</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">self</span><span class="o">.</span><span class="n">task</span> <span class="o">=</span> <span class="n">task</span>
      <span class="k">self</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">text</span> <span class="p">??</span> <span class="n">task</span><span class="o">.</span><span class="n">task</span><span class="o">.</span><span class="n">toString</span>
      <span class="k">self</span><span class="o">.</span><span class="n">errorMessage</span> <span class="o">=</span> <span class="n">errorMessage</span>
   <span class="p">}</span></code></pre></figure>

<p>That looks simple enough.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">state</span> <span class="o">=</span> <span class="kt">TCATextFeature</span><span class="o">.</span><span class="kt">State</span><span class="p">(</span><span class="nv">task</span><span class="p">:</span> <span class="n">initialType</span><span class="p">,</span>
                                 <span class="nv">text</span><span class="p">:</span> <span class="n">expectedText</span><span class="p">,</span>
                                 <span class="nv">errorMessage</span><span class="p">:</span> <span class="s">"errorMessage"</span><span class="p">)</span></code></pre></figure>

<p>We’d assume that the <code class="language-plaintext highlighter-rouge">task</code> == <code class="language-plaintext highlighter-rouge">initialType</code>, <code class="language-plaintext highlighter-rouge">text</code> == <code class="language-plaintext highlighter-rouge">expectedText</code>, and
<code class="language-plaintext highlighter-rouge">errorMessage</code> == <code class="language-plaintext highlighter-rouge">"errorMessage"</code>.</p>

<p>Unfortunately, the order of assignments in the initializer (as well as the <code class="language-plaintext highlighter-rouge">didSet</code> code) ensured that we got something else: <code class="language-plaintext highlighter-rouge">task</code> == <code class="language-plaintext highlighter-rouge">initialType</code>, <code class="language-plaintext highlighter-rouge">text</code> == <code class="language-plaintext highlighter-rouge">initialType.task.toString</code>, <code class="language-plaintext highlighter-rouge">errorMessage</code> == “errorMessage”. The task was set properly, which set the text. The text assignment overwrote that value. And then the errorMessage assignment overwrote the text value again.</p>

<p>Inexhaustive testing would begin from inaccurate initial state, and things would continue to fail from that point.</p>

<p>One solution would be to change the order of the arguments, but a better solution would be to have a <code class="language-plaintext highlighter-rouge">TCATextFeature.State</code> without <code class="language-plaintext highlighter-rouge">didSet</code> logic, and therefore ensure any changes to the state object could only be done by <em>Reducer</em> instead.</p>

<h3 id="reported-errors">Reported Errors</h3>

<p>Another challenge with TCA is that errors can be a little convoluted.</p>

<p>Let’s open up <code class="language-plaintext highlighter-rouge">TCATaskFeature.swift</code> and edit the <em>Reducer</em>.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Line 87</span>
<span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">destination</span><span class="p">(</span><span class="o">.</span><span class="nf">presented</span><span class="p">(</span><span class="o">.</span><span class="nf">editTask</span><span class="p">(</span><span class="o">.</span><span class="nf">delegate</span><span class="p">(</span><span class="o">.</span><span class="nf">saveTask</span><span class="p">(</span><span class="n">task</span><span class="p">))))):</span></code></pre></figure>

<p>Change that to:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Line 87</span>
<span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">destination</span><span class="p">(</span><span class="o">.</span><span class="nf">presented</span><span class="p">(</span><span class="o">.</span><span class="nf">editTask</span><span class="p">(</span><span class="o">.</span><span class="nf">delegate</span><span class="p">(</span><span class="o">.</span><span class="nf">saveTaskDifferent</span><span class="p">(</span><span class="n">task</span><span class="p">))))):</span></code></pre></figure>

<p>You’d expect that line 87 would indicate an error, because <code class="language-plaintext highlighter-rouge">.saveTaskDifferent</code> doesn’t exist in the code base. Instead, line 54 displays a <em>Cannot infer contextual base in reference to member ‘addTask’</em> error instead.</p>

<p><img src="/img/CannotInferError-20250706.png" alt="Error Message" /></p>

<p>I’ve found that changes in the view or the reducer should be triple checked, when you start finding issues like that that don’t make sense. When I was first playing around with TCA, I found that I was commenting out code until things were stable and then adding elements back piece by piece to try to triage where the issue was introduced.</p>

<p>But that leads us to a decision about which architecture I will be choosing for the <code class="language-plaintext highlighter-rouge">TaskManager</code> app.</p>

<h2 id="why-im-picking-tca-to-go-forward-with">Why I’m picking TCA to go forward with</h2>

<p>While challenges in TCA exist, I don’t think that’s enough to stop me from continuing with TCA for <code class="language-plaintext highlighter-rouge">TaskManager</code> going forward. I really like the clarity and the expansiveness of the library. Especially for greenfield SwiftUI projects.</p>

<p>There’s something about the clarity of <em>State</em> and <em>Action</em>, as well as the clarity of having the business logic wrapped in the <em>Reducer</em>.</p>

<hr />

<p>Next article, we’ll continue with the SwiftUI UI/UX for our <code class="language-plaintext highlighter-rouge">TaskManager</code> app with the TCA architecture.</p>]]></content><author><name>Jp</name></author><category term="coding" /><category term="taskmanager" /><category term="swiftui" /><category term="architecture" /><category term="tca" /><category term="mvvm" /><category term="testing" /><summary type="html"><![CDATA[Continuing with our discussion of the two common architectures from our last post MVVM and TCA, we’re going to talk about testing with the different architectures, as well as some of the debugging lessons learned from coding up the sample code. All of the code for this blog post is in this sample code repo.]]></summary></entry><entry><title type="html">Architectures for SwiftUI Projects</title><link href="https://jp4mobile.com/coding/2025/06/25/Architecture.html" rel="alternate" type="text/html" title="Architectures for SwiftUI Projects" /><published>2025-06-25T10:00:01+00:00</published><updated>2025-06-25T10:00:01+00:00</updated><id>https://jp4mobile.com/coding/2025/06/25/Architecture</id><content type="html" xml:base="https://jp4mobile.com/coding/2025/06/25/Architecture.html"><![CDATA[<p>Three common architectures for modern iOS apps are: <strong>MVVM</strong>, <strong>TCA</strong>, and <strong>VIPER</strong>.</p>

<p>This post will talk about using MVVM and TCA for our spec <code class="language-plaintext highlighter-rouge">TaskManager</code> app.</p>

<p>All of the code for this blog post is in this <a href="https://github.com/Jp4Mobile/SampleCode/tree/main/posts/projects/Architect-2025-05-25">sample code repo</a>.</p>

<!--more-->

<h2 id="app-functionality">App Functionality</h2>

<p>We’ve ensured that the three versions of the app: <em>MVVM</em>, <em>MVVM with Combine</em>, and <em>TCA</em> all have the same functionality:</p>

<ul>
  <li>Three tabs:
    <ul>
      <li>
        <p><strong>Task</strong></p>

        <p>The Task tab has a list of tasks, the ability to add a new task, an ability to edit an existing task, and an ability to delete an existing task.</p>
      </li>
      <li>
        <p><strong>Text</strong></p>

        <p>The Text tab shows a single task and its children, converting from text into the task.</p>
      </li>
      <li>
        <p><strong>Setting</strong></p>

        <p>A placeholder view that will have settings in the future.</p>
      </li>
    </ul>
  </li>
</ul>

<p>Let’s walk through each of the architectures in more detail:</p>

<h2 id="mvvm">MVVM</h2>

<p>MVVM is defined as follows:</p>

<h3 id="model---view---view-model">Model - View - View Model</h3>

<p><code class="language-plaintext highlighter-rouge">View</code> ↔️ <code class="language-plaintext highlighter-rouge">View Model</code> ↔️ <code class="language-plaintext highlighter-rouge">Model</code></p>

<h4 id="model">Model</h4>

<p>As we’ve talked through multiple blog posts so far, we have a lot of models that make up the various elements of the Task Manager data.</p>

<h4 id="view">View</h4>

<p>These will be our UI/UX layer, as described above. It won’t communicate directly with the model. Each view will have the appropriate <em>View Model</em>.</p>

<h4 id="view-model">View Model</h4>

<p>Each view model handles both the data segregation so that each <em>View</em> only sees the data that they’re supposed to as well as the business logic to ensure that the <em>Model</em> can be updated.</p>

<h3 id="mvvm-sample-code">MVVM Sample Code</h3>

<p>All of this will be based upon the <a href="https://github.com/Jp4Mobile/SampleCode/tree/main/posts/projects/Architect-2025-05-25/MVVM/TaskManager">MVVM Sample Code from the repo</a>.</p>

<p>Let’s examine the <code class="language-plaintext highlighter-rouge">TagView</code> that we built previously, as we incorporate view models. I like Paul Hudson’s idea of incorporating the definition of the view model within the SwiftUI view struct to make it clear which <code class="language-plaintext highlighter-rouge">ViewModel</code> is associated with which SwiftUI view. (He outlined this idea in his MVVM post: <a href="https://www.hackingwithswift.com/books/ios-swiftui/introducing-mvvm-into-your-swiftui-project">Hacking With SwiftUI on MVVM</a>).</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">TagView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
   <span class="kd">@Observable</span>
   <span class="kd">class</span> <span class="kt">ViewModel</span> <span class="p">{</span>
      <span class="c1">// MARK: Properties</span>
      <span class="c1">/// The edit state of the view</span>
      <span class="k">var</span> <span class="nv">editMode</span><span class="p">:</span> <span class="kt">EditMode</span>
      <span class="c1">/// The actual tag</span>
      <span class="kd">private(set)</span> <span class="k">var</span> <span class="nv">tag</span><span class="p">:</span> <span class="kt">Tag</span> <span class="p">{</span>
         <span class="k">didSet</span> <span class="p">{</span>
            <span class="n">text</span> <span class="o">=</span> <span class="n">tag</span><span class="o">.</span><span class="n">toString</span>
         <span class="p">}</span>
      <span class="p">}</span>
      <span class="c1">/// The entered text to be converted into the tag</span>
      <span class="k">var</span> <span class="nv">text</span><span class="p">:</span> <span class="kt">String</span>
   
      <span class="c1">// MARK: Computed Properties</span>
      <span class="k">var</span> <span class="nv">isEditing</span><span class="p">:</span> <span class="kt">Bool</span> <span class="p">{</span>
         <span class="n">editMode</span> <span class="o">==</span> <span class="o">.</span><span class="n">active</span>
      <span class="p">}</span>
   
      <span class="c1">// MARK: Initializer</span>
      <span class="nf">init</span><span class="p">(</span><span class="n">_</span> <span class="nv">tag</span><span class="p">:</span> <span class="kt">Tag</span><span class="p">,</span> <span class="nv">editMode</span><span class="p">:</span> <span class="kt">EditMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">inactive</span><span class="p">)</span> <span class="p">{</span>
         <span class="k">self</span><span class="o">.</span><span class="n">editMode</span> <span class="o">=</span> <span class="n">editMode</span>
         <span class="k">self</span><span class="o">.</span><span class="n">tag</span> <span class="o">=</span> <span class="n">tag</span>
         <span class="k">self</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">tag</span><span class="o">.</span><span class="n">toString</span>
      <span class="p">}</span>
   
      <span class="c1">// MARK: Helper Function</span>
      <span class="c1">/// Converts into a tag, if valid</span>
      <span class="c1">/// - parameter from: the text to attempt to convert</span>
      <span class="kd">func</span> <span class="nf">convertTagIfValid</span><span class="p">(</span><span class="n">from</span> <span class="nv">string</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
         <span class="k">guard</span> <span class="k">let</span> <span class="nv">convertedTag</span> <span class="o">=</span> <span class="n">string</span><span class="o">.</span><span class="nf">toTag</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">self</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">tag</span><span class="o">.</span><span class="n">toString</span>
            <span class="k">return</span>
         <span class="p">}</span>
         <span class="k">self</span><span class="o">.</span><span class="n">tag</span> <span class="o">=</span> <span class="n">convertedTag</span>
         <span class="k">self</span><span class="o">.</span><span class="n">editMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">inactive</span>
      <span class="p">}</span>
   <span class="p">}</span>

   <span class="c1">// ...</span>
<span class="p">}</span></code></pre></figure>

<p>For the <code class="language-plaintext highlighter-rouge">TagView</code>, we’ll want to be able to detect whether the app is in or not in edit mode, which will trigger whether or not the view should show a static <code class="language-plaintext highlighter-rouge">Tag</code> model view or whether it should show a text field that will trigger an attempt to convert the tag and save it, if appropriate.</p>

<p>Let’s breakdown the elements:</p>

<h4 id="model-properties">Model properties</h4>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">   <span class="c1">// MARK: Properties</span>
   <span class="c1">/// The edit state of the view</span>
   <span class="k">var</span> <span class="nv">editMode</span><span class="p">:</span> <span class="kt">EditMode</span>
   <span class="c1">/// The actual tag</span>
   <span class="kd">private(set)</span> <span class="k">var</span> <span class="nv">tag</span><span class="p">:</span> <span class="kt">Tag</span> <span class="p">{</span>
    <span class="k">didSet</span> <span class="p">{</span>
      <span class="n">text</span> <span class="o">=</span> <span class="n">tag</span><span class="o">.</span><span class="n">toString</span>
    <span class="p">}</span>
   <span class="p">}</span>
   <span class="c1">/// The entered text to be converted into the tag</span>
   <span class="k">var</span> <span class="nv">text</span><span class="p">:</span> <span class="kt">String</span>
   
   <span class="c1">// MARK: Computed Properties</span>
   <span class="k">var</span> <span class="nv">isEditing</span><span class="p">:</span> <span class="kt">Bool</span> <span class="p">{</span>
    <span class="n">editMode</span> <span class="o">==</span> <span class="o">.</span><span class="n">active</span>
   <span class="p">}</span></code></pre></figure>

<p>These are all the properties that the <code class="language-plaintext highlighter-rouge">ViewModel</code> will be able to support and the <code class="language-plaintext highlighter-rouge">View</code> will be able to read/interact with. The <code class="language-plaintext highlighter-rouge">editMode</code> can be set to <code class="language-plaintext highlighter-rouge">.inactive</code> or <code class="language-plaintext highlighter-rouge">.active</code> to indicate whether or not the <code class="language-plaintext highlighter-rouge">View</code> will be an editable or non-editable view and toggle between them. The <code class="language-plaintext highlighter-rouge">tag</code> conforms to the <code class="language-plaintext highlighter-rouge">Tag</code> model and the editable text field will leverage it. The <code class="language-plaintext highlighter-rouge">textTag</code> is a string value that is populated by the <code class="language-plaintext highlighter-rouge">tag</code> and this text value is used to convert into a <code class="language-plaintext highlighter-rouge">tag</code>. There’s also a helper computed property to tell whether the view is in edit mode.</p>

<h4 id="initializer">Initializer</h4>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">  <span class="nf">init</span><span class="p">(</span><span class="n">_</span> <span class="nv">tag</span><span class="p">:</span> <span class="kt">Tag</span><span class="p">,</span> <span class="nv">editMode</span><span class="p">:</span> <span class="kt">EditMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">inactive</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">self</span><span class="o">.</span><span class="n">editMode</span> <span class="o">=</span> <span class="n">editMode</span>
    <span class="k">self</span><span class="o">.</span><span class="n">tag</span> <span class="o">=</span> <span class="n">tag</span>
    <span class="k">self</span><span class="o">.</span><span class="n">textTag</span> <span class="o">=</span> <span class="n">tag</span><span class="o">.</span><span class="n">toString</span>
  <span class="p">}</span></code></pre></figure>

<p>This simplifies the initialization by ensuring that the textTag is initialized to the string value of the <code class="language-plaintext highlighter-rouge">Tag</code> model, when the <code class="language-plaintext highlighter-rouge">tag</code> is initialized.</p>

<h4 id="conversion-functionality">Conversion Functionality</h4>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">  <span class="kd">func</span> <span class="nf">convertTagIfValid</span><span class="p">(</span><span class="n">from</span> <span class="nv">string</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">guard</span> <span class="k">let</span> <span class="nv">convertedTag</span> <span class="o">=</span> <span class="n">string</span><span class="o">.</span><span class="nf">toTag</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
      <span class="k">self</span><span class="o">.</span><span class="n">textTag</span> <span class="o">=</span> <span class="n">tag</span><span class="o">.</span><span class="n">toString</span>
      <span class="k">return</span>
    <span class="p">}</span>
    <span class="k">self</span><span class="o">.</span><span class="n">tag</span> <span class="o">=</span> <span class="n">convertedTag</span>
    <span class="k">self</span><span class="o">.</span><span class="n">editMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">inactive</span>
  <span class="p">}</span></code></pre></figure>

<p>If the entered text converts properly to a <code class="language-plaintext highlighter-rouge">Tag</code> entity, the tag and text are updated. (This ensures that the text is normalized. And things like <code class="language-plaintext highlighter-rouge">@tag()</code> properly becomes <code class="language-plaintext highlighter-rouge">@tag</code>.) If it doesn’t, the previously saved tag is used to reset the text. Edit mode only turns off, if the text converts properly to the <code class="language-plaintext highlighter-rouge">tag</code>.</p>

<h4 id="using-the-viewmodel-in-our-swiftui-view">Using the <code class="language-plaintext highlighter-rouge">ViewModel</code> in our SwiftUI <code class="language-plaintext highlighter-rouge">View</code></h4>

<p>Now, we see how the <code class="language-plaintext highlighter-rouge">View</code> uses the <code class="language-plaintext highlighter-rouge">ViewModel</code>, so that it can be used.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">@State</span> <span class="k">var</span> <span class="nv">viewModel</span><span class="p">:</span> <span class="kt">ViewModel</span>

<span class="c1">// How it will be initialized...</span>
<span class="kt">TagView</span><span class="p">(</span><span class="nv">viewModel</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="kt">Constants</span><span class="o">.</span><span class="kt">MockTag</span><span class="o">.</span><span class="n">test</span><span class="p">))</span></code></pre></figure>

<h4 id="how-its-used-in-the-swiftui-view">How it’s used in the SwiftUI <code class="language-plaintext highlighter-rouge">View</code></h4>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">   <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
     <span class="k">if</span> <span class="n">viewModel</span><span class="o">.</span><span class="n">isEditing</span> <span class="p">{</span>
       <span class="n">tagEditView</span>
     <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
       <span class="n">tagView</span>
     <span class="p">}</span>
   <span class="p">}</span>
   <span class="c1">// Accessing the property</span>
   <span class="nf">tagView</span><span class="p">(</span><span class="nv">tag</span><span class="p">:</span> <span class="n">viewModel</span><span class="o">.</span><span class="n">tag</span><span class="p">)</span>
   <span class="c1">// Binding the property</span>
   <span class="kt">TextField</span><span class="p">(</span><span class="kt">Constants</span><span class="o">.</span><span class="kt">Tag</span><span class="o">.</span><span class="n">placeholder</span><span class="p">,</span>
             <span class="nv">text</span><span class="p">:</span> <span class="err">$</span><span class="n">viewModel</span><span class="o">.</span><span class="n">text</span><span class="p">,</span>
             <span class="nv">axis</span><span class="p">:</span> <span class="o">.</span><span class="n">vertical</span><span class="p">)</span>
   <span class="o">.</span><span class="n">onSubmit</span> <span class="p">{</span>
      <span class="n">viewModel</span><span class="o">.</span><span class="nf">convertTagIfValid</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="n">viewModel</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
   <span class="p">}</span></code></pre></figure>

<p>The conversion of the <code class="language-plaintext highlighter-rouge">TagView</code> basically extracted individual <code class="language-plaintext highlighter-rouge">@State</code> objects into an an <code class="language-plaintext highlighter-rouge">@Observable</code> <em>viewModel</em>. And those objects are interacted with through that <code class="language-plaintext highlighter-rouge">@Observable</code> <em>viewModel</em>.</p>

<h4 id="navigation">Navigation</h4>

<p>Let’s examine the more complicated MVVM models for our the <code class="language-plaintext highlighter-rouge">TaskView</code> and the <code class="language-plaintext highlighter-rouge">TaskDetailView</code>, wrapped within a <code class="language-plaintext highlighter-rouge">TaskMasterAndDetailView</code>. These illustrate communication between multiple views, passing data between them–as the user adds a new task from the task view, which can be edited and verified in the detail view.</p>

<p>We do this by defining a viewModel in the master view and bind it for the children (<code class="language-plaintext highlighter-rouge">TaskView</code> and the <code class="language-plaintext highlighter-rouge">TaskDetailView</code>) to interact with.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">@Observable</span>
<span class="kd">class</span> <span class="kt">ViewModel</span> <span class="p">{</span>
   <span class="k">var</span> <span class="nv">selectedItem</span><span class="p">:</span> <span class="kt">IdentifiedTMType</span><span class="p">?</span>
   <span class="k">var</span> <span class="nv">responseType</span><span class="p">:</span> <span class="kt">DetailResponseType</span><span class="p">?</span>
   <span class="k">var</span> <span class="nv">detailMode</span><span class="p">:</span> <span class="kt">DetailMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">add</span>
   <span class="k">var</span> <span class="nv">items</span><span class="p">:</span> <span class="p">[</span><span class="kt">IdentifiedTMType</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>

   <span class="nf">init</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="p">{</span><span class="o">...</span><span class="p">}</span>

   <span class="c1">// MARK: - Helper Methods</span>
   <span class="kd">func</span> <span class="nf">addItem</span><span class="p">(</span><span class="n">from</span> <span class="nv">text</span><span class="p">:</span> <span class="kt">String</span><span class="p">?)</span> <span class="p">{</span><span class="o">...</span><span class="p">}</span>

   <span class="kd">func</span> <span class="nf">select</span><span class="p">(</span><span class="nv">item</span><span class="p">:</span> <span class="kt">IdentifiedTMType</span><span class="p">)</span> <span class="p">{</span><span class="o">...</span><span class="p">}</span>

   <span class="kd">func</span> <span class="nf">process</span><span class="p">(</span><span class="n">_</span> <span class="nv">response</span><span class="p">:</span> <span class="kt">DetailResponseType</span><span class="p">?)</span> <span class="p">{</span><span class="o">...</span><span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>The <code class="language-plaintext highlighter-rouge">IdentifiedTMType</code> wraps our <code class="language-plaintext highlighter-rouge">TMType</code> in an <code class="language-plaintext highlighter-rouge">Identifiable</code> and <code class="language-plaintext highlighter-rouge">Hashable</code> wrapper. This allows us to easily edit or delete a specific TMType object.
The <code class="language-plaintext highlighter-rouge">DetailResponseType</code> is an enum that allows the detail view to pass back to the master view whether the item was canceled, saved, or deleted.
The <code class="language-plaintext highlighter-rouge">DetailMode</code> allows the <code class="language-plaintext highlighter-rouge">TaskDetailView</code> to toggle between the <em>add</em> mode or the <em>edit</em> mode.</p>

<p>Our main property is <code class="language-plaintext highlighter-rouge">items</code> array.</p>

<p>Then we have our helper methods to wrap the functionality needed throughout the app: <code class="language-plaintext highlighter-rouge">addItem</code> to add a new item, <code class="language-plaintext highlighter-rouge">select</code> a known <code class="language-plaintext highlighter-rouge">IdentifiedItem</code>, and <code class="language-plaintext highlighter-rouge">process</code> the response from the <code class="language-plaintext highlighter-rouge">TaskDetailView</code>.</p>

<p>Each of the children, <code class="language-plaintext highlighter-rouge">TaskView</code> and <code class="language-plaintext highlighter-rouge">TaskDetailView</code> just bind the master’s view model and leverage that.</p>

<p>People can look over the <code class="language-plaintext highlighter-rouge">TextView</code> to see how a view model can interact directly with a <code class="language-plaintext highlighter-rouge">TextEditor</code>, handle the flow between entered text that may or may not successfully convert into a valid <code class="language-plaintext highlighter-rouge">TMType</code>, as well as presenting an error message for the user. This is relatively straightforward (similar to the <code class="language-plaintext highlighter-rouge">TagView</code>), as it is a single view/view model pair with no navigation: text is entered; upon submission, an attempt to parse the text into <code class="language-plaintext highlighter-rouge">TMType</code> models and normalize them into parents and children models, as we have discussed in previous blog posts. If there are problems, an error message will be presented to the user and the type and text cleaned up appropriately.</p>

<h3 id="mvvm-flow">MVVM Flow</h3>

<p>Seeing the MVVM Flow in action:</p>

<p><img src="/img/MVVM-2025-06-24.gif" alt="MVVM Flow" /></p>

<h2 id="mvvm-with-combine">MVVM (with combine)</h2>

<p>There’s a variant of the MVVM with combine that I saw in <a href="https://bocato.medium.com">Eduardo Sanches Bocato’s</a> medium article on <a href="https://bocato.medium.com/improving-mvvm-forms-in-swiftui-14b032065095">Improving MVVM (with Combine)</a>.</p>

<p>To sum up, there is an <code class="language-plaintext highlighter-rouge">Equatable</code> state object encapsulating the model properties. The <code class="language-plaintext highlighter-rouge">StateBindingViewModel</code> wraps the state object in functionality to allow users to access the state’s properties, bind the state’s properties so that it be read/write from SwiftUI views, update the state’s properties, as well as ensure notification on state changes.</p>

<h3 id="mvvm-combine-sample-code">MVVM (Combine) Sample Code</h3>

<p>We won’t be discussing it, but I converted the MVVM Sample Code into <a href="https://github.com/Jp4Mobile/SampleCode/tree/main/posts/projects/Architect-2025-05-25/MVVM-Combine/TaskManager">MVVM (Combine) Sample Code from the repo</a>.</p>

<h3 id="mvvm-combine-flow">MVVM-Combine Flow</h3>

<p>Seeing the MVVM-Combine Flow in action:</p>

<p><img src="/img/MVVM-Combine-2025-06-24.gif" alt="MVVM-Combine Flow" /></p>

<h2 id="tca">TCA</h2>

<p><strong>T</strong>he <strong>C</strong>omposable <strong>A</strong>rchitecture is a Swift adaption of the Redux framework. It follows a <em>Unidirectional Data Flow</em> model to ensure state management and a single source of truth.</p>

<p>Point Free has some really useful tutorials, if readers would like to find out more than what I’ve done: <a href="https://pointfreeco.github.io/swift-composable-architecture/main/tutorials/meetcomposablearchitecture/">TCA tutorials</a>.</p>

<p>Within TCA, the three components flow from one to another in a single direction:</p>

<h3 id="state---view---action">State - View - Action</h3>

<p><code class="language-plaintext highlighter-rouge">State</code> ➡️ <code class="language-plaintext highlighter-rouge">View</code> ➡️ <code class="language-plaintext highlighter-rouge">Action</code> ↩️</p>

<p>Each flows from one to the other in a single direction: <code class="language-plaintext highlighter-rouge">State</code> to <code class="language-plaintext highlighter-rouge">View</code> to <code class="language-plaintext highlighter-rouge">Action</code> and back to <code class="language-plaintext highlighter-rouge">State</code>.</p>

<h4 id="state">State</h4>

<p>The state of your app. This can contain the data/model to ensure that the state is contained. As state can also refer to a specific view, it could also refer to the state of the view itself.</p>

<h3 id="view-1">View</h3>

<p>What is seen on the screen by the user based upon the <strong>State</strong>, and their manipulations of the elements on the screen become an <strong>Action</strong>. (The initial <strong>State</strong> and the <strong>Reducer</strong>–which we’ll discuss more in a moment–are in the <strong>Store</strong>, which we discuss a bit below.)</p>

<h4 id="action">Action</h4>

<p>All possible supported actions that the app can perform. As an action can also refer to a specific view, it could also refer to all of the available actions that the view can perform. This can be a button being tapped, a text field having text entered, a toggle toggling, etc…</p>

<h3 id="diving-a-little-deeper-into-tca">Diving a little deeper into TCA</h3>

<p>But how does that work, you may wonder.</p>

<h4 id="reducer">Reducer</h4>

<p>The magic of <strong>TCA</strong> is that all of the business logic is in the reducer. These take an <strong>Action</strong>, update the <strong>State</strong> and return an <strong>Effect</strong>. At this point, I’ve probably lost you, so I’ll put in an example:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">State</span><span class="p">:</span> <span class="kt">Equatable</span> <span class="p">{</span>
  <span class="k">var</span> <span class="nv">lastSavedState</span><span class="p">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">""</span>
  <span class="k">var</span> <span class="nv">text</span><span class="p">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">""</span>
<span class="p">}</span>

<span class="kd">enum</span> <span class="kt">Action</span> <span class="p">{</span>
  <span class="k">case</span> <span class="nf">setText</span><span class="p">(</span><span class="kt">String</span><span class="p">)</span>
  <span class="k">case</span> <span class="n">cancelButtonTapped</span>
  <span class="k">case</span> <span class="n">saveButtonTapped</span>
<span class="p">}</span>

<span class="c1">// Extracted reducer</span>
<span class="kt">Reduce</span> <span class="p">{</span> <span class="n">state</span><span class="p">,</span> <span class="n">action</span> <span class="p">}</span> <span class="k">in</span>
   <span class="k">switch</span> <span class="n">action</span> <span class="p">{</span>
      <span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">setText</span><span class="p">(</span><span class="n">text</span><span class="p">):</span>
        <span class="n">state</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">text</span>
        <span class="k">return</span> <span class="o">.</span><span class="k">none</span>
      <span class="k">case</span> <span class="o">.</span><span class="nv">cancelButtonTapped</span><span class="p">:</span>
        <span class="n">state</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">state</span><span class="o">.</span><span class="n">lastSavedState</span>
        <span class="k">return</span> <span class="o">.</span><span class="k">none</span>
      <span class="k">case</span> <span class="o">.</span><span class="nv">saveButtonTapped</span><span class="p">:</span>
        <span class="n">state</span><span class="o">.</span><span class="n">lastSavedState</span> <span class="o">=</span> <span class="n">state</span><span class="o">.</span><span class="n">text</span>
        <span class="k">return</span> <span class="o">.</span><span class="k">none</span>
   <span class="p">}</span></code></pre></figure>

<p>In this case, we have a state that includes two properties: <code class="language-plaintext highlighter-rouge">lastSavedState</code> and <code class="language-plaintext highlighter-rouge">text</code>. We have a simple set of actions, the user can enter text, and tap on either a cancel or save button.</p>

<p>All of the business logic is in the reducer.</p>

<ul>
  <li>Entering text updates the text property in the <strong>State</strong>.</li>
  <li>Tapping the cancel button, sets the text back to the last saved state.</li>
  <li>Tapping the save button, sets the last saved state.</li>
</ul>

<h4 id="effect">Effect</h4>

<p>An effect is a wrapper around any piece of work or a task such a network call or an asynchronous task. This could result in a new <em>Action</em> that can be fed back into the <em>Reducer</em>.</p>

<p>In our example above, each case returns <code class="language-plaintext highlighter-rouge">.none</code> ending the flow, but in a more complicated model, we may have an error state such as this:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">State</span><span class="p">:</span> <span class="kt">Equatable</span> <span class="p">{</span>
  <span class="k">var</span> <span class="nv">errorMessage</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span>
  <span class="k">var</span> <span class="nv">lastSavedState</span><span class="p">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">""</span>
  <span class="k">var</span> <span class="nv">text</span><span class="p">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">""</span>
<span class="p">}</span>

<span class="kd">enum</span> <span class="kt">Action</span> <span class="p">{</span>
  <span class="k">case</span> <span class="nf">setText</span><span class="p">(</span><span class="kt">String</span><span class="p">)</span>
  <span class="k">case</span> <span class="n">cancelButtonTapped</span>
  <span class="k">case</span> <span class="n">saveButtonTapped</span>
  <span class="k">case</span> <span class="nf">error</span><span class="p">(</span><span class="kt">String</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// Extracted reducer</span>
<span class="kt">Reduce</span> <span class="p">{</span> <span class="n">state</span><span class="p">,</span> <span class="n">action</span> <span class="p">}</span> <span class="k">in</span>
   <span class="k">switch</span> <span class="n">action</span> <span class="p">{</span>
      <span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">setText</span><span class="p">(</span><span class="n">text</span><span class="p">):</span>
        <span class="n">state</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">text</span>
        <span class="k">return</span> <span class="o">.</span><span class="k">none</span>
      <span class="k">case</span> <span class="o">.</span><span class="nv">cancelButtonTapped</span><span class="p">:</span>
        <span class="n">state</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">state</span><span class="o">.</span><span class="n">lastSavedState</span>
        <span class="k">return</span> <span class="o">.</span><span class="k">none</span>
      <span class="k">case</span> <span class="o">.</span><span class="nv">saveButtonTapped</span><span class="p">:</span>
        <span class="k">guard</span> <span class="k">let</span> <span class="nv">validState</span> <span class="o">=</span> <span class="nf">validate</span><span class="p">(</span><span class="n">state</span><span class="o">.</span><span class="n">text</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
          <span class="k">return</span> <span class="o">.</span><span class="nf">send</span><span class="p">(</span><span class="o">.</span><span class="nf">error</span><span class="p">(</span><span class="kt">Constant</span><span class="o">.</span><span class="kt">InvalidState</span><span class="p">))</span>
        <span class="p">}</span>
        
        <span class="n">state</span><span class="o">.</span><span class="n">lastSavedState</span> <span class="o">=</span> <span class="n">validState</span>
        <span class="k">return</span> <span class="o">.</span><span class="k">none</span>
      <span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">error</span><span class="p">(</span><span class="n">errorMessage</span><span class="p">):</span>
        <span class="n">state</span><span class="o">.</span><span class="n">errorMessage</span> <span class="o">=</span> <span class="n">errorMessage</span>
        <span class="k">return</span> <span class="o">.</span><span class="k">none</span>
   <span class="p">}</span></code></pre></figure>

<p>This illustrates the way a reducer can return a different action so that the reducer will then process that new action.</p>

<h4 id="environment">Environment</h4>

<p>This layer is where network, persistent storage, OS service functionality goes. We’re not doing much with that, but we will as we expand are sample code.</p>

<h4 id="store">Store</h4>

<p>This wraps everything together, including the initial state and the reducer, as well as the initializer. This is bound into the <strong>View</strong>.</p>

<h3 id="tca-sample-code">TCA Sample Code</h3>

<p>All of this will be based upon the <a href="https://github.com/Jp4Mobile/SampleCode/tree/main/posts/projects/Architect-2025-05-25/TCA/TaskManager">TCA Sample Code from the repo</a>.</p>

<p>Let’s examine our TCA version of the <code class="language-plaintext highlighter-rouge">TagView</code> that we built previously, as we incorporate the <code class="language-plaintext highlighter-rouge">TCA</code> methodology. (There are three different versions of it: <code class="language-plaintext highlighter-rouge">TCASimpleBindingTagFeature</code>, <code class="language-plaintext highlighter-rouge">TCATagFeatureFirstPass</code>, and <code class="language-plaintext highlighter-rouge">TCATagFeatureFinalPass</code>.)</p>

<p>In <code class="language-plaintext highlighter-rouge">TCASimpleBindingTagFeature</code>, there aren’t specific <code class="language-plaintext highlighter-rouge">Action</code> elements. Instead, we just bind the state. This makes it so that the <code class="language-plaintext highlighter-rouge">View</code> directly changes the <code class="language-plaintext highlighter-rouge">State</code>. This feels like it breaks the spirit of TCA, so I tried to do better.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// MARK: Action</span>
<span class="kd">enum</span> <span class="kt">Action</span><span class="p">:</span> <span class="kt">BindableAction</span><span class="p">,</span> <span class="kt">Sendable</span> <span class="p">{</span>
   <span class="k">case</span> <span class="nf">binding</span><span class="p">(</span><span class="kt">BindingAction</span><span class="o">&lt;</span><span class="kt">State</span><span class="o">&gt;</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// MARK: Body (Reducer)</span>
<span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">Reducer</span><span class="o">&lt;</span><span class="kt">State</span><span class="p">,</span> <span class="kt">Action</span><span class="o">&gt;</span> <span class="p">{</span>
   <span class="kt">BindingReducer</span><span class="p">()</span>
<span class="p">}</span>

<span class="c1">// And in our view directly affecting the State:</span>
   <span class="kt">TextField</span><span class="p">(</span><span class="kt">Constants</span><span class="o">.</span><span class="kt">Tag</span><span class="o">.</span><span class="n">placeholder</span><span class="p">,</span>
             <span class="nv">text</span><span class="p">:</span> <span class="err">$</span><span class="n">store</span><span class="o">.</span><span class="n">text</span><span class="p">,</span>
             <span class="nv">axis</span><span class="p">:</span> <span class="o">.</span><span class="n">vertical</span><span class="p">)</span>
   <span class="o">.</span><span class="n">onSubmit</span> <span class="p">{</span>
     <span class="nf">onTextFieldSubmission</span><span class="p">()</span>
   <span class="p">}</span>

<span class="kd">func</span> <span class="nf">onTextFieldSubmission</span><span class="p">()</span> <span class="p">{</span>
   <span class="k">defer</span> <span class="p">{</span>
     <span class="n">store</span><span class="o">.</span><span class="n">editMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">inactive</span>
   <span class="p">}</span>

   <span class="k">guard</span> <span class="k">let</span> <span class="nv">updatedTag</span> <span class="o">=</span> <span class="n">store</span><span class="o">.</span><span class="n">text</span><span class="o">.</span><span class="nf">toTag</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
     <span class="c1">// TODO: Present an error</span>
     <span class="n">store</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">store</span><span class="o">.</span><span class="n">tag</span><span class="o">.</span><span class="n">toString</span>
     <span class="k">return</span>
   <span class="p">}</span>
   <span class="n">store</span><span class="o">.</span><span class="n">tag</span> <span class="o">=</span> <span class="n">updatedTag</span>
<span class="p">}</span></code></pre></figure>

<p>In <code class="language-plaintext highlighter-rouge">TCATagFeatureFirstPass</code>, we now have an <code class="language-plaintext highlighter-rouge">Action</code> enum defined with actual things that a user can do:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">enum</span> <span class="kt">Action</span> <span class="p">{</span>
   <span class="k">case</span> <span class="n">tapped</span>
   <span class="k">case</span> <span class="nf">entered</span><span class="p">(</span><span class="kt">String</span><span class="p">)</span>
   <span class="k">case</span> <span class="nf">submitted</span><span class="p">(</span><span class="kt">String</span><span class="p">)</span>
   <span class="k">case</span> <span class="nf">saved</span><span class="p">(</span><span class="kt">Tag</span><span class="p">)</span>
   <span class="k">case</span> <span class="nf">error</span><span class="p">(</span><span class="kt">String</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// MARK: Body (Reducer)</span>
<span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">ReducerOf</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;</span> <span class="p">{</span>
   <span class="kt">Reduce</span> <span class="p">{</span> <span class="n">state</span><span class="p">,</span> <span class="n">action</span> <span class="k">in</span>
     <span class="k">switch</span> <span class="n">action</span> <span class="p">{</span>
     <span class="k">case</span> <span class="o">.</span><span class="nv">tapped</span><span class="p">:</span>
       <span class="n">state</span><span class="o">.</span><span class="n">editMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">active</span>
       <span class="k">return</span> <span class="o">.</span><span class="k">none</span>

     <span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">entered</span><span class="p">(</span><span class="n">text</span><span class="p">):</span>
       <span class="n">state</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">text</span>
       <span class="k">return</span> <span class="o">.</span><span class="k">none</span>

     <span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">submitted</span><span class="p">(</span><span class="n">text</span><span class="p">):</span>
       <span class="k">guard</span> <span class="k">let</span> <span class="nv">newTag</span> <span class="o">=</span> <span class="n">text</span><span class="o">.</span><span class="nf">toTag</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
         <span class="k">let</span> <span class="nv">errorMessage</span> <span class="o">=</span> <span class="s">"Unable to convert &lt;</span><span class="se">\(</span><span class="n">text</span><span class="se">)</span><span class="s">&gt; into a tag."</span>
         <span class="k">return</span> <span class="o">.</span><span class="nf">send</span><span class="p">(</span><span class="o">.</span><span class="nf">error</span><span class="p">(</span><span class="n">errorMessage</span><span class="p">))</span>
       <span class="p">}</span>

       <span class="k">return</span> <span class="o">.</span><span class="nf">send</span><span class="p">(</span><span class="o">.</span><span class="nf">saved</span><span class="p">(</span><span class="n">newTag</span><span class="p">))</span>

     <span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">error</span><span class="p">(</span><span class="n">errorMessage</span><span class="p">):</span>
       <span class="n">state</span><span class="o">.</span><span class="n">errorMessage</span> <span class="o">=</span> <span class="n">errorMessage</span>
       <span class="k">return</span> <span class="o">.</span><span class="k">none</span>

     <span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">saved</span><span class="p">(</span><span class="n">newTag</span><span class="p">):</span>
       <span class="n">state</span><span class="o">.</span><span class="n">tag</span> <span class="o">=</span> <span class="n">newTag</span>
       <span class="n">state</span><span class="o">.</span><span class="n">errorMessage</span> <span class="o">=</span> <span class="kc">nil</span>
       <span class="k">return</span> <span class="o">.</span><span class="k">none</span>
     <span class="p">}</span>
   <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>We can clean that up a bit more. And our state having <code class="language-plaintext highlighter-rouge">didSet</code> functionality ensures that the business logic is in two places, the reducer and the state. Let’s clean that up a bit more in our final version of the <code class="language-plaintext highlighter-rouge">TCATagView</code>:</p>

<h4 id="reducer-1">Reducer</h4>

<p>We define our state and the actions available. You’ll note that I’ve taken the liberty of leveraging our previous work with the <code class="language-plaintext highlighter-rouge">TextView</code> code from the <em>MVVM</em> and <em>MVVM-Combine</em> examples.</p>

<p>In the <strong>State</strong>, we see the properties of the feature:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">@Reducer</span>
<span class="kd">struct</span> <span class="kt">TagConverter</span> <span class="p">{</span>
   <span class="c1">// MARK: State</span>
   <span class="kd">@ObservableState</span>
   <span class="kd">struct</span> <span class="kt">State</span><span class="p">:</span> <span class="kt">Equatable</span> <span class="p">{</span>
      <span class="c1">// MARK: Properties</span>
      <span class="c1">/// An optional error message to present to the user, if present</span>
      <span class="k">var</span> <span class="nv">errorMessage</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span>
      <span class="c1">/// The edit state of the view</span>
      <span class="k">var</span> <span class="nv">editMode</span><span class="p">:</span> <span class="kt">EditMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">inactive</span>
      <span class="c1">/// The actual tag</span>
      <span class="k">var</span> <span class="nv">tag</span><span class="p">:</span> <span class="kt">Tag</span>
      <span class="c1">/// The entered text to be converted into the tag</span>
      <span class="k">var</span> <span class="nv">text</span><span class="p">:</span> <span class="kt">String</span>

      <span class="c1">// MARK: Computed Properties</span>
      <span class="k">var</span> <span class="nv">isEditing</span><span class="p">:</span> <span class="kt">Bool</span> <span class="p">{</span>
         <span class="n">editMode</span> <span class="o">==</span> <span class="o">.</span><span class="n">active</span>
      <span class="p">}</span>
      <span class="k">var</span> <span class="nv">shouldPresentError</span><span class="p">:</span> <span class="kt">Bool</span> <span class="p">{</span>
         <span class="n">errorMessage</span> <span class="o">!=</span> <span class="kc">nil</span>
      <span class="p">}</span>

      <span class="c1">// MARK: Initializer</span>
      <span class="nf">init</span><span class="p">(</span><span class="nv">tag</span><span class="p">:</span> <span class="kt">Tag</span><span class="p">)</span> <span class="p">{</span>
         <span class="k">self</span><span class="o">.</span><span class="n">tag</span> <span class="o">=</span> <span class="n">tag</span>
         <span class="k">self</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">tag</span><span class="o">.</span><span class="n">toString</span>
      <span class="p">}</span>
   <span class="p">}</span></code></pre></figure>

<p>In the <strong>Action</strong>, we see the behaviors/functionality supported in the feature:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">   <span class="c1">// MARK: Action</span>
   <span class="kd">enum</span> <span class="kt">Action</span> <span class="p">{</span>
      <span class="c1">/// When the user taps on the tag to initiate editing</span>
      <span class="k">case</span> <span class="n">tapped</span>
      <span class="c1">/// When the user changes the text in the text field</span>
      <span class="k">case</span> <span class="nf">entered</span><span class="p">(</span><span class="kt">String</span><span class="p">)</span>
      <span class="c1">/// When the user taps return to submit the answer</span>
      <span class="k">case</span> <span class="n">submitted</span>
   <span class="p">}</span></code></pre></figure>

<p>In the <strong>Body</strong> of the reducer, we see the flow from action to action supported in the feature:</p>

<p>A future developer can easily see the flow of the feature:</p>

<ul>
  <li>A user taps a view, which toggles the editMode into an active state.</li>
  <li>When editable, a user can enter text into the text field, which can be submitted.</li>
  <li>When submitted, an attempt is made to convert the text into a valid <code class="language-plaintext highlighter-rouge">Tag</code> object.</li>
  <li>If not successful, an error is presented.</li>
  <li>If successful, the tag is updated, the text normalized, and any presented errors are cleared away.</li>
</ul>

<p>(You’ll note that the <code class="language-plaintext highlighter-rouge">State</code> object, leverages <code class="language-plaintext highlighter-rouge">didSet</code> functionality to ensure that the <code class="language-plaintext highlighter-rouge">text</code> and <code class="language-plaintext highlighter-rouge">editMode</code> properties are updated appropriately on changes to the <code class="language-plaintext highlighter-rouge">task</code> and <code class="language-plaintext highlighter-rouge">errorMessage</code> state properties.)</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">   <span class="c1">// MARK: Body (Reducer)</span>
   <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">ReducerOf</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;</span> <span class="p">{</span>
     <span class="kt">Reduce</span> <span class="p">{</span> <span class="n">state</span><span class="p">,</span> <span class="n">action</span> <span class="k">in</span>
       <span class="k">switch</span> <span class="n">action</span> <span class="p">{</span>
       <span class="k">case</span> <span class="o">.</span><span class="nv">tapped</span><span class="p">:</span>
         <span class="n">state</span><span class="o">.</span><span class="n">editMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">active</span>
         <span class="k">return</span> <span class="o">.</span><span class="k">none</span>
   
       <span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">entered</span><span class="p">(</span><span class="n">text</span><span class="p">):</span>
         <span class="n">state</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">text</span>
         <span class="k">return</span> <span class="o">.</span><span class="k">none</span>
   
       <span class="k">case</span> <span class="o">.</span><span class="nv">submitted</span><span class="p">:</span>
         <span class="c1">// As you can see, all the logic is now in the reducer.</span>
         <span class="c1">// Extraneous actions are removed so this is pared down to just what</span>
         <span class="c1">// is needed.</span>
         <span class="k">guard</span> <span class="k">let</span> <span class="nv">newTag</span> <span class="o">=</span> <span class="n">state</span><span class="o">.</span><span class="n">text</span><span class="o">.</span><span class="nf">toTag</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
            <span class="c1">// In the error case, the error is created and state set up to</span>
            <span class="c1">// present it all from within the submission.</span>
            <span class="k">let</span> <span class="nv">errorMessage</span> <span class="o">=</span> <span class="s">"Unable to convert &lt;</span><span class="se">\(</span><span class="n">state</span><span class="o">.</span><span class="n">text</span><span class="se">)</span><span class="s">&gt; into a tag."</span>
            <span class="n">state</span><span class="o">.</span><span class="n">errorMessage</span> <span class="o">=</span> <span class="n">errorMessage</span>
            <span class="k">return</span> <span class="o">.</span><span class="k">none</span>
         <span class="p">}</span>
   
         <span class="c1">// In the success case, we set the updated tag.</span>
         <span class="n">state</span><span class="o">.</span><span class="n">tag</span> <span class="o">=</span> <span class="n">newTag</span>
         <span class="n">state</span><span class="o">.</span><span class="n">errorMessage</span> <span class="o">=</span> <span class="kc">nil</span>
         <span class="c1">// Clear the state</span>
         <span class="n">state</span><span class="o">.</span><span class="n">editMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">inactive</span>
         <span class="n">state</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">newTag</span><span class="o">.</span><span class="n">toString</span>
         <span class="k">return</span> <span class="o">.</span><span class="k">none</span>
       <span class="p">}</span>
     <span class="p">}</span>
   <span class="p">}</span></code></pre></figure>

<h4 id="view-2">View</h4>

<p>The magic happens in the the <code class="language-plaintext highlighter-rouge">View</code>. This is where we bind our store and access it.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">TCATagView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
   <span class="kd">@Bindable</span>
   <span class="k">var</span> <span class="nv">store</span><span class="p">:</span> <span class="kt">StoreOf</span><span class="o">&lt;</span><span class="kt">TagConverter</span><span class="o">&gt;</span>
   <span class="kd">@ScaledMetric</span><span class="p">(</span><span class="nv">relativeTo</span><span class="p">:</span> <span class="o">.</span><span class="n">caption</span><span class="p">)</span> <span class="kd">private</span> <span class="k">var</span> <span class="nv">scaledPadding</span> <span class="o">=</span> <span class="kt">Spacing</span><span class="o">.</span><span class="k">default</span>

    <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="kt">VStack</span> <span class="p">{</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nv">errorMessage</span> <span class="o">=</span> <span class="n">store</span><span class="o">.</span><span class="n">errorMessage</span> <span class="p">{</span>
                <span class="kt">Text</span><span class="p">(</span><span class="n">errorMessage</span><span class="p">)</span>
                    <span class="o">.</span><span class="nf">font</span><span class="p">(</span><span class="o">.</span><span class="n">caption</span><span class="p">)</span>
                    <span class="o">.</span><span class="nf">foregroundStyle</span><span class="p">(</span><span class="o">.</span><span class="n">red</span><span class="p">)</span>
            <span class="p">}</span>
            <span class="k">if</span> <span class="n">store</span><span class="o">.</span><span class="n">isEditing</span> <span class="p">{</span>
                <span class="n">editTagView</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="n">tagView</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
   <span class="c1">// ...</span>
   <span class="c1">// Accessing a store property and sending in an action:</span>
   <span class="c1">// Accessing a store property</span>
   <span class="nf">textTag</span><span class="p">(</span><span class="nv">tag</span><span class="p">:</span> <span class="n">store</span><span class="o">.</span><span class="n">tag</span><span class="p">)</span>
      <span class="o">.</span><span class="n">onLongPressGesture</span> <span class="p">{</span>
         <span class="c1">// Sending in an action</span>
         <span class="n">store</span><span class="o">.</span><span class="nf">send</span><span class="p">(</span><span class="o">.</span><span class="n">tapped</span><span class="p">)</span>
      <span class="p">}</span>
   <span class="c1">// Binding a store property</span>
   <span class="kt">TextField</span><span class="p">(</span><span class="kt">Constants</span><span class="o">.</span><span class="kt">Tag</span><span class="o">.</span><span class="n">placeholder</span><span class="p">,</span>
             <span class="nv">text</span><span class="p">:</span> <span class="err">$</span><span class="n">store</span><span class="o">.</span><span class="n">text</span><span class="o">.</span><span class="nf">sending</span><span class="p">(\</span><span class="o">.</span><span class="n">entered</span><span class="p">),</span>
             <span class="nv">axis</span><span class="p">:</span> <span class="o">.</span><span class="n">vertical</span><span class="p">)</span>
      <span class="o">.</span><span class="n">onSubmit</span> <span class="p">{</span>
         <span class="n">store</span><span class="o">.</span><span class="nf">send</span><span class="p">(</span><span class="o">.</span><span class="n">submitted</span><span class="p">)</span>
      <span class="p">}</span></code></pre></figure>

<h4 id="navigation-1">Navigation</h4>

<p>Unlike in the MVVM examples, the flow between features within TCA architecture is done differently. The TCA Tutorials explain much better than I can (with ample examples that build upon the information learned), but I will try to walk readers through how I’ve used it in the Task flows within my sample code.</p>

<p><strong>TCATaskFeature</strong></p>

<p>With our state, we define our identified array of tasks, so that we can can present them, add to our array or remove from the array. We also have two different types of destinations: an alert to confirm deletion and our add/edit feature where an item (new or existing) can be edited.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">@Reducer</span>
<span class="kd">struct</span> <span class="kt">TCATaskFeature</span> <span class="p">{</span>
   <span class="c1">// MARK: State</span>
   <span class="kd">@ObservableState</span>
   <span class="kd">struct</span> <span class="kt">State</span><span class="p">:</span> <span class="kt">Equatable</span> <span class="p">{</span>
      <span class="k">var</span> <span class="nv">tasks</span><span class="p">:</span> <span class="kt">IdentifiedArrayOf</span><span class="o">&lt;</span><span class="kt">TCATask</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">[]</span>

      <span class="kd">@Presents</span> <span class="k">var</span> <span class="nv">alert</span><span class="p">:</span> <span class="kt">AlertState</span><span class="o">&lt;</span><span class="kt">Action</span><span class="o">.</span><span class="kt">Alert</span><span class="o">&gt;</span><span class="p">?</span>
      <span class="kd">@Presents</span> <span class="k">var</span> <span class="nv">destination</span><span class="p">:</span> <span class="kt">Destination</span><span class="o">.</span><span class="kt">State</span><span class="p">?</span>
   <span class="p">}</span></code></pre></figure>

<p>The actions supported within our feature: a new task can be added, an alert can communicate back, a destination can communicate back, a deletion has been requested (and an alert is needed to confirm it), and an edit task has been initiated.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">   <span class="kd">enum</span> <span class="kt">Action</span> <span class="p">{</span>
      <span class="k">case</span> <span class="n">addButtonTapped</span>
      <span class="k">case</span> <span class="nf">alert</span><span class="p">(</span><span class="kt">PresentationAction</span><span class="o">&lt;</span><span class="kt">Alert</span><span class="o">&gt;</span><span class="p">)</span>
      <span class="k">case</span> <span class="nf">deleteSent</span><span class="p">(</span><span class="kt">TCATask</span><span class="p">)</span>
      <span class="k">case</span> <span class="nf">destination</span><span class="p">(</span><span class="kt">PresentationAction</span><span class="o">&lt;</span><span class="kt">Destination</span><span class="o">.</span><span class="kt">Action</span><span class="o">&gt;</span><span class="p">)</span>
      <span class="k">case</span> <span class="nf">editTask</span><span class="p">(</span><span class="kt">TCATask</span><span class="p">)</span>
   
      <span class="kd">@CasePathable</span>
      <span class="kd">enum</span> <span class="kt">Alert</span><span class="p">:</span> <span class="kt">Equatable</span> <span class="p">{</span>
         <span class="k">case</span> <span class="nf">confirmDeletion</span><span class="p">(</span><span class="nv">id</span><span class="p">:</span> <span class="kt">TCATask</span><span class="o">.</span><span class="kt">ID</span><span class="p">)</span>
      <span class="p">}</span>
   <span class="p">}</span></code></pre></figure>

<p>When we walk through the reducer itself, we can see how the actions flow from one to another:</p>

<ul>
  <li>When the add button is tapped, the add/edit feature becomes a destination for a newly created task in <em>add</em> mode.</li>
  <li>When the add/edit feature sends back a save response, the newly created (and now properly edited) task is added into the store.</li>
  <li>An add cancelation, ensures nothing happens.</li>
  <li>When an item has been selected, the add/edit feature becomes a destination for the selected task in <em>edit</em> mode.</li>
  <li>When the add/edit feature sends back a save response, the updated task is replaced in the store.</li>
  <li>When the add/edit feature sends back a delete response, a delete sent action is sent.</li>
  <li>An edit cancelation, ensures nothing happens.</li>
  <li>When a delete sent action is sent, an alert is presented to confirm the deletion.</li>
  <li>An alert that isn’t confirmed, does nothing.</li>
  <li>An alert once confirmed, ensures that the task is deleted.</li>
</ul>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">   <span class="c1">// MARK: Body</span>
   <span class="kd">@Dependency</span><span class="p">(\</span><span class="o">.</span><span class="n">uuid</span><span class="p">)</span> <span class="k">var</span> <span class="nv">uuid</span>
   <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">ReducerOf</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;</span> <span class="p">{</span>
      <span class="kt">Reduce</span> <span class="p">{</span> <span class="n">state</span><span class="p">,</span> <span class="n">action</span> <span class="k">in</span>

         <span class="k">switch</span> <span class="n">action</span> <span class="p">{</span>
         <span class="k">case</span> <span class="o">.</span><span class="nv">addButtonTapped</span><span class="p">:</span>
            <span class="n">state</span><span class="o">.</span><span class="n">destination</span> <span class="o">=</span> <span class="o">.</span><span class="nf">addTask</span><span class="p">(</span>
               <span class="kt">TCAAddEditTaskFeature</span><span class="o">.</span><span class="kt">State</span><span class="p">(</span>
                  <span class="nv">mode</span><span class="p">:</span> <span class="o">.</span><span class="n">add</span><span class="p">,</span>
                  <span class="nv">task</span><span class="p">:</span> <span class="kt">TCATask</span><span class="p">(</span><span class="nv">id</span><span class="p">:</span> <span class="k">self</span><span class="o">.</span><span class="nf">uuid</span><span class="p">(),</span>
                                <span class="nv">task</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">type</span><span class="p">:</span> <span class="o">.</span><span class="nf">text</span><span class="p">(</span><span class="s">""</span><span class="p">)))</span>
               <span class="p">)</span>
            <span class="p">)</span>

            <span class="k">return</span> <span class="o">.</span><span class="k">none</span>

         <span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">alert</span><span class="p">(</span><span class="o">.</span><span class="nf">presented</span><span class="p">(</span><span class="o">.</span><span class="nf">confirmDeletion</span><span class="p">(</span><span class="nv">id</span><span class="p">:</span> <span class="n">id</span><span class="p">))):</span>
            <span class="n">state</span><span class="o">.</span><span class="n">tasks</span><span class="o">.</span><span class="nf">remove</span><span class="p">(</span><span class="nv">id</span><span class="p">:</span> <span class="n">id</span><span class="p">)</span>
            <span class="k">return</span> <span class="o">.</span><span class="k">none</span>

         <span class="k">case</span> <span class="o">.</span><span class="nv">alert</span><span class="p">:</span>
            <span class="k">return</span> <span class="o">.</span><span class="k">none</span>

         <span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">deleteSent</span><span class="p">(</span><span class="n">task</span><span class="p">):</span>
            <span class="n">state</span><span class="o">.</span><span class="n">alert</span> <span class="o">=</span> <span class="kt">AlertState</span> <span class="p">{</span>
               <span class="kt">TextState</span><span class="p">(</span><span class="kt">Constants</span><span class="o">.</span><span class="kt">Alert</span><span class="o">.</span><span class="n">message</span><span class="p">)</span>
            <span class="p">}</span> <span class="nv">actions</span><span class="p">:</span> <span class="p">{</span>
               <span class="kt">ButtonState</span><span class="p">(</span><span class="nv">role</span><span class="p">:</span> <span class="o">.</span><span class="n">destructive</span><span class="p">,</span>
                           <span class="nv">action</span><span class="p">:</span> <span class="o">.</span><span class="nf">confirmDeletion</span><span class="p">(</span><span class="nv">id</span><span class="p">:</span> <span class="n">task</span><span class="o">.</span><span class="n">id</span><span class="p">))</span> <span class="p">{</span>
                  <span class="kt">TextState</span><span class="p">(</span><span class="kt">Constants</span><span class="o">.</span><span class="kt">Alert</span><span class="o">.</span><span class="n">deleteTitle</span><span class="p">)</span>
               <span class="p">}</span>
            <span class="p">}</span>
            <span class="k">return</span> <span class="o">.</span><span class="k">none</span>

         <span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">destination</span><span class="p">(</span><span class="o">.</span><span class="nf">presented</span><span class="p">(</span><span class="o">.</span><span class="nf">addTask</span><span class="p">(</span><span class="o">.</span><span class="nf">delegate</span><span class="p">(</span><span class="o">.</span><span class="nf">saveTask</span><span class="p">(</span><span class="n">task</span><span class="p">))))):</span>
            <span class="n">state</span><span class="o">.</span><span class="n">tasks</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="n">task</span><span class="p">)</span>

            <span class="k">return</span> <span class="o">.</span><span class="k">none</span>

         <span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">destination</span><span class="p">(</span><span class="o">.</span><span class="nf">presented</span><span class="p">(</span><span class="o">.</span><span class="nf">editTask</span><span class="p">(</span><span class="o">.</span><span class="nf">delegate</span><span class="p">(</span><span class="o">.</span><span class="nf">saveTask</span><span class="p">(</span><span class="n">task</span><span class="p">))))):</span>

            <span class="k">guard</span> <span class="k">let</span> <span class="nv">index</span> <span class="o">=</span> <span class="n">state</span><span class="o">.</span><span class="n">tasks</span><span class="o">.</span><span class="nf">firstIndex</span><span class="p">(</span><span class="nv">where</span><span class="p">:</span> <span class="p">{</span> <span class="nv">$0</span><span class="o">.</span><span class="n">id</span> <span class="o">==</span> <span class="n">task</span><span class="o">.</span><span class="n">id</span> <span class="p">})</span> <span class="k">else</span> <span class="p">{</span>
               <span class="k">return</span> <span class="o">.</span><span class="k">none</span>
            <span class="p">}</span>

            <span class="n">state</span><span class="o">.</span><span class="n">tasks</span><span class="p">[</span><span class="n">index</span><span class="p">]</span> <span class="o">=</span> <span class="n">task</span>

            <span class="k">return</span> <span class="o">.</span><span class="k">none</span>

         <span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">destination</span><span class="p">(</span><span class="o">.</span><span class="nf">presented</span><span class="p">(</span><span class="o">.</span><span class="nf">editTask</span><span class="p">(</span><span class="o">.</span><span class="nf">delegate</span><span class="p">(</span><span class="o">.</span><span class="nf">deleteTask</span><span class="p">(</span><span class="n">task</span><span class="p">))))):</span>

            <span class="k">return</span> <span class="o">.</span><span class="nf">send</span><span class="p">(</span><span class="o">.</span><span class="nf">deleteSent</span><span class="p">(</span><span class="n">task</span><span class="p">))</span>

         <span class="k">case</span> <span class="o">.</span><span class="nv">destination</span><span class="p">:</span>
            <span class="k">return</span> <span class="o">.</span><span class="k">none</span>

         <span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">editTask</span><span class="p">(</span><span class="n">task</span><span class="p">):</span>

            <span class="n">state</span><span class="o">.</span><span class="n">destination</span> <span class="o">=</span> <span class="o">.</span><span class="nf">editTask</span><span class="p">(</span>
               <span class="kt">TCAAddEditTaskFeature</span><span class="o">.</span><span class="kt">State</span><span class="p">(</span>
                  <span class="nv">mode</span><span class="p">:</span> <span class="o">.</span><span class="n">edit</span><span class="p">,</span>
                  <span class="nv">task</span><span class="p">:</span> <span class="n">task</span>
               <span class="p">)</span>
            <span class="p">)</span>

            <span class="k">return</span> <span class="o">.</span><span class="k">none</span>
         <span class="p">}</span>

      <span class="p">}</span>
      <span class="o">.</span><span class="nf">ifLet</span><span class="p">(\</span><span class="o">.</span><span class="err">$</span><span class="n">destination</span><span class="p">,</span>
             <span class="nv">action</span><span class="p">:</span> <span class="p">\</span><span class="o">.</span><span class="n">destination</span><span class="p">)</span> <span class="p">{</span>
         <span class="kt">Destination</span><span class="o">.</span><span class="n">body</span>
      <span class="p">}</span>
      <span class="o">.</span><span class="nf">ifLet</span><span class="p">(\</span><span class="o">.</span><span class="err">$</span><span class="n">alert</span><span class="p">,</span> <span class="nv">action</span><span class="p">:</span> <span class="p">\</span><span class="o">.</span><span class="n">alert</span><span class="p">)</span>
   <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>When the destinations (alert or add/edit feature) are triggered, this is handled by the destination states and actions that allow the reducer to handle those.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// MARK: - Destination</span>
<span class="kd">extension</span> <span class="kt">TCATaskFeature</span> <span class="p">{</span>
   <span class="c1">// MARK: Reducer</span>
   <span class="kd">@Reducer</span>
   <span class="kd">enum</span> <span class="kt">Destination</span> <span class="p">{</span>
      <span class="k">case</span> <span class="nf">addTask</span><span class="p">(</span><span class="kt">TCAAddEditTaskFeature</span><span class="p">)</span>
      <span class="k">case</span> <span class="nf">editTask</span><span class="p">(</span><span class="kt">TCAAddEditTaskFeature</span><span class="p">)</span>
   <span class="p">}</span>
<span class="p">}</span>
<span class="c1">// MARK: State</span>
<span class="kd">extension</span> <span class="kt">TCATaskFeature</span><span class="o">.</span><span class="kt">Destination</span><span class="o">.</span><span class="kt">State</span><span class="p">:</span> <span class="kt">Equatable</span> <span class="p">{}</span></code></pre></figure>

<p><strong>TCATaskFeatureView</strong></p>

<p>As we did with the <code class="language-plaintext highlighter-rouge">TCATagView</code>, the store is bound. And the view accesses the store properties, sends actions via the store, or binds elements of the store such as the scope.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">TCATaskFeatureView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
   <span class="kd">@Bindable</span> <span class="k">var</span> <span class="nv">store</span><span class="p">:</span> <span class="kt">StoreOf</span><span class="o">&lt;</span><span class="kt">TCATaskFeature</span><span class="o">&gt;</span>

   <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
      <span class="kt">NavigationStack</span> <span class="p">{</span>
         <span class="kt">List</span> <span class="p">{</span>
            <span class="kt">ForEach</span> <span class="p">(</span><span class="n">store</span><span class="o">.</span><span class="n">tasks</span><span class="p">)</span> <span class="p">{</span> <span class="n">task</span> <span class="k">in</span>

               <span class="kt">HStack</span> <span class="p">{</span>
                  <span class="kt">Text</span><span class="p">(</span><span class="n">task</span><span class="o">.</span><span class="n">task</span><span class="o">.</span><span class="n">type</span><span class="o">.</span><span class="n">toString</span><span class="p">)</span>
                     <span class="o">.</span><span class="nf">bodyMode</span><span class="p">()</span> <span class="c1">// A view modifier for `Text` view.</span>
                  <span class="kt">Spacer</span><span class="p">()</span>
                  <span class="kt">Button</span> <span class="p">{</span>
                     <span class="n">store</span><span class="o">.</span><span class="nf">send</span><span class="p">(</span><span class="o">.</span><span class="nf">editTask</span><span class="p">(</span><span class="n">task</span><span class="p">))</span>
                  <span class="p">}</span> <span class="nv">label</span><span class="p">:</span> <span class="p">{</span>
                     <span class="kt">Image</span><span class="o">.</span><span class="kt">Task</span><span class="o">.</span><span class="kt">Icon</span><span class="o">.</span><span class="n">edit</span>
                        <span class="o">.</span><span class="nf">tint</span><span class="p">(</span><span class="kt">Color</span><span class="o">.</span><span class="n">green</span><span class="o">.</span><span class="nf">opacity</span><span class="p">(</span><span class="mf">0.8</span><span class="p">))</span>
                  <span class="p">}</span>
                  <span class="o">.</span><span class="nf">buttonStyle</span><span class="p">(</span><span class="o">.</span><span class="n">borderless</span><span class="p">)</span>
               <span class="p">}</span>

            <span class="p">}</span>
         <span class="p">}</span>
         <span class="o">.</span><span class="nf">navigationTitle</span><span class="p">(</span><span class="kt">Constants</span><span class="o">.</span><span class="kt">TaskView</span><span class="o">.</span><span class="n">title</span><span class="p">)</span>
         <span class="o">.</span><span class="n">toolbar</span> <span class="p">{</span>
            <span class="kt">ToolbarItem</span> <span class="p">{</span>
               <span class="kt">Button</span> <span class="p">{</span>
                  <span class="n">store</span><span class="o">.</span><span class="nf">send</span><span class="p">(</span><span class="o">.</span><span class="n">addButtonTapped</span><span class="p">)</span>
               <span class="p">}</span> <span class="nv">label</span><span class="p">:</span> <span class="p">{</span>
                  <span class="kt">Image</span><span class="p">(</span><span class="nv">systemName</span><span class="p">:</span> <span class="s">"plus"</span><span class="p">)</span>
               <span class="p">}</span>
            <span class="p">}</span>
         <span class="p">}</span>
      <span class="p">}</span>
       <span class="c1">// Defines the add task version of the `TCAAddEditTaskView`.</span>
       <span class="o">.</span><span class="nf">sheet</span><span class="p">(</span>
         <span class="nv">item</span><span class="p">:</span> <span class="err">$</span><span class="n">store</span><span class="o">.</span><span class="nf">scope</span><span class="p">(</span><span class="nv">state</span><span class="p">:</span> <span class="p">\</span><span class="o">.</span><span class="n">destination</span><span class="p">?</span><span class="o">.</span><span class="n">addTask</span><span class="p">,</span>
                            <span class="nv">action</span><span class="p">:</span> <span class="p">\</span><span class="o">.</span><span class="n">destination</span><span class="o">.</span><span class="n">addTask</span><span class="p">)</span>
      <span class="p">)</span> <span class="p">{</span> <span class="n">addTaskStore</span> <span class="k">in</span>

         <span class="kt">NavigationStack</span> <span class="p">{</span>
            <span class="kt">TCAAddEditTaskView</span><span class="p">(</span><span class="nv">store</span><span class="p">:</span> <span class="n">addTaskStore</span><span class="p">)</span>
         <span class="p">}</span>
      <span class="p">}</span>
       <span class="c1">// Defines the edit task version of the `TCAAddEditTaskView`.</span>
       <span class="o">.</span><span class="nf">sheet</span><span class="p">(</span>
         <span class="nv">item</span><span class="p">:</span> <span class="err">$</span><span class="n">store</span><span class="o">.</span><span class="nf">scope</span><span class="p">(</span><span class="nv">state</span><span class="p">:</span> <span class="p">\</span><span class="o">.</span><span class="n">destination</span><span class="p">?</span><span class="o">.</span><span class="n">editTask</span><span class="p">,</span>
                            <span class="nv">action</span><span class="p">:</span> <span class="p">\</span><span class="o">.</span><span class="n">destination</span><span class="o">.</span><span class="n">editTask</span><span class="p">)</span>
      <span class="p">)</span> <span class="p">{</span> <span class="n">editTaskStore</span> <span class="k">in</span>

         <span class="kt">NavigationStack</span> <span class="p">{</span>
            <span class="kt">TCAAddEditTaskView</span><span class="p">(</span><span class="nv">store</span><span class="p">:</span> <span class="n">editTaskStore</span><span class="p">)</span>
         <span class="p">}</span>
      <span class="p">}</span>
       <span class="c1">// Defines the alert presentation.</span>
       <span class="o">.</span><span class="nf">alert</span><span class="p">(</span><span class="err">$</span><span class="n">store</span><span class="o">.</span><span class="nf">scope</span><span class="p">(</span><span class="nv">state</span><span class="p">:</span> <span class="p">\</span><span class="o">.</span><span class="n">alert</span><span class="p">,</span> <span class="nv">action</span><span class="p">:</span> <span class="p">\</span><span class="o">.</span><span class="n">alert</span><span class="p">))</span>
   <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<h3 id="tca-flow">TCA Flow</h3>

<p>Seeing the TCA Flow in action:</p>

<p><img src="/img/TCA-2025-06-24.gif" alt="TCA Flow" /></p>

<hr />

<p>Next article, we’ll delve further into testing MVVM and TCA architectures, as I decide which better fits what I want to do with the <code class="language-plaintext highlighter-rouge">TaskManager</code> app.</p>]]></content><author><name>Jp</name></author><category term="coding" /><category term="taskmanager" /><category term="swiftui" /><category term="architecture" /><category term="tca" /><category term="mvvm" /><summary type="html"><![CDATA[Three common architectures for modern iOS apps are: MVVM, TCA, and VIPER. This post will talk about using MVVM and TCA for our spec TaskManager app. All of the code for this blog post is in this sample code repo.]]></summary></entry><entry><title type="html">Making an Editable SwiftUI TagsView</title><link href="https://jp4mobile.com/coding/2025/05/18/TagsView.html" rel="alternate" type="text/html" title="Making an Editable SwiftUI TagsView" /><published>2025-05-18T14:00:01+00:00</published><updated>2025-05-18T14:00:01+00:00</updated><id>https://jp4mobile.com/coding/2025/05/18/TagsView</id><content type="html" xml:base="https://jp4mobile.com/coding/2025/05/18/TagsView.html"><![CDATA[<p>Last post, we talked about the <code class="language-plaintext highlighter-rouge">TagView</code> but it was only view only. For this post, we talk about leveraging <code class="language-plaintext highlighter-rouge">EditMode</code> to allow <code class="language-plaintext highlighter-rouge">TagView</code> for both viewing and editing the data.</p>

<p>All of the code for this blog post is in this <a href="https://github.com/Jp4Mobile/SampleCode/tree/main/posts/projects/SwiftUI-2025-05-18">sample code repo</a>.</p>

<!--more-->

<h2 id="switching-to-state-objects">Switching to State Objects</h2>

<p>To shift from a static view to a view that can be changed, we need to make some minor changes in our view.</p>

<h3 id="step-one">Step one:</h3>

<p>Add an <code class="language-plaintext highlighter-rouge">EditMode</code> variable, so that we can detect whether we’re in an edit state or not.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">    <span class="kd">@State</span> <span class="kd">private</span> <span class="k">var</span> <span class="nv">editMode</span><span class="p">:</span> <span class="kt">EditMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">inactive</span></code></pre></figure>

<p>Then we shift from a constant value to a <code class="language-plaintext highlighter-rouge">@State</code> values that can be changed.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Removed: let tag: Tag</span>
    <span class="kd">@State</span> <span class="k">var</span> <span class="nv">tag</span><span class="p">:</span> <span class="kt">Tag</span>
    <span class="kd">@State</span> <span class="k">var</span> <span class="nv">tagPlaceHolder</span><span class="p">:</span> <span class="kt">String</span></code></pre></figure>

<p>This is needed because we’re going to be using a TextField which allows you to change <code class="language-plaintext highlighter-rouge">String</code> values. As the <code class="language-plaintext highlighter-rouge">tagPlaceHolder</code> will always be tied to the value of the tag itself, let’s make life easier for ourselves by adding an initializer.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">    <span class="nf">init</span><span class="p">(</span><span class="nv">editMode</span><span class="p">:</span> <span class="kt">EditMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">inactive</span><span class="p">,</span>
         <span class="nv">tag</span><span class="p">:</span> <span class="kt">Tag</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="n">editMode</span> <span class="o">=</span> <span class="n">editMode</span>
        <span class="k">self</span><span class="o">.</span><span class="n">tag</span> <span class="o">=</span> <span class="n">tag</span>
        <span class="k">self</span><span class="o">.</span><span class="n">tagPlaceHolder</span> <span class="o">=</span> <span class="n">tag</span><span class="o">.</span><span class="n">toString</span>
    <span class="p">}</span></code></pre></figure>

<p>This way we can continue to use the same initializer that we had been using. ie; <code class="language-plaintext highlighter-rouge">TagView(tag: Tag("tag"))</code> without adding a chance that the tag value and the tagPlaceHolder could ever get out of sync.</p>

<h3 id="step-two">Step two:</h3>

<p>Now we just have to change the body property to leverage the <code class="language-plaintext highlighter-rouge">editMode</code> state and either show a static view or <code class="language-plaintext highlighter-rouge">TextField</code> that will allow a user to change the value.</p>

<p>First, we need to add a way to toggle the <code class="language-plaintext highlighter-rouge">editMode</code>. I added a long press gesture to the static view to toggle the value.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">	<span class="o">.</span><span class="n">onLongPressGesture</span> <span class="p">{</span>
		<span class="n">editMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">active</span>
	<span class="p">}</span></code></pre></figure>

<h3 id="step-three">Step three:</h3>

<p>Now that we can have different <code class="language-plaintext highlighter-rouge">editMode</code> states, we can leverage them with an <code class="language-plaintext highlighter-rouge">if/else</code> block.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">	<span class="k">if</span> <span class="n">editMode</span> <span class="o">==</span> <span class="o">.</span><span class="n">active</span> <span class="p">{</span>
			<span class="kt">TextField</span><span class="p">(</span><span class="kt">Constants</span><span class="o">.</span><span class="kt">Tag</span><span class="o">.</span><span class="n">placeholder</span><span class="p">,</span>
					  <span class="nv">text</span><span class="p">:</span> <span class="err">$</span><span class="n">tagPlaceHolder</span><span class="p">,</span>
					  <span class="nv">axis</span><span class="p">:</span> <span class="o">.</span><span class="n">vertical</span><span class="p">)</span>
			<span class="o">.</span><span class="nf">textFieldStyle</span><span class="p">(</span><span class="o">.</span><span class="n">plain</span><span class="p">)</span>
			<span class="o">.</span><span class="nf">multilineTextAlignment</span><span class="p">(</span><span class="o">.</span><span class="n">center</span><span class="p">)</span>
			<span class="o">.</span><span class="n">onSubmit</span> <span class="p">{</span>
				<span class="n">editMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">inactive</span>
				<span class="nf">convertToTag</span><span class="p">(</span><span class="nv">placeholder</span><span class="p">:</span> <span class="n">tagPlaceHolder</span><span class="p">,</span>
							 <span class="nv">baseTag</span><span class="p">:</span> <span class="n">tag</span><span class="p">)</span>
			<span class="p">}</span>
	<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
		<span class="kt">Text</span><span class="p">(</span><span class="s">"@</span><span class="se">\(</span><span class="n">tag</span><span class="o">.</span><span class="n">tag</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
		<span class="k">if</span> <span class="k">let</span> <span class="nv">payload</span> <span class="o">=</span> <span class="n">tag</span><span class="o">.</span><span class="n">payload</span> <span class="p">{</span>
			<span class="kt">Text</span><span class="p">(</span><span class="s">"@</span><span class="se">\(</span><span class="n">tag</span><span class="o">.</span><span class="n">tag</span><span class="se">)</span><span class="s">(**</span><span class="se">\(</span><span class="n">payload</span><span class="se">)</span><span class="s">**)"</span><span class="p">)</span>
		<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
			<span class="kt">Text</span><span class="p">(</span><span class="s">"@</span><span class="se">\(</span><span class="n">tag</span><span class="o">.</span><span class="n">tag</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
		<span class="p">}</span>
	<span class="p">}</span></code></pre></figure>

<p>Using the <code class="language-plaintext highlighter-rouge">.onSubmit</code> for the TextField allows us to toggle the <code class="language-plaintext highlighter-rouge">editMode</code> to reset the view and our new <code class="language-plaintext highlighter-rouge">convertToTag</code> function to manage the logic for the conversion from a <code class="language-plaintext highlighter-rouge">String</code> to a <code class="language-plaintext highlighter-rouge">Tag</code>.</p>

<p>While the <code class="language-plaintext highlighter-rouge">Text</code> to <code class="language-plaintext highlighter-rouge">TextField</code> are visually different; I felt that the overlay should also change to better illustrate the change.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">	<span class="kt">Group</span> <span class="p">{</span>
		<span class="k">if</span> <span class="n">editMode</span> <span class="o">==</span> <span class="o">.</span><span class="n">active</span> <span class="p">{</span>
			<span class="kt">RoundedRectangle</span><span class="p">(</span><span class="nv">cornerRadius</span><span class="p">:</span> <span class="mi">8</span><span class="p">)</span>
				<span class="o">.</span><span class="nf">stroke</span><span class="p">(</span><span class="nv">style</span><span class="p">:</span> <span class="kt">StrokeStyle</span><span class="p">(</span><span class="nv">lineWidth</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
										   <span class="nv">dash</span><span class="p">:</span> <span class="p">[</span><span class="mi">2</span><span class="p">]))</span>
				<span class="o">.</span><span class="nf">foregroundColor</span><span class="p">(</span><span class="kt">Color</span><span class="o">.</span><span class="kt">Tag</span><span class="o">.</span><span class="n">border</span><span class="p">)</span>
		<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
			<span class="kt">Capsule</span><span class="p">()</span>
				<span class="o">.</span><span class="nf">stroke</span><span class="p">(</span><span class="kt">Color</span><span class="o">.</span><span class="kt">Tag</span><span class="o">.</span><span class="n">border</span><span class="p">,</span>
						<span class="nv">lineWidth</span><span class="p">:</span> <span class="mi">1</span><span class="p">)</span>
		<span class="p">}</span>
	<span class="p">}</span></code></pre></figure>

<p>Personally, I felt that the dashed rounded rectangle looked a little better as the <code class="language-plaintext highlighter-rouge">TextField</code> expands.</p>

<h3 id="step-four">Step four:</h3>

<p>Lastly, we fill in the placeholder for the <code class="language-plaintext highlighter-rouge">convertToTag</code> logic.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">    <span class="kd">func</span> <span class="nf">convertToTag</span><span class="p">(</span><span class="nv">placeholder</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span>
                      <span class="nv">baseTag</span><span class="p">:</span> <span class="kt">Tag</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">guard</span> <span class="k">let</span> <span class="nv">convertedTag</span> <span class="o">=</span> <span class="n">placeholder</span><span class="o">.</span><span class="nf">toTag</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
            <span class="n">tagPlaceHolder</span> <span class="o">=</span> <span class="n">baseTag</span><span class="o">.</span><span class="n">toString</span>
            <span class="k">return</span>
        <span class="p">}</span>

        <span class="k">self</span><span class="o">.</span><span class="n">tag</span> <span class="o">=</span> <span class="n">convertedTag</span>
        <span class="k">self</span><span class="o">.</span><span class="n">tagPlaceHolder</span> <span class="o">=</span> <span class="n">convertedTag</span><span class="o">.</span><span class="n">toString</span>
    <span class="p">}</span></code></pre></figure>

<p>This makes sure that the text converts to a <code class="language-plaintext highlighter-rouge">Tag</code>. Otherwise, we reset our tagPlaceholder back to the original this way incorrect strings don’t linger for the next time that the <code class="language-plaintext highlighter-rouge">TextField</code> is shown. Feel free to comment that bit out and see what you get.</p>

<p>If it’s properly converted, we reset the tag (and the tagPlaceHolder, so that the edge cases for a tag without a placeholder don’t result in a tag of <code class="language-plaintext highlighter-rouge">@tag</code> and a saved tagPlaceholder of <code class="language-plaintext highlighter-rouge">@tag()</code>).</p>

<p>Unfortunately, our existing extract tags conversion code is for <code class="language-plaintext highlighter-rouge">Substring</code> objects not <code class="language-plaintext highlighter-rouge">String</code> objects, so we needed a new extension to put in that logic. This also allowed us to put in some business logic to ensure that the tag conversion works when this is exactly one tag in the entered text.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">    <span class="kd">func</span> <span class="nf">toTag</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">Tag</span><span class="p">?</span> <span class="p">{</span>
        <span class="k">guard</span> <span class="k">self</span><span class="o">.</span><span class="nf">contains</span><span class="p">(</span><span class="s">"@"</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="k">let</span> <span class="nv">tags</span> <span class="o">=</span> <span class="kt">Substring</span><span class="p">(</span><span class="k">self</span><span class="p">)</span><span class="o">.</span><span class="nf">extractTags</span><span class="p">()</span>

        <span class="c1">// We only want to convert from `String` to `Tag`, when there's one and only one tag in it.</span>
        <span class="k">guard</span> <span class="n">tags</span><span class="o">.</span><span class="n">count</span> <span class="o">==</span> <span class="mi">1</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="k">return</span> <span class="n">tags</span><span class="o">.</span><span class="n">first</span>
    <span class="p">}</span></code></pre></figure>

<h2 id="seeing-it-in-action">Seeing it in action</h2>

<p>We walk through the following cases:</p>

<ul>
  <li>Text that isn’t a tag.</li>
  <li>Text that is multiple tags.</li>
  <li>Text with a tag without a payload.</li>
  <li>Text with a tag with a payload.</li>
  <li>Text with a very large payload to verify wrapping.</li>
</ul>

<p><img src="/img/TagView-20250518.gif" alt="TagView being edited" /></p>

<hr />

<p>Next post, we will pull back a bit to talk about the different architectural options such as MVVM, VIPER, or TCA for a SwiftUI project, before we get too deep into how we’ve defined our UI.</p>]]></content><author><name>Jp</name></author><category term="coding" /><category term="taskmanager" /><category term="swiftui" /><category term="editing" /><summary type="html"><![CDATA[Last post, we talked about the TagView but it was only view only. For this post, we talk about leveraging EditMode to allow TagView for both viewing and editing the data. All of the code for this blog post is in this sample code repo.]]></summary></entry><entry><title type="html">SwiftUI TagsView</title><link href="https://jp4mobile.com/coding/2025/05/11/TagsView.html" rel="alternate" type="text/html" title="SwiftUI TagsView" /><published>2025-05-11T14:00:01+00:00</published><updated>2025-05-11T14:00:01+00:00</updated><id>https://jp4mobile.com/coding/2025/05/11/TagsView</id><content type="html" xml:base="https://jp4mobile.com/coding/2025/05/11/TagsView.html"><![CDATA[<p>Picking back up my <code class="language-plaintext highlighter-rouge">TaskManager</code> app project, it’s time to start on the UI/UX portion. When working on how your app or even how a single view is going to look, it’s tempting to wait until everything is perfect before sharing it with stakeholders.</p>

<p>I’ve always found that a more iterative process works better. It may allow design and development to go back and forth on accessibility issues.</p>

<p>With that in mind, I write this as I try to illustrate some of the challenges in a simple reusable UI element in my current design.</p>

<p>How a <code class="language-plaintext highlighter-rouge">Tag</code> data model will represented as a view.</p>

<p>All of the code for this blog post is in this <a href="https://github.com/Jp4Mobile/SampleCode/tree/main/posts/projects/SwiftUI-2025-05-11">sample code repo</a>.</p>

<!--more-->

<h2 id="tag-data-model">Tag Data Model</h2>

<p>There are two types of tags: <code class="language-plaintext highlighter-rouge">@tag</code> or <code class="language-plaintext highlighter-rouge">@tag(with content)</code>.</p>

<p>During the brainstorming session with design, we talked about these being configurable in the future so that specific tags may have different colors or that they’d interact with task items depending on their values.</p>

<p>To keep it simple, we had two known tags that have clear meanings:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">due</code> which will indicate that there’s a due date.
    <ul>
      <li>This could be an unspecific due date, such as following up on an email or a conversation <code class="language-plaintext highlighter-rouge">@due</code>.</li>
      <li>This could be a specific date date/time, such as being due on a specific date, date and time, an appointment, or a recurring appointment:
        <ul>
          <li>Specific date: <code class="language-plaintext highlighter-rouge">@due(2025-05-11)</code></li>
          <li>Specific date and time: <code class="language-plaintext highlighter-rouge">@due(2025-05-11 10:00)</code></li>
          <li>An appointment with a date, time and end time: <code class="language-plaintext highlighter-rouge">@due(2025-05-11 13:00-14:00)</code></li>
          <li>A recurring appointment: <code class="language-plaintext highlighter-rouge">@due(2025-05-12 13:00 thru 2025-05-19 23:59)</code></li>
        </ul>
      </li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">done</code> which indicates that the task has been completed.
    <ul>
      <li>Typically, this is a simple specific done with a date to be able to tell when a task was accomplished: <code class="language-plaintext highlighter-rouge">@done(2025-05-11)</code>.</li>
    </ul>
  </li>
</ul>

<p>The concept of a tag means that they can be any string depending on how the user may want to categorize things, such as <code class="language-plaintext highlighter-rouge">@work</code>, <code class="language-plaintext highlighter-rouge">@home</code>, <code class="language-plaintext highlighter-rouge">@self</code>, etc…</p>

<p>It was decided that in the interest of streamlining an <em>MVP</em> (Minimally Viable Product), the app wouldn’t support colors at this point.</p>

<h2 id="first-pass-at-the-ui">First Pass at the UI.</h2>

<p>Design wanted a simple view of the <code class="language-plaintext highlighter-rouge">@tag</code> or <code class="language-plaintext highlighter-rouge">@tag(content)</code> in the <code class="language-plaintext highlighter-rouge">.caption</code> font with a capsule shape around it.</p>

<p>In my code base, I defined some constants to simplify my life: <code class="language-plaintext highlighter-rouge">Spacing</code> constants building around a default of <code class="language-plaintext highlighter-rouge">8</code> (default, half, double, triple, or quadruple) for some of the spacing that we want around various visual elements. We also defined specific colors that can be reused in multiple places: border color (<code class="language-plaintext highlighter-rouge">gray</code>), tint color (<code class="language-plaintext highlighter-rouge">black</code>), and standard tag color (<code class="language-plaintext highlighter-rouge">black.opacity(0.75)</code>). This ensures that should we want to change things, we can change the constant and it changes the color everywhere in the app.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">TagView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">tag</span><span class="p">:</span> <span class="kt">Tag</span>
    <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="kt">Group</span> <span class="p">{</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nv">payload</span> <span class="o">=</span> <span class="n">tag</span><span class="o">.</span><span class="n">payload</span> <span class="p">{</span>
                <span class="kt">Text</span><span class="p">(</span><span class="s">"@</span><span class="se">\(</span><span class="n">tag</span><span class="o">.</span><span class="n">tag</span><span class="se">)</span><span class="s">(</span><span class="se">\(</span><span class="n">payload</span><span class="se">)</span><span class="s">)"</span><span class="p">)</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="kt">Text</span><span class="p">(</span><span class="s">"@</span><span class="se">\(</span><span class="n">tag</span><span class="o">.</span><span class="n">tag</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="o">.</span><span class="nf">font</span><span class="p">(</span><span class="o">.</span><span class="n">caption</span><span class="p">)</span>
        <span class="o">.</span><span class="nf">foregroundColor</span><span class="p">(</span><span class="kt">Color</span><span class="o">.</span><span class="kt">Tag</span><span class="o">.</span><span class="k">default</span><span class="p">)</span>
        <span class="o">.</span><span class="nf">padding</span><span class="p">(</span><span class="kt">Spacing</span><span class="o">.</span><span class="k">default</span><span class="p">)</span>
        <span class="o">.</span><span class="nf">overlay</span><span class="p">(</span>
            <span class="kt">Capsule</span><span class="p">()</span>
                <span class="o">.</span><span class="nf">stroke</span><span class="p">(</span><span class="kt">Color</span><span class="o">.</span><span class="kt">Tag</span><span class="o">.</span><span class="n">border</span><span class="p">,</span> <span class="nv">lineWidth</span><span class="p">:</span> <span class="mi">1</span><span class="p">)</span>
        <span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>As you can see, it looks okay, but it might be nice to draw the eye to the tag’s payload:</p>

<p><img src="/img/TagView-PRE-01-2025-05-11.png" alt="TagView initial pass, no highlighting yet." /></p>

<h2 id="tweak-to-add-some-highlighting-to-the-payloads">Tweak to add some highlighting to the payloads.</h2>

<p>My first thought was to shift the payload <code class="language-plaintext highlighter-rouge">Text</code> view into an <code class="language-plaintext highlighter-rouge">HStack</code>, where I’d have multiple elements and the multiple font calls with the different font weight to bolden the payload.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">	<span class="kt">HStack</span> <span class="p">{</span>
	  <span class="kt">Text</span><span class="p">(</span><span class="s">"@</span><span class="se">\(</span><span class="n">tag</span><span class="o">.</span><span class="n">tag</span><span class="se">)</span><span class="s">("</span><span class="p">)</span>
		<span class="o">.</span><span class="nf">font</span><span class="p">(</span><span class="o">.</span><span class="n">caption</span><span class="p">)</span>
	  <span class="kt">Text</span><span class="p">(</span><span class="s">"</span><span class="se">\(</span><span class="n">payload</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
	    <span class="o">.</span><span class="nf">font</span><span class="p">(</span><span class="o">.</span><span class="n">caption</span><span class="o">.</span><span class="nf">bold</span><span class="p">())</span>
	  <span class="kt">Text</span><span class="p">(</span><span class="s">")"</span><span class="p">)</span>
	    <span class="o">.</span><span class="nf">font</span><span class="p">(</span><span class="o">.</span><span class="n">caption</span><span class="p">)</span>
	<span class="p">}</span></code></pre></figure>

<p>From the look of it, it seemed okay:
<img src="/img/TagView-HStack-01-2025-05-11.png" alt="TagView HStack pass, now with highlighting." /></p>

<p>But I felt that this was overly complicated. A better solution was much simpler. Since iOS 15, SwiftUI supports Markdown. The solution was to just change the appropriate <code class="language-plaintext highlighter-rouge">Text()</code> view definition.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">                <span class="kt">Text</span><span class="p">(</span><span class="s">"@</span><span class="se">\(</span><span class="n">tag</span><span class="o">.</span><span class="n">tag</span><span class="se">)</span><span class="s">(**</span><span class="se">\(</span><span class="n">payload</span><span class="se">)</span><span class="s">**)"</span><span class="p">)</span></code></pre></figure>

<p>Now we’ve got highlighted payloads:
<img src="/img/TagView-PRE-02-2025-05-11.png" alt="TagView initial pass, now with highlighting." /></p>

<h1 id="challenges">Challenges</h1>

<p>When verifying how a view will look, I like to check the usual light mode/dark mode and with Dynamic Type fonts. For the most part it works, but for the larger fonts, it’s clear that a bit more work is needed:</p>

<p><img src="/img/TagView-PRE-03-2025-05-11.png" alt="TagView initial pass, doesn't quite work well with the larger Dynamic Type fonts." /></p>

<p>There are some obvious issues with the padding that affected capsule shape around the larger text, as well as the how the text wraps.</p>

<h3 id="hstack-version">HStack Version</h3>

<p>If we had gone with the HStack version, it was even more problematic, as only the payload was wrapping, which lead to an odd look:</p>

<p><img src="/img/TagView-HStack-02-2025-05-11.png" alt="TagView HStack pass, even odder wrapping for larger Dynamic Type fonts." /></p>

<h2 id="padding">Padding</h2>

<p>Once again, Apple was already there. By changing leveraging the <code class="language-plaintext highlighter-rouge">@ScaledMetric</code> to our default spacing, it will scale with the font.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">    <span class="c1">// Both of these are equivalent</span>
    <span class="kd">@ScaledMetric</span> <span class="kd">private</span> <span class="k">var</span> <span class="nv">scaledPaddingBase1</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="kt">Spacing</span><span class="o">.</span><span class="k">default</span>
    <span class="kd">@ScaledMetric</span><span class="p">(</span><span class="nv">relativeTo</span><span class="p">:</span> <span class="o">.</span><span class="n">body</span><span class="p">)</span> <span class="kd">private</span> <span class="k">var</span> <span class="nv">scaledPaddingBase2</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="kt">Spacing</span><span class="o">.</span><span class="k">default</span>
    <span class="c1">// As we're using the caption font, we base our changes to that.</span>
    <span class="kd">@ScaledMetric</span><span class="p">(</span><span class="nv">relativeTo</span><span class="p">:</span> <span class="o">.</span><span class="n">caption</span><span class="p">)</span> <span class="kd">private</span> <span class="k">var</span> <span class="nv">scaledPadding</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="kt">Spacing</span><span class="o">.</span><span class="k">default</span>
    
    <span class="c1">// Then we can just change our padding to use the appropriate scaled padding variable. </span>
    <span class="c1">// ...</span>
        <span class="o">.</span><span class="nf">padding</span><span class="p">(</span><span class="n">scaledPadding</span><span class="p">)</span></code></pre></figure>

<h2 id="wrapping-and-scaling">Wrapping and Scaling</h2>

<p>Similarly, this was just a matter of adding some modifiers to our <code class="language-plaintext highlighter-rouge">Text</code> view.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">        <span class="o">.</span><span class="nf">font</span><span class="p">(</span><span class="o">.</span><span class="n">caption</span><span class="p">)</span>
        <span class="o">.</span><span class="nf">textScale</span><span class="p">(</span><span class="o">.</span><span class="n">secondary</span><span class="p">)</span>
        <span class="o">.</span><span class="nf">foregroundColor</span><span class="p">(</span><span class="kt">Color</span><span class="o">.</span><span class="kt">Tag</span><span class="o">.</span><span class="k">default</span><span class="p">)</span>
        <span class="o">.</span><span class="nf">tint</span><span class="p">(</span><span class="kt">Color</span><span class="o">.</span><span class="kt">Tag</span><span class="o">.</span><span class="n">tint</span><span class="p">)</span>
        <span class="o">.</span><span class="nf">multilineTextAlignment</span><span class="p">(</span><span class="o">.</span><span class="n">trailing</span><span class="p">)</span>
        <span class="o">.</span><span class="nf">padding</span><span class="p">(</span><span class="n">scaledPadding</span><span class="p">)</span>
        <span class="o">.</span><span class="nf">overlay</span><span class="p">(</span>
            <span class="kt">Capsule</span><span class="p">()</span>
                <span class="o">.</span><span class="nf">stroke</span><span class="p">(</span><span class="kt">Color</span><span class="o">.</span><span class="kt">Tag</span><span class="o">.</span><span class="n">border</span><span class="p">,</span> <span class="nv">lineWidth</span><span class="p">:</span> <span class="mi">1</span><span class="p">)</span>
        <span class="p">)</span></code></pre></figure>

<p>The <code class="language-plaintext highlighter-rouge">.textScale(.secondary)</code> gave it the look that I liked even for the largest possible font size and by controlling the wrapping with <code class="language-plaintext highlighter-rouge">.multilineTextAlignment</code>, the text wrapped a little closer to what I wanted to see.</p>

<p><img src="/img/TagView-Ally-01-2025-05-11.png" alt="TagView accessible pass default font size." />
<img src="/img/TagView-Ally-02-2025-05-11.png" alt="TagView accessible pass max font size." /></p>

<hr />

<p>Next week, we will continue working on the UI layer and delving deeper into the SwiftUI of it all. How do you create a reusable SwiftUI element and trigger the UX changes when it’s interacted with.</p>]]></content><author><name>Jp</name></author><category term="coding" /><category term="taskmanager" /><category term="swiftui" /><category term="dynamictype" /><summary type="html"><![CDATA[Picking back up my TaskManager app project, it’s time to start on the UI/UX portion. When working on how your app or even how a single view is going to look, it’s tempting to wait until everything is perfect before sharing it with stakeholders. I’ve always found that a more iterative process works better. It may allow design and development to go back and forth on accessibility issues. With that in mind, I write this as I try to illustrate some of the challenges in a simple reusable UI element in my current design. How a Tag data model will represented as a view. All of the code for this blog post is in this sample code repo.]]></summary></entry><entry><title type="html">Text Models</title><link href="https://jp4mobile.com/coding/2024/12/01/TextModels.html" rel="alternate" type="text/html" title="Text Models" /><published>2024-12-01T14:00:01+00:00</published><updated>2024-12-01T14:00:01+00:00</updated><id>https://jp4mobile.com/coding/2024/12/01/TextModels</id><content type="html" xml:base="https://jp4mobile.com/coding/2024/12/01/TextModels.html"><![CDATA[<p>One of the biggest factors into why I love <code class="language-plaintext highlighter-rouge">TaskPaper</code> is the way that it effortlessly converts to and from text files. For my <code class="language-plaintext highlighter-rouge">TaskManager</code> app, I consider that to be must have functionality as well.</p>

<p>We’ll be talking about <code class="language-plaintext highlighter-rouge">String</code> parsing as we break down converting back and forth from a hypothetical <code class="language-plaintext highlighter-rouge">TaskPaper</code> project with multiple tasks. Some of these tasks may have a variety of tags. As well as the possibility of notes.</p>

<p>All of the code for this blog post is in this <a href="https://github.com/Jp4Mobile/SampleCode/tree/main/posts/projects/TextModels-2024-11-24">sample code repo</a>.</p>

<!--more-->

<h2 id="our-sample-taskpaper-file">Our sample TaskPaper file</h2>

<p>Again the three major components to a <code class="language-plaintext highlighter-rouge">TaskPaper</code> are:</p>

<ul>
  <li>
    <p>Project</p>

    <p>A <code class="language-plaintext highlighter-rouge">Project Title:</code> (Any text that ends with a colon).</p>
  </li>
  <li>
    <p>Task</p>

    <p>A <code class="language-plaintext highlighter-rouge">- Task Name</code> (Any text that begins with a dash).</p>
  </li>
  <li>
    <p>Text</p>

    <p>Any other <code class="language-plaintext highlighter-rouge">text</code>.</p>
  </li>
  <li>
    <p>Tags</p>

    <p>Optionally, there can be tags. <code class="language-plaintext highlighter-rouge">@tag</code> or <code class="language-plaintext highlighter-rouge">@tag(with content)</code>. Typically, these are usually used with tasks, but obviously, they can be on any of these items.</p>
  </li>
</ul>

<p>Let’s put that all together in a text file with a certain level of complexity, so that we can be sure that we’ve caught some of our edge cases:</p>

<figure class="highlight"><pre><code class="language-taskpaper" data-lang="taskpaper">Project:
 Notes on a project.
 TaskPaper uses a hierarchical structure to keep track of parents and
 ownership.
 ie; all of the below items are within this project.
 - Item for that project.
  - Child item with a note.
   Note about that item.
 - Tagged Item @tag
 - Tagged Item where the tag has a payload @tag(payload)
 Let's illustrate some of the time formats that we've worked so hard on.
 - Something completed @test @due(2024-11-23) @done(2024-11-23)
 - Something with a set due date and time @test @due(2024-11-24 10:00)
 - An appointment @test @due(2024-11-24 10:00-10:30)
 - A spanning appointment @test @due(2024-11-23 11:00 thru 2024-11-24 10:00)

Other Project:
 - Item for the other project</code></pre></figure>

<p>For this blog post, the code is a little more complex than it needs to be so that I can illustrate the steps a little better.</p>

<h2 id="our-new-models">Our New Models</h2>

<p>First, we have our tags. At their simplest, there are either <code class="language-plaintext highlighter-rouge">@tag</code> or <code class="language-plaintext highlighter-rouge">@tag(payload)</code>.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">enum</span> <span class="kt">Tag</span> <span class="p">{</span>
  <span class="k">case</span> <span class="nf">tag</span><span class="p">(</span><span class="kt">String</span><span class="p">)</span>
  <span class="k">case</span> <span class="nf">payloadTag</span><span class="p">(</span><span class="kt">String</span><span class="p">,</span> <span class="kt">String</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<p>Then we have our <code class="language-plaintext highlighter-rouge">TaskPaper</code> types:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">enum</span> <span class="kt">TPType</span> <span class="p">{</span>
  <span class="k">case</span> <span class="nf">project</span><span class="p">(</span><span class="kt">String</span><span class="p">)</span>
  <span class="k">case</span> <span class="nf">task</span><span class="p">(</span><span class="kt">String</span><span class="p">)</span>
  <span class="k">case</span> <span class="nf">text</span><span class="p">(</span><span class="kt">String</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<p>And then lastly, we can create a model with both of our new enums:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">TMType</span> <span class="p">{</span>
  <span class="k">let</span> <span class="nv">tabLevel</span><span class="p">:</span> <span class="kt">Int</span>
  <span class="k">let</span> <span class="nv">type</span><span class="p">:</span> <span class="kt">TPType</span>
  <span class="k">let</span> <span class="nv">tags</span><span class="p">:</span> <span class="p">[</span><span class="kt">Tag</span><span class="p">]</span>
  <span class="k">let</span> <span class="nv">children</span><span class="p">:</span> <span class="p">[</span><span class="kt">TMType</span><span class="p">]</span>
<span class="p">}</span></code></pre></figure>

<h3 id="gotchas">Gotchas</h3>

<p>One of the first gotchas comes from how we might be parsing things.</p>

<p>If we’ll be updating our models, as we’re parsing it, <code class="language-plaintext highlighter-rouge">tags</code> and <code class="language-plaintext highlighter-rouge">children</code> may not all be known as the record is being parsed, so some mutating functions might be a little easier to work with:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">TMType</span> <span class="p">{</span>
  <span class="k">let</span> <span class="nv">tabLevel</span><span class="p">:</span> <span class="kt">Int</span>
  <span class="k">let</span> <span class="nv">type</span><span class="p">:</span> <span class="kt">TPType</span>
  <span class="kd">private(set)</span> <span class="k">var</span> <span class="nv">tags</span><span class="p">:</span> <span class="p">[</span><span class="kt">Tag</span><span class="p">]</span>
  <span class="kd">private(set)</span> <span class="k">var</span> <span class="nv">children</span><span class="p">:</span> <span class="p">[</span><span class="kt">TMType</span><span class="p">]</span>
  
  <span class="c1">// MARK: Update Functionality</span>
  <span class="k">mutating</span> <span class="kd">func</span> <span class="nf">append</span><span class="p">(</span><span class="nv">tag</span><span class="p">:</span> <span class="kt">Tag</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">tags</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="n">tag</span><span class="p">)</span>
  <span class="p">}</span>
  
  <span class="k">mutating</span> <span class="kd">func</span> <span class="nf">append</span><span class="p">(</span><span class="nv">child</span><span class="p">:</span> <span class="kt">TMType</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">children</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="n">child</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<h2 id="regex-builder">Regex Builder</h2>

<p>Regex is a powerful tool for parsing strings with known formats.</p>

<p>You may be familiar with something like this <code class="language-plaintext highlighter-rouge">/\d{3}\D?\d{3}\D?\d{4}/</code> in terms of a phone number validation regex.</p>

<p>In Swift 5.7, RegexBuilder was added, which is a bit different. There are plenty of blog posts about how RegexBuilder works, so I’ll just talk about my use of it.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Set up references</span>
<span class="k">let</span> <span class="nv">indentRef</span> <span class="o">=</span> <span class="kt">Reference</span><span class="p">(</span><span class="kt">Substring</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">hyphenRef</span> <span class="o">=</span> <span class="kt">Reference</span><span class="p">(</span><span class="kt">Substring</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">colonRef</span> <span class="o">=</span> <span class="kt">Reference</span><span class="p">(</span><span class="kt">Substring</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">textRef</span> <span class="o">=</span> <span class="kt">Reference</span><span class="p">(</span><span class="kt">Substring</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>

<span class="c1">// Set up the regex</span>
<span class="k">let</span> <span class="nv">tmTypeSearch</span> <span class="o">=</span> <span class="kt">Regex</span> <span class="p">{</span>
	<span class="c1">// Initial Indent</span>
	<span class="kt">Capture</span><span class="p">(</span><span class="nv">as</span><span class="p">:</span> <span class="n">indentRef</span><span class="p">)</span> <span class="p">{</span>
		<span class="kt">ZeroOrMore</span><span class="p">(</span><span class="o">.</span><span class="n">whitespace</span><span class="p">)</span>
	<span class="p">}</span>
	<span class="c1">// Whether or not it's a task</span>
	<span class="kt">Capture</span><span class="p">(</span><span class="nv">as</span><span class="p">:</span> <span class="n">hyphenRef</span><span class="p">)</span> <span class="p">{</span>
		<span class="kt">Optionally</span> <span class="p">{</span>
			<span class="s">"-"</span>
		<span class="p">}</span>
	<span class="p">}</span>
	<span class="kt">ZeroOrMore</span><span class="p">(</span><span class="o">.</span><span class="n">whitespace</span><span class="p">)</span>
	<span class="c1">// Text of the element (including tags)</span>
	<span class="kt">Capture</span><span class="p">(</span><span class="nv">as</span><span class="p">:</span> <span class="n">textRef</span><span class="p">)</span> <span class="p">{</span>
		<span class="kt">OneOrMore</span><span class="p">(</span>
			<span class="kt">ChoiceOf</span> <span class="p">{</span>
				<span class="c1">// All of the valid characters for the text area.</span>
				<span class="kt">CharacterClass</span><span class="p">(</span><span class="o">.</span><span class="n">anyNonNewline</span><span class="p">)</span>
			<span class="p">},</span> <span class="o">.</span><span class="n">reluctant</span>
		<span class="p">)</span>
	<span class="p">}</span>
	<span class="c1">// Whether it's a project or not</span>
	<span class="kt">Capture</span><span class="p">(</span><span class="nv">as</span><span class="p">:</span> <span class="n">colonRef</span><span class="p">)</span> <span class="p">{</span>
		<span class="kt">Optionally</span> <span class="p">{</span>
			<span class="s">":"</span>
		<span class="p">}</span>
	<span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>This breaks a string into several elements that the regex can be captured and then parsed.</p>

<p>If there’s white space at the front, that can be parsed to figure out the indentation level.
If there’s a hyphen, that can tell us that this should become a <code class="language-plaintext highlighter-rouge">task</code>.
If there’s a colon, that can tell us that this should become a <code class="language-plaintext highlighter-rouge">project</code>.
Whatever text is there, becomes the basis of the text for the model.</p>

<h3 id="gotchas-1">Gotchas</h3>

<p>We’ll have to do a bit of additional processing to handle the tags. Otherwise <code class="language-plaintext highlighter-rouge">- Task</code>, <code class="language-plaintext highlighter-rouge">- Task @tag</code>, <code class="language-plaintext highlighter-rouge">- Task @tag(payload)</code> would not have the identical <code class="language-plaintext highlighter-rouge">TPType</code>.</p>

<p>Happily, regex can help with that, too.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Set up references</span>
<span class="k">let</span> <span class="nv">textRef</span> <span class="o">=</span> <span class="kt">Reference</span><span class="p">(</span><span class="kt">Substring</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">payloadRef</span> <span class="o">=</span> <span class="kt">Reference</span><span class="p">(</span><span class="kt">Substring</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>

<span class="c1">// Set up the regexes</span>
<span class="c1">// ie; @&lt;tag&gt;(&lt;payload&gt;)</span>
<span class="k">let</span> <span class="nv">payloadSearch</span> <span class="o">=</span> <span class="kt">Regex</span> <span class="p">{</span>
	<span class="s">"@"</span>
	<span class="kt">Capture</span><span class="p">(</span><span class="nv">as</span><span class="p">:</span> <span class="n">textRef</span><span class="p">)</span> <span class="p">{</span>
		<span class="kt">OneOrMore</span><span class="p">(</span><span class="o">.</span><span class="n">word</span><span class="p">)</span>
	<span class="p">}</span>
	<span class="s">"("</span>
	<span class="kt">Capture</span><span class="p">(</span><span class="nv">as</span><span class="p">:</span> <span class="n">payloadRef</span><span class="p">)</span> <span class="p">{</span>
		<span class="kt">OneOrMore</span><span class="p">(</span>
			<span class="kt">ChoiceOf</span> <span class="p">{</span>
				<span class="kt">CharacterClass</span><span class="p">(</span><span class="o">.</span><span class="n">word</span><span class="p">)</span>
				<span class="kt">CharacterClass</span><span class="p">(</span><span class="o">.</span><span class="n">whitespace</span><span class="p">)</span>
				<span class="kt">CharacterClass</span><span class="p">(</span><span class="o">.</span><span class="n">digit</span><span class="p">)</span>
				<span class="s">"-"</span>
				<span class="s">":"</span>
			<span class="p">}</span>
		<span class="p">)</span>
	<span class="p">}</span>
	<span class="s">")"</span>
	<span class="kt">ZeroOrMore</span><span class="p">(</span><span class="o">.</span><span class="n">whitespace</span><span class="p">)</span>
<span class="p">}</span>
<span class="c1">// ie; @&lt;tag&gt;</span>
<span class="k">let</span> <span class="nv">tagSearch</span> <span class="o">=</span> <span class="kt">Regex</span> <span class="p">{</span>
	<span class="s">"@"</span>
	<span class="kt">Capture</span><span class="p">(</span><span class="nv">as</span><span class="p">:</span> <span class="n">textRef</span><span class="p">)</span> <span class="p">{</span>
		<span class="kt">OneOrMore</span><span class="p">(</span><span class="o">.</span><span class="n">word</span><span class="p">)</span>
	<span class="p">}</span>
	<span class="kt">ZeroOrMore</span><span class="p">(</span><span class="o">.</span><span class="n">whitespace</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<p>The parsing of this is a little more complicated, as we have two different regex to check against. (I tried to make a unified regex that would handle both <code class="language-plaintext highlighter-rouge">@tag</code> and <code class="language-plaintext highlighter-rouge">@tag(payload)</code>, but I couldn’t figure out how to manage it and capture the payload properly, so I decided to just go with separate regex parsers, parse with each, and then replace <code class="language-plaintext highlighter-rouge">Tag.tag</code> models with the proper <code class="language-plaintext highlighter-rouge">Tag.payloadTag</code> models, if the exist.</p>

<h2 id="using-our-regex-parsers">Using our regex parsers</h2>

<p>We expect whole matches for the <code class="language-plaintext highlighter-rouge">tmTypeSearch</code>. If the whole record doesn’t match, it’s not a valid model.</p>

<p>The <code class="language-plaintext highlighter-rouge">tagSearch</code> and <code class="language-plaintext highlighter-rouge">payloadSearch</code> parses, we want to get all the matches within the string and turn them into arrays of our <code class="language-plaintext highlighter-rouge">Tag</code> model.</p>

<p>We also needed another pass for text with tags so that we can get the text up until the first <code class="language-plaintext highlighter-rouge">@</code> to normalize the text.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">normalizeText</span> <span class="o">=</span> <span class="kt">Regex</span> <span class="p">{</span>
	<span class="kt">Capture</span><span class="p">(</span><span class="nv">as</span><span class="p">:</span> <span class="n">textRef</span><span class="p">)</span> <span class="p">{</span>
		<span class="kt">OneOrMore</span><span class="p">(</span><span class="o">.</span><span class="n">anyNonNewline</span><span class="p">,</span> <span class="o">.</span><span class="n">reluctant</span><span class="p">)</span>
	<span class="p">}</span>
	<span class="kt">ZeroOrMore</span><span class="p">(</span><span class="o">.</span><span class="n">whitespace</span><span class="p">)</span>
	<span class="c1">// We know there's a tag.</span>
	<span class="s">"@"</span>
	<span class="c1">// And anything after to fill out the line.</span>
	<span class="kt">ZeroOrMore</span><span class="p">(</span><span class="o">.</span><span class="n">anyNonNewline</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<h2 id="it-cant-be-this-easy-right">It can’t be this easy, right?</h2>

<p>Parsing a <code class="language-plaintext highlighter-rouge">String</code> into a <code class="language-plaintext highlighter-rouge">TMType</code> model was a necessary first step, but we also need to be able to turn an array of <code class="language-plaintext highlighter-rouge">String</code> objects into a normalized <code class="language-plaintext highlighter-rouge">TMType</code> array with populated <code class="language-plaintext highlighter-rouge">children</code> within the <code class="language-plaintext highlighter-rouge">TMType</code> models.</p>

<p>When the tabLevel is higher, it should become the child of the last child of the current <code class="language-plaintext highlighter-rouge">TMType</code> model. If the tabLevel is within the current <code class="language-plaintext highlighter-rouge">TMType</code> model, it should become a child at the appropriate level. Otherwise, we should append it to the list of <code class="language-plaintext highlighter-rouge">TMType</code> models, this becomes the new current <code class="language-plaintext highlighter-rouge">TMType</code> model, and continue parsing.</p>

<h3 id="gotchas-2">Gotchas</h3>

<p>Some useful recursive helper functions made this a lot easier:</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">func lastChild() -&gt; TMType?</code></p>

    <p>A method that will walk the model to find the last child or child’s child.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">func lastChild(with tabLevel: Int) -&gt; TMType?</code></p>

    <p>A method that will walk the model to find the last child or child’s child with a specific indentation level.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">func parent(of child: TMType) -&gt; TMType?</code></p>

    <p>A method that will walk the models to find the parent of a specific model.</p>
  </li>
  <li>
    <p>@discardableResult <code class="language-plaintext highlighter-rouge">mutating func replace(child: TMType, with updatedChild: TMType) -&gt; Bool</code></p>

    <p>A method to find a child within the <code class="language-plaintext highlighter-rouge">children</code> array and replace that object with the updated version.</p>
  </li>
</ul>

<p>With these, we can now dig through the various children to find the appropriate places to insert models at the proper level and then replace things up the chain.</p>

<h2 id="from-models-back-to-strings">From Models Back to Strings</h2>

<p>Converting from a <code class="language-plaintext highlighter-rouge">TMType</code> model to a <code class="language-plaintext highlighter-rouge">String</code> was much easier.</p>

<p>I made life easier for myself by setting up a simple protocol that each level of the <code class="language-plaintext highlighter-rouge">TMType</code> model could conform to.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">protocol</span> <span class="kt">FormattedTMType</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">toString</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>Then I could stitch the pieces together appropriately:</p>

<h3 id="tag-models">Tag models</h3>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">var</span> <span class="nv">toString</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span>
	<span class="k">switch</span> <span class="k">self</span> <span class="p">{</span>
	<span class="k">case</span> <span class="o">.</span><span class="nf">tag</span><span class="p">(</span><span class="k">let</span> <span class="nv">tag</span><span class="p">):</span>
		<span class="k">return</span> <span class="s">"@</span><span class="se">\(</span><span class="n">tag</span><span class="se">)</span><span class="s">"</span>
	<span class="k">case</span> <span class="o">.</span><span class="nf">payloadTag</span><span class="p">(</span><span class="k">let</span> <span class="nv">tag</span><span class="p">,</span> <span class="k">let</span> <span class="nv">payload</span><span class="p">):</span>
		<span class="k">return</span> <span class="s">"@</span><span class="se">\(</span><span class="n">tag</span><span class="se">)</span><span class="s">(</span><span class="se">\(</span><span class="n">payload</span><span class="se">)</span><span class="s">)"</span>
	<span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<h3 id="tptype-models">TPType models</h3>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">var</span> <span class="nv">toString</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span>
	<span class="k">switch</span> <span class="k">self</span> <span class="p">{</span>
	<span class="k">case</span> <span class="o">.</span><span class="nf">project</span><span class="p">(</span><span class="k">let</span> <span class="nv">string</span><span class="p">):</span>
		<span class="k">return</span> <span class="s">"</span><span class="se">\(</span><span class="n">string</span><span class="se">)</span><span class="s">:"</span>
	<span class="k">case</span> <span class="o">.</span><span class="nf">task</span><span class="p">(</span><span class="k">let</span> <span class="nv">string</span><span class="p">):</span>
		<span class="k">return</span> <span class="s">"- </span><span class="se">\(</span><span class="n">string</span><span class="se">)</span><span class="s">"</span>
	<span class="k">case</span> <span class="o">.</span><span class="nf">text</span><span class="p">(</span><span class="k">let</span> <span class="nv">string</span><span class="p">):</span>
		<span class="k">return</span> <span class="n">string</span>
	<span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<h3 id="tmtype-models">TMType models</h3>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">var</span> <span class="nv">toString</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span>
    <span class="c1">// Indicates the indentation level</span>
	<span class="k">var</span> <span class="nv">result</span> <span class="o">=</span> <span class="kt">String</span><span class="p">(</span><span class="nv">repeating</span><span class="p">:</span> <span class="s">"</span><span class="se">\t</span><span class="s">"</span><span class="p">,</span> <span class="nv">count</span><span class="p">:</span> <span class="n">tabLevel</span><span class="p">)</span>
    <span class="c1">// The `TPType` from above</span>
	<span class="n">result</span> <span class="o">+=</span> <span class="n">type</span><span class="o">.</span><span class="n">toString</span>
	<span class="c1">// The `Tag` from above, if present.</span>
	<span class="n">tags</span><span class="o">.</span><span class="n">forEach</span> <span class="p">{</span> <span class="n">tag</span> <span class="k">in</span>
		<span class="k">if</span> <span class="o">!</span><span class="n">result</span><span class="o">.</span><span class="n">isEmpty</span> <span class="p">{</span>
			<span class="n">result</span> <span class="o">+=</span> <span class="s">" "</span>
		<span class="p">}</span>
		<span class="n">result</span> <span class="o">+=</span> <span class="n">tag</span><span class="o">.</span><span class="n">toString</span>
	<span class="p">}</span>
	<span class="c1">// The children, if present.</span>
	<span class="k">guard</span> <span class="o">!</span><span class="n">children</span><span class="o">.</span><span class="n">isEmpty</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="n">result</span> <span class="p">}</span>

	<span class="n">children</span><span class="o">.</span><span class="n">forEach</span> <span class="p">{</span> <span class="n">child</span> <span class="k">in</span>
		<span class="k">if</span> <span class="o">!</span><span class="n">result</span><span class="o">.</span><span class="n">isEmpty</span> <span class="p">{</span>
			<span class="n">result</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span>
		<span class="p">}</span>
		<span class="n">result</span> <span class="o">+=</span> <span class="n">child</span><span class="o">.</span><span class="n">toString</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="n">result</span>
<span class="p">}</span></code></pre></figure>

<h2 id="testing">Testing</h2>

<p>Testing here was especially important, so that the parsers could be verified both for the expected simple path and some of the more complicated edge cases.</p>

<p>We also needed to make sure that the tag payloads for our date models were parsing properly and we’re able to convert from a <code class="language-plaintext highlighter-rouge">Tag.payloadTag</code> to the <code class="language-plaintext highlighter-rouge">TMDateType</code> models properly, so that we can better use these with our <code class="language-plaintext highlighter-rouge">EKManager</code> <code class="language-plaintext highlighter-rouge">EventKit</code> wrapper.</p>

<h2 id="payload-parsing">Payload Parsing</h2>

<p>Parsing the different date formats can be looks like it might difficult. Here’s a list of the formats, from our previous blog entries:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">2024-12-01</code></li>
  <li><code class="language-plaintext highlighter-rouge">2024-12-01 12:01</code></li>
  <li><code class="language-plaintext highlighter-rouge">2024-12-01 12:01-13:00</code> which could also be <code class="language-plaintext highlighter-rouge">2024-12-01 12:01 thru 13:00</code></li>
  <li><code class="language-plaintext highlighter-rouge">2024-12-01 12:01-2024-12-31 12:31</code> which could also be <code class="language-plaintext highlighter-rouge">2024-12-01 12:01 thru 2024-12-31 12:31</code></li>
</ul>

<h3 id="gotchas-3">Gotchas</h3>

<p>One solution might be to try to build a unified regex to handle all possible cases and capture the appropriate variables.</p>

<p>Another solution may be a cleaner more brute force solution that I went with:</p>

<p>I built a series of regex matches for each of the formats above. Then I just needed to figure out the possible match to check based upon the string length.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">/// Helper Enum for Formatting...</span>
<span class="kd">enum</span> <span class="kt">DateParameterFormat</span><span class="p">:</span> <span class="kt">Equatable</span> <span class="p">{</span>
	<span class="k">case</span> <span class="n">date</span>
	<span class="k">case</span> <span class="n">dateTime</span>
	<span class="k">case</span> <span class="n">dateTimeEndTime</span>
	<span class="k">case</span> <span class="n">dateTimeDateTime</span>

	<span class="nf">init</span><span class="p">?(</span><span class="n">from</span> <span class="nv">string</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">switch</span> <span class="n">string</span><span class="o">.</span><span class="n">count</span> <span class="p">{</span>
		<span class="k">case</span> <span class="mi">10</span><span class="p">:</span>
			<span class="c1">// YYYY-mm-dd</span>
			<span class="k">self</span> <span class="o">=</span> <span class="o">.</span><span class="n">date</span>
		<span class="k">case</span> <span class="mi">16</span><span class="p">:</span>
			<span class="c1">// YYYY-mm-dd HH:mm</span>
			<span class="k">self</span> <span class="o">=</span> <span class="o">.</span><span class="n">dateTime</span>
		<span class="k">case</span> <span class="mi">22</span><span class="o">...</span><span class="mi">32</span><span class="p">:</span>
			<span class="c1">// YYYY-mm-dd HH:mm-HH:mm</span>
			<span class="c1">// ...</span>
			<span class="c1">// YYYY-mm-dd HH:mm through HH:mm</span>
			<span class="k">self</span> <span class="o">=</span> <span class="o">.</span><span class="n">dateTimeEndTime</span>
		<span class="k">case</span> <span class="mi">33</span><span class="o">...</span><span class="mi">41</span><span class="p">:</span>
			<span class="c1">// YYYY-mm-dd HH:mm-YYYY-mm-dd HH:mm</span>
			<span class="c1">// ...</span>
			<span class="c1">// YYYY-mm-dd HH:mm through YYYY-mm-dd HH:mm</span>
			<span class="k">self</span> <span class="o">=</span> <span class="o">.</span><span class="n">dateTimeDateTime</span>
		<span class="k">default</span><span class="p">:</span>
			<span class="k">return</span> <span class="kc">nil</span>
		<span class="p">}</span>
	<span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>Once that was done, the logic to compare the appropriate regex was equally clear. Check against the appropriate regex pattern, extract the captive values, plug them into our previously created date parameter structures. We can then extract the dates into our more generic <code class="language-plaintext highlighter-rouge">TMDateType</code>. My choice of doing that is to ensure that our validation logic is being called and the dates are valid.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">toTMDateType</span><span class="p">()</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">TMDateType</span><span class="p">?</span> <span class="p">{</span>
	<span class="k">guard</span> <span class="k">let</span> <span class="nv">format</span> <span class="o">=</span> <span class="kt">TMDateType</span><span class="o">.</span><span class="kt">DateParameterFormat</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="k">self</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="o">...</span>
	<span class="c1">// Check the possible format against our regex patterns...</span>
	<span class="k">switch</span> <span class="n">format</span> <span class="p">{</span>
	<span class="k">case</span> <span class="o">.</span><span class="nv">date</span><span class="p">:</span>
		<span class="c1">// yyyy-MM-dd</span>
		<span class="k">guard</span> <span class="k">let</span> <span class="nv">match</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="nf">wholeMatch</span><span class="p">(</span><span class="nv">of</span><span class="p">:</span> <span class="n">dateMatch</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="k">let</span> <span class="nv">year</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">yearRef</span><span class="p">]</span>
		<span class="k">let</span> <span class="nv">month</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">monthRef</span><span class="p">]</span>
		<span class="k">let</span> <span class="nv">day</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">dayRef</span><span class="p">]</span>

		<span class="k">return</span> <span class="k">try</span> <span class="kt">DateParameters</span><span class="p">(</span><span class="nv">year</span><span class="p">:</span> <span class="n">year</span><span class="p">,</span> <span class="nv">month</span><span class="p">:</span> <span class="n">month</span><span class="p">,</span> <span class="nv">day</span><span class="p">:</span> <span class="n">day</span><span class="p">)</span><span class="o">.</span><span class="nf">toDateType</span><span class="p">()</span>

	<span class="k">case</span> <span class="o">.</span><span class="nv">dateTime</span><span class="p">:</span>
		<span class="c1">// yyyy-MM-dd HH:mm</span>
		<span class="k">guard</span> <span class="k">let</span> <span class="nv">match</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="nf">wholeMatch</span><span class="p">(</span><span class="nv">of</span><span class="p">:</span> <span class="n">dateTimeMatch</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="k">let</span> <span class="nv">year</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">yearRef</span><span class="p">]</span>
		<span class="k">let</span> <span class="nv">month</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">monthRef</span><span class="p">]</span>
		<span class="k">let</span> <span class="nv">day</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">dayRef</span><span class="p">]</span>
		<span class="k">let</span> <span class="nv">hour</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">hourRef</span><span class="p">]</span>
		<span class="k">let</span> <span class="nv">minute</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">minuteRef</span><span class="p">]</span>

		<span class="k">return</span> <span class="k">try</span> <span class="kt">DateTimeParameters</span><span class="p">(</span><span class="nv">date</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">year</span><span class="p">:</span> <span class="n">year</span><span class="p">,</span> <span class="nv">month</span><span class="p">:</span> <span class="n">month</span><span class="p">,</span> <span class="nv">day</span><span class="p">:</span> <span class="n">day</span><span class="p">),</span>
									  <span class="nv">time</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">hour</span><span class="p">:</span> <span class="n">hour</span><span class="p">,</span> <span class="nv">minute</span><span class="p">:</span> <span class="n">minute</span><span class="p">))</span><span class="o">.</span><span class="nf">toDateType</span><span class="p">()</span>

	<span class="k">case</span> <span class="o">.</span><span class="nv">dateTimeEndTime</span><span class="p">:</span>
		<span class="c1">// yyyy-MM-dd HH:mm-HH:mm</span>
		<span class="k">guard</span> <span class="k">let</span> <span class="nv">match</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="nf">wholeMatch</span><span class="p">(</span><span class="nv">of</span><span class="p">:</span> <span class="n">dateTimeEndTimeMatch</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="k">let</span> <span class="nv">year</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">yearRef</span><span class="p">]</span>
		<span class="k">let</span> <span class="nv">month</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">monthRef</span><span class="p">]</span>
		<span class="k">let</span> <span class="nv">day</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">dayRef</span><span class="p">]</span>
		<span class="k">let</span> <span class="nv">hour</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">hourRef</span><span class="p">]</span>
		<span class="k">let</span> <span class="nv">minute</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">minuteRef</span><span class="p">]</span>
		<span class="k">let</span> <span class="nv">secondHour</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">secondHourRef</span><span class="p">]</span>
		<span class="k">let</span> <span class="nv">secondMinute</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">secondMinuteRef</span><span class="p">]</span>

		<span class="k">return</span> <span class="k">try</span> <span class="kt">DateTimeEndTimeParameters</span><span class="p">(</span><span class="nv">date</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">year</span><span class="p">:</span> <span class="n">year</span><span class="p">,</span> <span class="nv">month</span><span class="p">:</span> <span class="n">month</span><span class="p">,</span> <span class="nv">day</span><span class="p">:</span> <span class="n">day</span><span class="p">),</span>
											 <span class="nv">time</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">hour</span><span class="p">:</span> <span class="n">hour</span><span class="p">,</span> <span class="nv">minute</span><span class="p">:</span> <span class="n">minute</span><span class="p">),</span>
											 <span class="nv">endTime</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">hour</span><span class="p">:</span> <span class="n">secondHour</span><span class="p">,</span> <span class="nv">minute</span><span class="p">:</span> <span class="n">secondMinute</span><span class="p">))</span><span class="o">.</span><span class="nf">toDateType</span><span class="p">()</span>

	<span class="k">case</span> <span class="o">.</span><span class="nv">dateTimeDateTime</span><span class="p">:</span>
		<span class="c1">// yyyy-MM-dd HH:mm thru yyyy-MM-dd HH:mm</span>
		<span class="k">guard</span> <span class="k">let</span> <span class="nv">match</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="nf">wholeMatch</span><span class="p">(</span><span class="nv">of</span><span class="p">:</span> <span class="n">dateTimeDateTimeMatch</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="k">let</span> <span class="nv">year</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">yearRef</span><span class="p">]</span>
		<span class="k">let</span> <span class="nv">month</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">monthRef</span><span class="p">]</span>
		<span class="k">let</span> <span class="nv">day</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">dayRef</span><span class="p">]</span>
		<span class="k">let</span> <span class="nv">hour</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">hourRef</span><span class="p">]</span>
		<span class="k">let</span> <span class="nv">minute</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">minuteRef</span><span class="p">]</span>
		<span class="k">let</span> <span class="nv">secondYear</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">secondYearRef</span><span class="p">]</span>
		<span class="k">let</span> <span class="nv">secondMonth</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">secondMonthRef</span><span class="p">]</span>
		<span class="k">let</span> <span class="nv">secondDay</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">secondDayRef</span><span class="p">]</span>
		<span class="k">let</span> <span class="nv">secondHour</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">secondHourRef</span><span class="p">]</span>
		<span class="k">let</span> <span class="nv">secondMinute</span> <span class="o">=</span> <span class="n">match</span><span class="p">[</span><span class="n">secondMinuteRef</span><span class="p">]</span>

		<span class="k">return</span> <span class="k">try</span> <span class="kt">DateTimeDateTimeParameters</span><span class="p">(</span><span class="nv">start</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">date</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">year</span><span class="p">:</span> <span class="n">year</span><span class="p">,</span> <span class="nv">month</span><span class="p">:</span> <span class="n">month</span><span class="p">,</span> <span class="nv">day</span><span class="p">:</span> <span class="n">day</span><span class="p">),</span>
														   <span class="nv">time</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">hour</span><span class="p">:</span> <span class="n">hour</span><span class="p">,</span> <span class="nv">minute</span><span class="p">:</span> <span class="n">minute</span><span class="p">)),</span>
											  <span class="nv">end</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">date</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">year</span><span class="p">:</span> <span class="n">secondYear</span><span class="p">,</span> <span class="nv">month</span><span class="p">:</span> <span class="n">secondMonth</span><span class="p">,</span> <span class="nv">day</span><span class="p">:</span> <span class="n">secondDay</span><span class="p">),</span>
														 <span class="nv">time</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">hour</span><span class="p">:</span> <span class="n">secondHour</span><span class="p">,</span> <span class="nv">minute</span><span class="p">:</span> <span class="n">secondMinute</span><span class="p">)))</span><span class="o">.</span><span class="nf">toDateType</span><span class="p">()</span>
	<span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>Converting from formatted date strings to <code class="language-plaintext highlighter-rouge">TMDateType</code> will give us start and end dates, if appropriate.</p>

<p>Then, we just need to be able to convert from <code class="language-plaintext highlighter-rouge">TMDateType</code> back to formatted date strings. So I added a separator type enum and we can leverage the date parameter format we used above for this:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">toFormattedDateString</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="kt">DateParameterFormat</span><span class="p">,</span>
						   <span class="nv">separator</span><span class="p">:</span> <span class="kt">DateFormatSeparatorType</span> <span class="o">=</span> <span class="o">.</span><span class="n">compact</span><span class="p">,</span>
						   <span class="nv">withSpaces</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">true</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">String</span><span class="p">?</span> <span class="p">{</span>
<span class="o">...</span>
<span class="p">}</span></code></pre></figure>

<p>This allows us to ensure that the formats are valid by using business logic to verify things such as the <code class="language-plaintext highlighter-rouge">.date</code> or <code class="language-plaintext highlighter-rouge">.dateTime</code> are only valid if there’s no <code class="language-plaintext highlighter-rouge">endDate</code> property of the <code class="language-plaintext highlighter-rouge">TMDateType</code>. Likewise, <code class="language-plaintext highlighter-rouge">.dateTimeEndTime</code> is only valid if the <code class="language-plaintext highlighter-rouge">endDate</code> is within the same calendar day as the <code class="language-plaintext highlighter-rouge">startDate</code> property.</p>

<h2 id="testing-1">Testing</h2>

<p>Unit testing allowed me to verify edge cases in my format parsing and helped me make sure that all of the variants worked properly.</p>

<p>The unit tests are repetitive but thorough.</p>

<hr />

<p>Next week, we start working on the UI layer and getting into the SwiftUI of it all.</p>]]></content><author><name>Jp</name></author><category term="coding" /><category term="taskmanager" /><category term="text-conversion" /><category term="regex" /><summary type="html"><![CDATA[One of the biggest factors into why I love TaskPaper is the way that it effortlessly converts to and from text files. For my TaskManager app, I consider that to be must have functionality as well. We’ll be talking about String parsing as we break down converting back and forth from a hypothetical TaskPaper project with multiple tasks. Some of these tasks may have a variety of tags. As well as the possibility of notes. All of the code for this blog post is in this sample code repo.]]></summary></entry><entry><title type="html">Testing Event Kit Manager</title><link href="https://jp4mobile.com/coding/2024/11/17/TestingEventKitManager.html" rel="alternate" type="text/html" title="Testing Event Kit Manager" /><published>2024-11-17T14:00:01+00:00</published><updated>2024-11-17T14:00:01+00:00</updated><id>https://jp4mobile.com/coding/2024/11/17/TestingEventKitManager</id><content type="html" xml:base="https://jp4mobile.com/coding/2024/11/17/TestingEventKitManager.html"><![CDATA[<p>Unit Testing a manager that wraps third-party functionality can be challenging. You don’t want to waste your time and energy testing something that one assumes that the vendor has already tested and supports. If they’ve given you an API, you need to trust that API is accurate.</p>

<p>What you want to test is your business logic and your code. There are two ways to do that. One is to leverage a pseudo object that conforms to the API but you can control the outputs. Ie; if you should be thrown an error, you can trigger that; or if you should be given a response, you can control what the response is.</p>

<p>This lets your tests focus on your business logic, not someone else’s.</p>

<p>All of the code for this blog post is in this <a href="https://github.com/Jp4Mobile/SampleCode/tree/main/posts/projects/TestingEKWrapper-2024-11-17">sample code repo</a>.</p>

<!--more-->

<h2 id="faking-ekeventstore">Faking EKEventStore</h2>

<p>As we learned in the <a href="https://www.jp4mobile.com/coding/2024/11/10/EventKitManager.html">EventKit Manager blog post</a>, the main path into interacting with the <code class="language-plaintext highlighter-rouge">EventKit</code> is the <code class="language-plaintext highlighter-rouge">EKEventStore</code> object. So that’s what we’re going to fake and inject into our EventKitManager and test, so that we can verify our code.</p>

<p>This post might be easier, if you follow along in the sample code. In the <code class="language-plaintext highlighter-rouge">EKManagerTests</code> file, we create a fake event store class: <code class="language-plaintext highlighter-rouge">FakeEKEventStore</code>. This needs to conform to the functionality of the <code class="language-plaintext highlighter-rouge">EKEventStore</code>, but in each case it should allow the injection of a canned return or thrown response to the caller.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">FakeEKEventStore</span><span class="p">:</span> <span class="kt">EKEventStore</span> <span class="p">{</span>
   <span class="k">var</span> <span class="nv">errorToThrow</span><span class="p">:</span> <span class="kt">Error</span><span class="p">?</span>

   <span class="c1">// MARK: - Access</span>
   <span class="k">var</span> <span class="nv">accessResult</span><span class="p">:</span> <span class="kt">Bool</span><span class="p">?</span>

   <span class="k">override</span> <span class="kd">func</span> <span class="nf">requestFullAccessToEvents</span><span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">Bool</span> <span class="p">{</span>
      <span class="k">if</span> <span class="k">let</span> <span class="nv">errorToThrow</span> <span class="p">{</span>
         <span class="k">throw</span> <span class="n">errorToThrow</span>
      <span class="p">}</span>

      <span class="k">if</span> <span class="k">let</span> <span class="nv">accessResult</span> <span class="p">{</span>
         <span class="k">return</span> <span class="n">accessResult</span>
      <span class="p">}</span>
        
      <span class="k">return</span> <span class="k">try</span> <span class="k">await</span> <span class="k">super</span><span class="o">.</span><span class="nf">requestFullAccessToEvents</span><span class="p">()</span>
   <span class="p">}</span>

   <span class="k">override</span> <span class="kd">func</span> <span class="nf">requestFullAccessToReminders</span><span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">Bool</span> <span class="p">{</span>
      <span class="k">if</span> <span class="k">let</span> <span class="nv">errorToThrow</span> <span class="p">{</span>
         <span class="k">throw</span> <span class="n">errorToThrow</span>
      <span class="p">}</span>
        
      <span class="k">if</span> <span class="k">let</span> <span class="nv">accessResult</span> <span class="p">{</span>
         <span class="k">return</span> <span class="n">accessResult</span>
      <span class="p">}</span>
        
      <span class="k">return</span> <span class="k">try</span> <span class="k">await</span> <span class="k">super</span><span class="o">.</span><span class="nf">requestFullAccessToReminders</span><span class="p">()</span>
   <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>This allows us to inject <code class="language-plaintext highlighter-rouge">errorToThrow</code> and <code class="language-plaintext highlighter-rouge">accessResult</code> into our access request functions: <code class="language-plaintext highlighter-rouge">requestFullAccessToEvents</code> and <code class="language-plaintext highlighter-rouge">requestFullAccessToReminders</code>.</p>

<p>The logic is relatively simple to follow:</p>

<ul>
  <li><em>If there’s an error, throw it.</em></li>
  <li><em>Otherwise, if there’s a result, return it.</em></li>
  <li><em>And the fallback, if nothing was injected, make the actual call.</em></li>
</ul>

<h3 id="gotchas">Gotchas</h3>

<p>Falling through and making the actual call may or may not meet your needs. My rule of thumb when I was faking the event store was whether or not the calls were changing things within the event store.</p>

<h2 id="using-the-fake-in-tests">Using the fake in tests</h2>

<p>Different people may have different practices in terms of how they name and write their unit tests. Over time, I’ve learned that when I go back to my unit tests, after weeks or months of other work, clarity is much more useful than brevity, so that I know exactly what should be being tested, what the conditions are and what the expectations are. That’s one of the reasons that I name my tests as I do.</p>

<p>Let’s break down what this test is doing:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// MARK: - Access Wrapper Tests</span>
<span class="c1">// MARK: Events</span>
<span class="kd">func</span> <span class="nf">test_requestCalendarAccess_whenThrowingAnError_thenThrowsError</span><span class="p">()</span> <span class="k">async</span> <span class="p">{</span>
  <span class="c1">// Set up injection</span>
  <span class="k">let</span> <span class="nv">fakeEventStore</span> <span class="o">=</span> <span class="kt">FakeEKEventStore</span><span class="p">()</span>
  <span class="n">fakeEventStore</span><span class="o">.</span><span class="n">errorToThrow</span> <span class="o">=</span> <span class="kt">TestError</span><span class="o">.</span><span class="n">error</span>
  <span class="n">sut</span> <span class="o">=</span> <span class="kt">EKManager</span><span class="o">.</span><span class="n">shared</span>
  <span class="n">sut</span><span class="o">.</span><span class="n">eventStore</span> <span class="o">=</span> <span class="n">fakeEventStore</span>

  <span class="c1">// Verify that the error was thrown</span>
  <span class="k">do</span> <span class="p">{</span>
    <span class="k">try</span> <span class="k">await</span> <span class="n">sut</span><span class="o">.</span><span class="nf">requestCalendarAccess</span><span class="p">()</span>
    <span class="kt">XCTFail</span><span class="p">(</span><span class="s">"Should not have succeeded"</span><span class="p">)</span>
  <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
    <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">error</span> <span class="k">as?</span> <span class="kt">TestError</span><span class="p">,</span> <span class="o">.</span><span class="n">error</span><span class="p">)</span>
    <span class="kt">XCTAssertFalse</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">hasCalendarAccess</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>In my unit tests, I group tests by logic and then by area, which makes it much easier to read through the code and to find the tests at a later date. Then I name my unit tests by the function being tested, what the set up is and lastly what the expectations of the test is.</p>

<p>First we instantiate the fake event store, inject the error that our fake event store will throw, and then we inject the fake event store into the <code class="language-plaintext highlighter-rouge">EKManager</code> that will be testing.</p>

<p>Then we wrap the call and verify that it wasn’t succeeding, that the error is as expected, and lastly the manager access values are as expected.</p>

<h3 id="gotchas-1">Gotchas</h3>

<p>Testing best practice involves making sure at the end of your test (or in the teardown), you’ve reset your test environment back to the pre-test state. Otherwise, you may find that you’ve introduced flakiness in your test environment and subsequent tests may no longer conform to your assumptions, or even worse, you’ve added this same sort of issue to your environment on devices that you’re testing on.</p>

<p>In our case, we’re injecting a fake event store into a singleton. That could clearly cause problems. So some extra changes needed to be made to our <code class="language-plaintext highlighter-rouge">EKManager</code> as well as to our test environment.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="cp">#if DEBUG</span>

<span class="kd">extension</span> <span class="kt">EKManager</span> <span class="p">{</span>
   <span class="kd">func</span> <span class="nf">reset</span><span class="p">()</span> <span class="p">{</span>
      <span class="k">self</span><span class="o">.</span><span class="n">eventStore</span> <span class="o">=</span> <span class="kt">EKEventStore</span><span class="p">()</span>
      <span class="k">self</span><span class="o">.</span><span class="n">hasCalendarAccess</span> <span class="o">=</span> <span class="kc">false</span>
      <span class="k">self</span><span class="o">.</span><span class="n">hasReminderAccess</span> <span class="o">=</span> <span class="kc">false</span>
   <span class="p">}</span>
<span class="p">}</span>

<span class="cp">#endif</span></code></pre></figure>

<p>I only needed the reset function for unit tests, so wrapping it to ensure that it would never end up in a production release seems like adequate protection.</p>

<p>Then all we have to do is call it after every unit test. Happily, Apple has given us a function that’s called after every unit test called <code class="language-plaintext highlighter-rouge">tearDown()</code>. There’s also a similar class based function that is called after each test suite file has finished running, but for our purposes resetting after each unit test is sufficient.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">override</span> <span class="kd">func</span> <span class="nf">tearDown</span><span class="p">()</span> <span class="p">{</span>
   <span class="n">sut</span><span class="o">.</span><span class="nf">reset</span><span class="p">()</span>
   <span class="n">sut</span> <span class="o">=</span> <span class="kc">nil</span>

   <span class="k">super</span><span class="o">.</span><span class="nf">tearDown</span><span class="p">()</span>
<span class="p">}</span></code></pre></figure>

<h2 id="types-of-tests-to-run">Types of tests to run</h2>

<p>Looking at my access requesting methods, there’s not that much to test:</p>

<ul>
  <li>When the <code class="language-plaintext highlighter-rouge">EventKit</code> functions are called and they throw an error, throw an error from the <code class="language-plaintext highlighter-rouge">EKManager</code> wrapper function.</li>
  <li>When the user did not grant access, the <code class="language-plaintext highlighter-rouge">EKManager</code> does not think that the user has granted access.</li>
  <li>When the user did grant access, the <code class="language-plaintext highlighter-rouge">EKManager</code> thinks that the user has granted access.</li>
</ul>

<p>Here are the other two cases:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">test_requestCalendarAccess_whenUnsuccessful_doesNotGrantAccess</span><span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="p">{</span>
   <span class="c1">// Set up injection</span>
   <span class="k">let</span> <span class="nv">fakeEventStore</span> <span class="o">=</span> <span class="kt">FakeEKEventStore</span><span class="p">()</span>
   <span class="n">fakeEventStore</span><span class="o">.</span><span class="n">accessResult</span> <span class="o">=</span> <span class="kc">false</span>
   <span class="n">sut</span> <span class="o">=</span> <span class="kt">EKManager</span><span class="o">.</span><span class="n">shared</span>
   <span class="n">sut</span><span class="o">.</span><span class="n">eventStore</span> <span class="o">=</span> <span class="n">fakeEventStore</span>

   <span class="k">try</span> <span class="k">await</span> <span class="n">sut</span><span class="o">.</span><span class="nf">requestCalendarAccess</span><span class="p">()</span>
   <span class="kt">XCTAssertFalse</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">hasCalendarAccess</span><span class="p">)</span>
<span class="p">}</span>

<span class="kd">func</span> <span class="nf">test_requestCalendarAccess_whenSuccessful_grantsAccess</span><span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="p">{</span>
   <span class="c1">// Set up injection</span>
   <span class="k">let</span> <span class="nv">fakeEventStore</span> <span class="o">=</span> <span class="kt">FakeEKEventStore</span><span class="p">()</span>
   <span class="n">fakeEventStore</span><span class="o">.</span><span class="n">accessResult</span> <span class="o">=</span> <span class="kc">true</span>
   <span class="n">sut</span> <span class="o">=</span> <span class="kt">EKManager</span><span class="o">.</span><span class="n">shared</span>
   <span class="n">sut</span><span class="o">.</span><span class="n">eventStore</span> <span class="o">=</span> <span class="n">fakeEventStore</span>

   <span class="k">try</span> <span class="k">await</span> <span class="n">sut</span><span class="o">.</span><span class="nf">requestCalendarAccess</span><span class="p">()</span>
   <span class="kt">XCTAssertTrue</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">hasCalendarAccess</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<p>From the lowest level wrapper function, now we can build up to calling functions and validate our business logic.</p>

<p>We have a verify access function that checks the current access and then handles the call to request access from the user.</p>

<p>Similar to request access functions, this will do the following:</p>

<ul>
  <li>When the <code class="language-plaintext highlighter-rouge">EKManager</code> already has access, don’t check.</li>
  <li>When the <code class="language-plaintext highlighter-rouge">EKManager</code> wrapping function returns <code class="language-plaintext highlighter-rouge">false</code>, the verify access function throws an error indicating that.</li>
  <li>When the <code class="language-plaintext highlighter-rouge">EKManager</code> wrapping function returns <code class="language-plaintext highlighter-rouge">true</code>, the verifying access function does nothing.</li>
</ul>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// MARK: Verify Access</span>
<span class="kd">func</span> <span class="nf">test_verifyAccess_forEvents_whenAccessHasBeenGranted_doesNotRequestAccess</span><span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="p">{</span>
   <span class="k">let</span> <span class="nv">fakeEventStore</span> <span class="o">=</span> <span class="kt">FakeEKEventStore</span><span class="p">()</span>
   <span class="n">fakeEventStore</span><span class="o">.</span><span class="n">errorToThrow</span> <span class="o">=</span> <span class="kt">TestError</span><span class="o">.</span><span class="n">error</span>
   <span class="n">sut</span> <span class="o">=</span> <span class="kt">EKManager</span><span class="o">.</span><span class="n">shared</span>
   <span class="n">sut</span><span class="o">.</span><span class="n">eventStore</span> <span class="o">=</span> <span class="n">fakeEventStore</span>
   <span class="n">sut</span><span class="o">.</span><span class="nf">set</span><span class="p">(</span><span class="nv">hasCalendarAccess</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>

   <span class="k">try</span> <span class="k">await</span> <span class="n">sut</span><span class="o">.</span><span class="nf">verifyAccess</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="o">.</span><span class="n">event</span><span class="p">)</span>
   <span class="kt">XCTAssertTrue</span><span class="p">(</span><span class="n">sut</span><span class="o">.</span><span class="n">hasCalendarAccess</span><span class="p">)</span>
<span class="p">}</span>

<span class="kd">func</span> <span class="nf">test_verifyAccess_forEvents_withoutAccess_throwsError</span><span class="p">()</span> <span class="k">async</span> <span class="p">{</span>
   <span class="k">let</span> <span class="nv">fakeEventStore</span> <span class="o">=</span> <span class="kt">FakeEKEventStore</span><span class="p">()</span>
   <span class="n">fakeEventStore</span><span class="o">.</span><span class="n">accessResult</span> <span class="o">=</span> <span class="kc">false</span>
   <span class="n">sut</span> <span class="o">=</span> <span class="kt">EKManager</span><span class="o">.</span><span class="n">shared</span>
   <span class="n">sut</span><span class="o">.</span><span class="n">eventStore</span> <span class="o">=</span> <span class="n">fakeEventStore</span>

   <span class="k">do</span> <span class="p">{</span>
      <span class="k">try</span> <span class="k">await</span> <span class="n">sut</span><span class="o">.</span><span class="nf">verifyAccess</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="o">.</span><span class="n">event</span><span class="p">)</span>
      <span class="kt">XCTFail</span><span class="p">(</span><span class="s">"Should not succeed"</span><span class="p">)</span>
   <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
      <span class="k">if</span> <span class="k">let</span> <span class="nv">ekError</span> <span class="o">=</span> <span class="n">error</span> <span class="k">as?</span> <span class="kt">EKManager</span><span class="o">.</span><span class="kt">EKManagerError</span><span class="p">,</span>
         <span class="k">case</span> <span class="o">.</span><span class="n">calendarAccessDenied</span> <span class="o">=</span> <span class="n">ekError</span> <span class="p">{</span>
         <span class="k">return</span>
      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
         <span class="kt">XCTFail</span><span class="p">(</span><span class="s">"Unknown error: </span><span class="se">\(</span><span class="n">error</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
      <span class="p">}</span>
   <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>We didn’t need to test the happy path of the <code class="language-plaintext highlighter-rouge">verifyAccess(for:)</code> method directly, because they’ll be tested in the retrieval tests.</p>

<h2 id="retrieval-tests">Retrieval tests</h2>

<p>Let’s walk through the logic of our retrieval function for the <code class="language-plaintext highlighter-rouge">getEvents</code> method.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">getEvents</span><span class="p">(</span><span class="n">from</span> <span class="nv">startDate</span><span class="p">:</span> <span class="kt">Date</span> <span class="o">=</span> <span class="kt">Date</span><span class="p">())</span> <span class="k">async</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="p">[</span><span class="kt">EKEvent</span><span class="p">]</span> <span class="p">{</span>
   <span class="k">let</span> <span class="nv">calendar</span> <span class="o">=</span> <span class="k">try</span> <span class="k">await</span> <span class="nf">getCalendarToUse</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="o">.</span><span class="n">event</span><span class="p">)</span>

   <span class="k">let</span> <span class="nv">endDate</span> <span class="o">=</span> <span class="kt">Calendar</span><span class="o">.</span><span class="n">current</span><span class="o">.</span><span class="nf">date</span><span class="p">(</span><span class="nv">byAdding</span><span class="p">:</span> <span class="o">.</span><span class="n">month</span><span class="p">,</span> <span class="nv">value</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nv">to</span><span class="p">:</span> <span class="n">startDate</span><span class="p">)</span> <span class="p">??</span> <span class="n">startDate</span>
   <span class="k">let</span> <span class="nv">predicate</span> <span class="o">=</span> <span class="n">eventStore</span><span class="o">.</span><span class="nf">predicateForEvents</span><span class="p">(</span><span class="nv">withStart</span><span class="p">:</span> <span class="n">startDate</span><span class="p">,</span>
                                      <span class="nv">end</span><span class="p">:</span> <span class="n">endDate</span><span class="p">,</span>
                                      <span class="nv">calendars</span><span class="p">:</span> <span class="p">[</span><span class="n">calendar</span><span class="p">])</span>
   
   <span class="k">let</span> <span class="nv">allEvents</span> <span class="o">=</span> <span class="n">eventStore</span><span class="o">.</span><span class="nf">events</span><span class="p">(</span><span class="nv">matching</span><span class="p">:</span> <span class="n">predicate</span><span class="p">)</span>

   <span class="k">return</span> <span class="n">allEvents</span>
<span class="p">}</span></code></pre></figure>

<p>Digging into the <code class="language-plaintext highlighter-rouge">getCalendarToUse(for:)</code> method, you’ll dig down to the <code class="language-plaintext highlighter-rouge">retrieveEventCalendars()</code> method. Before it tries to retrieve the calendars, it’ll make sure that the user has granted access to the calendar events.</p>

<p>In this article, we’re mostly going to be concentrating on calendar events.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// MARK: - Retrieve Tests</span>
<span class="c1">// MARK: Events</span>
<span class="kd">func</span> <span class="nf">test_getEvents_withoutAccess_throwsError</span><span class="p">()</span> <span class="k">async</span> <span class="p">{</span>
   <span class="k">let</span> <span class="nv">fakeEventStore</span> <span class="o">=</span> <span class="kt">FakeEKEventStore</span><span class="p">()</span>
   <span class="n">fakeEventStore</span><span class="o">.</span><span class="n">accessResult</span> <span class="o">=</span> <span class="kc">false</span>
   <span class="n">fakeEventStore</span><span class="o">.</span><span class="n">eventsResult</span> <span class="o">=</span> <span class="p">[]</span>
   <span class="n">sut</span> <span class="o">=</span> <span class="kt">EKManager</span><span class="o">.</span><span class="n">shared</span>
   <span class="n">sut</span><span class="o">.</span><span class="n">eventStore</span> <span class="o">=</span> <span class="n">fakeEventStore</span>
   
   <span class="k">do</span> <span class="p">{</span>
      <span class="n">_</span> <span class="o">=</span> <span class="k">try</span> <span class="k">await</span> <span class="n">sut</span><span class="o">.</span><span class="nf">getEvents</span><span class="p">()</span>
      <span class="kt">XCTFail</span><span class="p">(</span><span class="s">"Should not succeed"</span><span class="p">)</span>
   <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
      <span class="k">if</span> <span class="k">let</span> <span class="nv">ekError</span> <span class="o">=</span> <span class="n">error</span> <span class="k">as?</span> <span class="kt">EKManager</span><span class="o">.</span><span class="kt">EKManagerError</span><span class="p">,</span>
         <span class="k">case</span> <span class="o">.</span><span class="n">calendarAccessDenied</span> <span class="o">=</span> <span class="n">ekError</span> <span class="p">{</span>
         <span class="k">return</span>
      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
         <span class="kt">XCTFail</span><span class="p">(</span><span class="s">"Unknown error: </span><span class="se">\(</span><span class="n">error</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
      <span class="p">}</span>
   <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>In some ways, the error cases are the easier ones to write. When there’s a problem, just catch and verify the error thrown.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">test_getEvents_withAccess_returnsEvents</span><span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="p">{</span>
   <span class="k">let</span> <span class="nv">fakeEventStore</span> <span class="o">=</span> <span class="kt">FakeEKEventStore</span><span class="p">()</span>
   <span class="k">let</span> <span class="nv">event</span> <span class="o">=</span> <span class="kt">EKEvent</span><span class="p">(</span><span class="nv">eventStore</span><span class="p">:</span> <span class="n">fakeEventStore</span><span class="p">)</span>
   <span class="n">fakeEventStore</span><span class="o">.</span><span class="n">eventsResult</span> <span class="o">=</span> <span class="p">[</span><span class="n">event</span><span class="p">]</span>
   <span class="n">sut</span> <span class="o">=</span> <span class="kt">EKManager</span><span class="o">.</span><span class="n">shared</span>
   <span class="n">sut</span><span class="o">.</span><span class="n">eventStore</span> <span class="o">=</span> <span class="n">fakeEventStore</span>
   <span class="n">sut</span><span class="o">.</span><span class="nf">set</span><span class="p">(</span><span class="nv">hasCalendarAccess</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>

   <span class="k">let</span> <span class="nv">events</span> <span class="o">=</span> <span class="k">try</span> <span class="k">await</span> <span class="n">sut</span><span class="o">.</span><span class="nf">getEvents</span><span class="p">()</span>
   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">events</span><span class="p">,</span> <span class="p">[</span><span class="n">event</span><span class="p">])</span>
<span class="p">}</span>

<span class="kd">func</span> <span class="nf">test_getEvent_withAccess_returnsEvent</span><span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="p">{</span>
   <span class="k">let</span> <span class="nv">fakeEventStore</span> <span class="o">=</span> <span class="kt">FakeEKEventStore</span><span class="p">()</span>
   <span class="k">let</span> <span class="nv">event</span> <span class="o">=</span> <span class="kt">EKEvent</span><span class="p">(</span><span class="nv">eventStore</span><span class="p">:</span> <span class="n">fakeEventStore</span><span class="p">)</span>
   <span class="n">fakeEventStore</span><span class="o">.</span><span class="n">eventResult</span> <span class="o">=</span> <span class="n">event</span>
   <span class="n">sut</span> <span class="o">=</span> <span class="kt">EKManager</span><span class="o">.</span><span class="n">shared</span>
   <span class="n">sut</span><span class="o">.</span><span class="n">eventStore</span> <span class="o">=</span> <span class="n">fakeEventStore</span>
   <span class="n">sut</span><span class="o">.</span><span class="nf">set</span><span class="p">(</span><span class="nv">hasCalendarAccess</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
   <span class="k">let</span> <span class="nv">eventToTest</span> <span class="o">=</span> <span class="k">try</span> <span class="k">await</span> <span class="n">sut</span><span class="o">.</span><span class="nf">getEvent</span><span class="p">(</span><span class="nv">id</span><span class="p">:</span> <span class="s">"ignored"</span><span class="p">)</span>
   <span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">eventToTest</span><span class="p">,</span> <span class="n">event</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<p>The happy path is similarly straight forward. We verify that the faked response is being propagated up.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">test_getEvent_withNoResult_throwsError</span><span class="p">()</span> <span class="k">async</span> <span class="p">{</span>
   <span class="k">let</span> <span class="nv">fakeEventStore</span> <span class="o">=</span> <span class="kt">FakeEKEventStore</span><span class="p">()</span>
   <span class="n">sut</span> <span class="o">=</span> <span class="kt">EKManager</span><span class="o">.</span><span class="n">shared</span>
   <span class="n">sut</span><span class="o">.</span><span class="n">eventStore</span> <span class="o">=</span> <span class="n">fakeEventStore</span>
   <span class="n">sut</span><span class="o">.</span><span class="nf">set</span><span class="p">(</span><span class="nv">hasCalendarAccess</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
   <span class="k">do</span> <span class="p">{</span>
      <span class="n">_</span> <span class="o">=</span> <span class="k">try</span> <span class="k">await</span> <span class="n">sut</span><span class="o">.</span><span class="nf">getEvent</span><span class="p">(</span><span class="nv">id</span><span class="p">:</span> <span class="s">"ignored"</span><span class="p">)</span>
      <span class="kt">XCTFail</span><span class="p">(</span><span class="s">"Should not succeed"</span><span class="p">)</span>
   <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
      <span class="k">if</span> <span class="k">let</span> <span class="nv">ekError</span> <span class="o">=</span> <span class="n">error</span> <span class="k">as?</span> <span class="kt">EKManager</span><span class="o">.</span><span class="kt">EKManagerError</span><span class="p">,</span>
         <span class="k">case</span> <span class="o">.</span><span class="n">eventNotFound</span> <span class="o">=</span> <span class="n">ekError</span> <span class="p">{</span>
         <span class="k">return</span>
      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
         <span class="kt">XCTFail</span><span class="p">(</span><span class="s">"Unknown error: </span><span class="se">\(</span><span class="n">error</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
      <span class="p">}</span>
   <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>Our function to retrieve an event by identifier has one other bit of business logic. If there’s no returned event, we throw a specific error.</p>

<h3 id="gotchas-2">Gotchas</h3>

<p>With <em>Test Driven Development</em>, you write the tests, before you develop the code. (An oversimplification, but bear with me.) This could lead to tests that will not pass. Or, perhaps, there’s logic that needs to be verified, but your testing infrastructure doesn’t support it yet. Or, maybe, you might have a flakey test due to timing problems in your code or cascading dependencies that are causing you issues.</p>

<p>There may be times where you may need to skip a test until you can resolve the issue. Apple makes that very easy:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
</pre></td><td class="code"><pre><span class="kd">func</span> <span class="nf">test_getEvents_whenThrowingError_throwsError</span><span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="p">{</span>
   <span class="k">throw</span> <span class="kt">XCTSkip</span><span class="p">(</span><span class="s">"Unfortunately, though it should throw an error, "</span> <span class="o">+</span>
                 <span class="s">"faking EventStore doesn't allow us to overload "</span> <span class="o">+</span>
                 <span class="s">"this method."</span><span class="p">)</span>

   <span class="k">let</span> <span class="nv">fakeEventStore</span> <span class="o">=</span> <span class="kt">FakeEKEventStore</span><span class="p">()</span>
   <span class="n">fakeEventStore</span><span class="o">.</span><span class="n">errorToThrow</span> <span class="o">=</span> <span class="kt">TestError</span><span class="o">.</span><span class="n">error</span>
   <span class="n">sut</span> <span class="o">=</span> <span class="kt">EKManager</span><span class="o">.</span><span class="n">shared</span>
   <span class="n">sut</span><span class="o">.</span><span class="n">eventStore</span> <span class="o">=</span> <span class="n">fakeEventStore</span>
   <span class="n">sut</span><span class="o">.</span><span class="nf">set</span><span class="p">(</span><span class="nv">hasCalendarAccess</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
   
   <span class="k">do</span> <span class="p">{</span>
      <span class="n">_</span> <span class="o">=</span> <span class="k">try</span> <span class="k">await</span> <span class="n">sut</span><span class="o">.</span><span class="nf">getEvents</span><span class="p">()</span>
      <span class="kt">XCTFail</span><span class="p">(</span><span class="s">"Should not succeed"</span><span class="p">)</span>
   <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
      <span class="nf">print</span><span class="p">(</span><span class="s">"*Jp* </span><span class="se">\(</span><span class="k">self</span><span class="se">)</span><span class="s">::</span><span class="se">\(</span><span class="kd">#function</span><span class="se">)</span><span class="s">[</span><span class="se">\(</span><span class="kd">#line</span><span class="se">)</span><span class="s">] &lt;</span><span class="se">\(</span><span class="n">error</span><span class="se">)</span><span class="s">&gt;"</span><span class="p">)</span>
      <span class="k">if</span> <span class="k">let</span> <span class="nv">ekError</span> <span class="o">=</span> <span class="n">error</span> <span class="k">as?</span> <span class="kt">EKManager</span><span class="o">.</span><span class="kt">EKManagerError</span><span class="p">,</span>
         <span class="k">case</span> <span class="o">.</span><span class="n">calendarAccessDenied</span> <span class="o">=</span> <span class="n">ekError</span> <span class="p">{</span>
         <span class="k">return</span>
      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
         <span class="kt">XCTFail</span><span class="p">(</span><span class="s">"Unknown error: </span><span class="se">\(</span><span class="n">error</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
      <span class="p">}</span>
   <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>Line <code class="language-plaintext highlighter-rouge">#2</code> shows how to skip one of a test. It’s usually good practice to fill in the message, often with a ticket number where you’ll be fixing the issue or the reason why you’re skipping your test.</p>

<hr />

<p>Next week, one last infrastructure model before we can get to the UI layer.</p>]]></content><author><name>Jp</name></author><category term="coding" /><category term="taskmanager" /><category term="eventkit" /><category term="testing" /><summary type="html"><![CDATA[Unit Testing a manager that wraps third-party functionality can be challenging. You don’t want to waste your time and energy testing something that one assumes that the vendor has already tested and supports. If they’ve given you an API, you need to trust that API is accurate. What you want to test is your business logic and your code. There are two ways to do that. One is to leverage a pseudo object that conforms to the API but you can control the outputs. Ie; if you should be thrown an error, you can trigger that; or if you should be given a response, you can control what the response is. This lets your tests focus on your business logic, not someone else’s. All of the code for this blog post is in this sample code repo.]]></summary></entry><entry><title type="html">Event Kit Manager</title><link href="https://jp4mobile.com/coding/2024/11/10/EventKitManager.html" rel="alternate" type="text/html" title="Event Kit Manager" /><published>2024-11-10T14:00:01+00:00</published><updated>2024-11-10T14:00:01+00:00</updated><id>https://jp4mobile.com/coding/2024/11/10/EventKitManager</id><content type="html" xml:base="https://jp4mobile.com/coding/2024/11/10/EventKitManager.html"><![CDATA[<p>Apple’s <code class="language-plaintext highlighter-rouge">EventKit</code> can be a pain to work with. There’s specific logic that you need to make sure that you follow, so your app handles the permissions properly.</p>

<p>The easier way to do this is to wrap the <code class="language-plaintext highlighter-rouge">EventKit</code> functionality in a manager that will manage the requests for permissions and ensure that the actual calls to the services are done properly.</p>

<p>All of the code for this blog post is in this <a href="https://github.com/Jp4Mobile/SampleCode/tree/main/posts/projects/EventKitWrapper-2024-11-10">sample code repo</a>.</p>

<!--more-->

<p>There are some challenges with using using the EventKit. Not the least of it, is that you need to follow Apple’s guidelines to make sure that you request access properly for both events, reminders, and optionally their calendars, so that you can create the appropriate calendars, as well as read and write events and reminders.</p>

<h2 id="requesting-access">Requesting Access</h2>

<p>There are a few facets of this. Working from the inside of your project out:</p>

<ol>
  <li>Add the appropriate privacy descriptions for the calendar and/or reminder access to your app’s <code class="language-plaintext highlighter-rouge">Info.plist</code>.</li>
  <li>Within your <code class="language-plaintext highlighter-rouge">EventKit</code> retrieval logic, make sure that you’re <em>requesting access</em> and if you don’t receive access, <em>communicate that to your user</em> and <em>end your retrieval</em>.</li>
  <li>Once you have access, <em>retrieve</em> the calendar and then with the calendar, you can retrieve <code class="language-plaintext highlighter-rouge">Event</code> or <code class="language-plaintext highlighter-rouge">Reminder</code> objects.</li>
</ol>

<p><img src="/img/InfoPlist-2024-11-10.png" alt="Info.plist" /></p>

<p>For my sample code, I asked for full access, as you can see in both the screenshot or by perusing the sample code.</p>

<p>Apple uses that description as it presents the request for access to the user, as you can see below:
<img src="/img/RequestEventAccess-2024-11-10.png" alt="Request Event/Calendar Access" />
<img src="/img/RequestReminderAccess-2024-11-10.png" alt="Request Reminder/Calendar Access" /></p>

<p>Now, let’s walk through the logic of my <code class="language-plaintext highlighter-rouge">EKManager</code> which will wrap the <code class="language-plaintext highlighter-rouge">EventKit</code>.</p>

<h3 id="access-wrappers">Access Wrappers</h3>

<p>The access wrappers are simple to write:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">requestCalendarAccess</span><span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="p">{</span>
  <span class="k">do</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">access</span> <span class="o">=</span> <span class="k">try</span> <span class="k">await</span> <span class="n">eventStore</span><span class="o">.</span><span class="nf">requestFullAccessToEvents</span><span class="p">()</span>
    <span class="n">hasCalendarAccess</span> <span class="o">=</span> <span class="n">access</span>
  <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
    <span class="n">hasCalendarAccess</span> <span class="o">=</span> <span class="kc">false</span>
    <span class="k">throw</span> <span class="n">error</span>
  <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>We use the event store and then through that, we request access for either event/calendar access or reminder access. I illustrated the event/calendar access, but reminder access is equally straightforward.</p>

<h3 id="access-verification-logic">Access Verification Logic</h3>

<p>Within the places that need to access your events or reminders, the logic also follows, though it may be repetitive in the various places you use it.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">if</span> <span class="o">!</span><span class="n">hasCalendarAccess</span> <span class="p">{</span>
  <span class="k">try</span> <span class="k">await</span> <span class="nf">requestCalendarAccess</span><span class="p">()</span>
<span class="p">}</span>
<span class="k">guard</span> <span class="n">hasCalendarAccess</span> <span class="k">else</span> <span class="p">{</span>
  <span class="k">throw</span> <span class="kt">EKManagerError</span><span class="o">.</span><span class="n">calendarAccessDenied</span>
<span class="p">}</span>
<span class="c1">// At this point you can continue to your business logic.</span></code></pre></figure>

<h2 id="retrieving-data">Retrieving Data</h2>

<p>Before you can retrieve calendar events or reminders, you’ll need to retrieve the appropriate calendar for those <code class="language-plaintext highlighter-rouge">EventKit</code> entities.</p>

<h3 id="retrieving-calendars">Retrieving Calendars</h3>

<p>Once again, Apple makes that easy for developers by having an API that is easy to understand. We can retrieve all event or reminder calendars with the <code class="language-plaintext highlighter-rouge">eventStore.calendars(for: .event)</code> or <code class="language-plaintext highlighter-rouge">eventStore.calendars(for: .reminder)</code>, then the developer can pick the one that they want.</p>

<p>Or if the user would like to just go with whatever calendar that the user already prefers to use, you can just grab the defaults using:
<code class="language-plaintext highlighter-rouge">eventStore.defaultCalendarForNewEvents</code> or <code class="language-plaintext highlighter-rouge">eventStore.defaultCalendarForNewRemindres</code>.</p>

<h3 id="creating-calendars">Creating Calendars</h3>

<p>Or you could create your own calendars. Myself, I prefer to create my own calendar, so that any events will be branded in a <code class="language-plaintext highlighter-rouge">"TaskManager"</code> calendar for events and reminders, which will make it easier for myself in terms of testing or with an eye towards removing items in mass, if a user requests it.</p>

<h4 id="gotchas">Gotchas</h4>

<p>A newly created calendar will need at least a title and a source.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">calendar</span> <span class="o">=</span> <span class="kt">EKCalendar</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="n">entityType</span><span class="p">,</span> <span class="nv">eventStore</span><span class="p">:</span> <span class="n">eventStore</span><span class="p">)</span>
<span class="n">calendar</span><span class="o">.</span><span class="n">title</span> <span class="o">=</span> <span class="s">"TaskManager"</span>
<span class="n">calendar</span><span class="o">.</span><span class="n">source</span> <span class="o">=</span> <span class="k">try</span> <span class="k">await</span> <span class="nf">getEventSource</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="n">entityType</span><span class="p">)</span>
<span class="k">try</span> <span class="n">eventStore</span><span class="o">.</span><span class="nf">saveCalendar</span><span class="p">(</span><span class="n">calendar</span><span class="p">,</span> <span class="nv">commit</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span></code></pre></figure>

<h3 id="finding-the-source">Finding the Source</h3>

<p>An <code class="language-plaintext highlighter-rouge">EKSourceType</code> can represent a variety of sources. Usually, it’s a good idea to leverage what the user is already using, if possible.</p>

<p>The logic is basically, use the same source as the default calendar for that entity, if possible. Otherwise, try to see if iCloud is possible for the user. If that doesn’t work, try to see if local access is available.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">getEventSource</span><span class="p">(</span><span class="k">for</span> <span class="nv">entityType</span><span class="p">:</span> <span class="kt">EKEntityType</span><span class="p">)</span> <span class="k">async</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">EKSource</span> <span class="p">{</span>
   <span class="kd">let</span> <span class="p">`</span><span class="nv">default</span><span class="p">`</span> <span class="o">=</span> <span class="k">try</span> <span class="k">await</span> <span class="nf">getDefaultCalendar</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="n">entityType</span><span class="p">)</span><span class="o">.</span><span class="n">source</span>
   <span class="k">let</span> <span class="nv">isICloudPresent</span><span class="p">:</span> <span class="p">(</span><span class="kt">EKSource</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nv">$0</span><span class="o">.</span><span class="n">title</span><span class="o">.</span><span class="nf">lowercased</span><span class="p">()</span><span class="o">.</span><span class="nf">contains</span><span class="p">(</span><span class="s">"icloud"</span><span class="p">)</span>
   <span class="p">}</span>
   <span class="k">let</span> <span class="nv">iCloud</span> <span class="o">=</span> <span class="n">eventStore</span><span class="o">.</span><span class="n">sources</span><span class="o">.</span><span class="nf">first</span><span class="p">(</span><span class="nv">where</span><span class="p">:</span> <span class="n">isICloudPresent</span><span class="p">)</span>
   <span class="k">let</span> <span class="nv">local</span> <span class="o">=</span> <span class="n">eventStore</span><span class="o">.</span><span class="n">sources</span><span class="o">.</span><span class="nf">first</span><span class="p">(</span><span class="nv">where</span><span class="p">:</span> <span class="p">{</span> <span class="nv">$0</span><span class="o">.</span><span class="n">sourceType</span> <span class="o">==</span> <span class="o">.</span><span class="n">local</span> <span class="p">})</span>

   <span class="k">guard</span> <span class="k">let</span> <span class="nv">source</span> <span class="o">=</span> <span class="p">`</span><span class="nv">default</span><span class="p">`</span> <span class="p">??</span> <span class="n">iCloud</span> <span class="p">??</span> <span class="n">local</span> <span class="k">else</span> <span class="p">{</span>
      <span class="k">throw</span> <span class="kt">EKManagerError</span><span class="o">.</span><span class="n">noSourceFound</span>
   <span class="p">}</span>

   <span class="k">return</span> <span class="n">source</span>
<span class="p">}</span></code></pre></figure>

<h3 id="retrieving-events-or-reminders">Retrieving Events or Reminders</h3>

<p>There are two ways to retrieve event or reminder items. Either those that match a specific predicate or by trying to retrieve a specific event or reminder item by their unique identifier.</p>

<p>In my initial code, I went with the more generic to retrieve all events and reminders for the TaskManager calendar that I created above.</p>

<p>The <code class="language-plaintext highlighter-rouge">getEvents()</code> function creates a predicate to search for calendar events within a known calendar for the next month. Start and end dates can be reconfigured to work best with individual requirements or for any calendar that the user has access to.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">getEvents</span><span class="p">(</span><span class="n">from</span> <span class="nv">startDate</span><span class="p">:</span> <span class="kt">Date</span> <span class="o">=</span> <span class="kt">Date</span><span class="p">())</span> <span class="k">async</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="p">[</span><span class="kt">EKEvent</span><span class="p">]</span> <span class="p">{</span>
   <span class="k">let</span> <span class="nv">calendar</span> <span class="o">=</span> <span class="k">try</span> <span class="k">await</span> <span class="nf">getCalendarToUse</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="o">.</span><span class="n">event</span><span class="p">)</span>

   <span class="k">let</span> <span class="nv">endDate</span> <span class="o">=</span> <span class="kt">Calendar</span><span class="o">.</span><span class="n">current</span><span class="o">.</span><span class="nf">date</span><span class="p">(</span><span class="nv">byAdding</span><span class="p">:</span> <span class="o">.</span><span class="n">month</span><span class="p">,</span>
                                       <span class="nv">value</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
                                       <span class="nv">to</span><span class="p">:</span> <span class="n">startDate</span><span class="p">)</span> <span class="p">??</span> <span class="n">startDate</span>
   <span class="k">let</span> <span class="nv">predicate</span> <span class="o">=</span> 
       <span class="n">eventStore</span><span class="o">.</span><span class="nf">predicateForEvents</span><span class="p">(</span><span class="nv">withStart</span><span class="p">:</span> <span class="n">startDate</span><span class="p">,</span>
                                     <span class="nv">end</span><span class="p">:</span> <span class="n">endDate</span><span class="p">,</span>
                                     <span class="nv">calendars</span><span class="p">:</span> <span class="p">[</span><span class="n">calendar</span><span class="p">])</span>
	
   <span class="k">let</span> <span class="nv">allEvents</span> <span class="o">=</span> <span class="n">eventStore</span><span class="o">.</span><span class="nf">events</span><span class="p">(</span><span class="nv">matching</span><span class="p">:</span> <span class="n">predicate</span><span class="p">)</span>

   <span class="k">return</span> <span class="n">allEvents</span>
<span class="p">}</span></code></pre></figure>

<p>The <code class="language-plaintext highlighter-rouge">getReminders()</code> function is a bit easier, as reminders are not required to have dates, so the predicate only checks against a specific reminder calendar.</p>

<h4 id="gotchas-1">Gotchas</h4>

<p>Most of the <code class="language-plaintext highlighter-rouge">EventKit</code> functionality has shifted from completion handlers to the more current async/await model. Unfortunately, <code class="language-plaintext highlighter-rouge">fetchReminders(matching:, completion:)</code> is not one of them.</p>

<p>However Apple has an easy to use way to convert completion handlers to async calls leveraging the <code class="language-plaintext highlighter-rouge">CheckedContinuation</code> interface.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">getReminders</span><span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="p">[</span><span class="kt">EKReminder</span><span class="p">]</span> <span class="p">{</span>
   <span class="k">let</span> <span class="nv">calendar</span> <span class="o">=</span> <span class="k">try</span> <span class="k">await</span> <span class="nf">getCalendarToUse</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="o">.</span><span class="n">reminder</span><span class="p">)</span>

   <span class="k">let</span> <span class="nv">predicate</span> <span class="o">=</span> <span class="n">eventStore</span><span class="o">.</span><span class="nf">predicateForReminders</span><span class="p">(</span><span class="nv">in</span><span class="p">:</span> <span class="p">[</span><span class="n">calendar</span><span class="p">])</span>

   <span class="k">return</span> <span class="k">await</span> <span class="n">withCheckedContinuation</span> <span class="p">{</span> <span class="n">continuation</span> <span class="k">in</span>

      <span class="n">eventStore</span><span class="o">.</span><span class="nf">fetchReminders</span><span class="p">(</span><span class="nv">matching</span><span class="p">:</span> <span class="n">predicate</span><span class="p">)</span> <span class="p">{</span> <span class="n">reminders</span> <span class="k">in</span>
         <span class="n">continuation</span><span class="o">.</span><span class="nf">resume</span><span class="p">(</span><span class="nv">returning</span><span class="p">:</span> <span class="n">reminders</span> <span class="p">??</span> <span class="p">[])</span>
      <span class="p">}</span>
   <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>By using <code class="language-plaintext highlighter-rouge">withCheckedContinuation</code>, the completion handler for the call you’re making can be converted to an <code class="language-plaintext highlighter-rouge">async</code> call that can return a value, throw an error, or just run asynchronously.</p>

<h4 id="optimizing-by-retrieving-individual-items">Optimizing by Retrieving Individual Items</h4>

<p>This certainly works, but retrieving all of them to then search the full list is going to cause problems at scale.</p>

<p>It would be better to retrieve events and items by an ID number, so that after the first time it’s been retrieved, it can be manipulated by an individual item.</p>

<p>There are simple retrieval methods for that:</p>

<p>You can use <code class="language-plaintext highlighter-rouge">eventStore.event(withIdentifier: id)</code> for calendar events.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">guard</span> <span class="k">let</span> <span class="nv">calendarItem</span> <span class="o">=</span> <span class="n">eventStore</span><span class="o">.</span><span class="nf">calendarItem</span><span class="p">(</span><span class="nv">withIdentifier</span><span class="p">:</span> <span class="n">id</span><span class="p">),</span>
      <span class="k">let</span> <span class="nv">reminder</span> <span class="o">=</span> <span class="n">calendarItem</span> <span class="k">as?</span> <span class="kt">EKReminder</span> <span class="k">else</span> <span class="p">{</span><span class="o">...</span><span class="p">}</span></code></pre></figure>

<h3 id="removing-events-or-reminders">Removing Events or Reminders</h3>

<p>The <code class="language-plaintext highlighter-rouge">eventStore.remove(_ event:, span:, commit:)</code> and <code class="language-plaintext highlighter-rouge">eventStore.remove(_ reminder:, commit:)</code> functions take the <code class="language-plaintext highlighter-rouge">EKEvent</code> and <code class="language-plaintext highlighter-rouge">EKReminder</code> models and remove them.</p>

<p>The <code class="language-plaintext highlighter-rouge">commit:</code> parameter with both functions indicates whether the removal happens now or later (in the case of a batched removal) when a subsequent commit is called.</p>

<p>The remove function wrapper and some proposed usages could help explain this a bit better:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">remove</span><span class="p">(</span><span class="nv">event</span><span class="p">:</span> <span class="kt">EKEvent</span><span class="p">,</span> <span class="nv">shouldBatch</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">false</span><span class="p">)</span> <span class="k">async</span> <span class="k">throws</span> <span class="p">{</span>
	<span class="k">if</span> <span class="o">!</span><span class="n">hasCalendarAccess</span> <span class="p">{</span>
		<span class="k">try</span> <span class="k">await</span> <span class="nf">requestCalendarAccess</span><span class="p">()</span>
	<span class="p">}</span>
	<span class="k">guard</span> <span class="n">hasCalendarAccess</span> <span class="k">else</span> <span class="p">{</span>
		<span class="k">throw</span> <span class="kt">EKManagerError</span><span class="o">.</span><span class="n">calendarAccessDenied</span>
	<span class="p">}</span>

	<span class="c1">// If we should batch, we shouldn't commit.</span>
	<span class="k">try</span> <span class="n">eventStore</span><span class="o">.</span><span class="nf">remove</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> 
	                      <span class="nv">span</span><span class="p">:</span><span class="o">.</span><span class="n">thisEvent</span><span class="p">,</span>
                              <span class="nv">commit</span><span class="p">:</span> <span class="o">!</span><span class="n">shouldBatch</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<p>This could be used in this manner:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">titledEvents</span> <span class="o">=</span> <span class="k">try</span> <span class="k">await</span> <span class="nf">getEvents</span><span class="p">()</span>
   <span class="o">.</span><span class="n">filter</span> <span class="p">{</span> <span class="nv">$0</span><span class="o">.</span><span class="n">title</span> <span class="o">==</span> <span class="n">specificTitle</span> <span class="p">}</span>

<span class="c1">// Delete one</span>
<span class="k">if</span> <span class="k">let</span> <span class="nv">specificEvent</span> <span class="o">=</span> <span class="n">titledEvents</span><span class="o">.</span><span class="n">first</span> <span class="p">{</span>
  <span class="c1">// Removes and commits the removal</span>
  <span class="k">try</span> <span class="n">ekManager</span><span class="o">.</span><span class="nf">remove</span><span class="p">(</span><span class="n">specificEvent</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// Delete many</span>
<span class="k">for</span> <span class="n">titledEvent</span> <span class="k">in</span> <span class="n">titledEvents</span> <span class="p">{</span>
  <span class="c1">// Don't commit these changes until ALL are successful removed.</span>
  <span class="k">try</span> <span class="k">await</span> <span class="n">ekManager</span><span class="o">.</span><span class="nf">remove</span><span class="p">(</span><span class="n">titledEvent</span><span class="p">,</span> <span class="nv">shouldBatch</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
<span class="p">}</span>
<span class="c1">// After all the events were removed, we commit.</span>
<span class="k">try</span> <span class="n">ekManager</span><span class="o">.</span><span class="n">eventStore</span><span class="o">.</span><span class="nf">commit</span><span class="p">()</span></code></pre></figure>

<h3 id="create-event-or-reminder">Create Event or Reminder</h3>

<p>Similar to creating a calendar, creating a calendar event or a reminder uses the same logic after making sure they have the appropriate access for calendars and reminders:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">event</span> <span class="o">=</span> <span class="kt">EKEvent</span><span class="p">(</span><span class="nv">eventStore</span><span class="p">:</span> <span class="n">eventStore</span><span class="p">)</span>
<span class="c1">// set the values: title, startDate, optional endDate, optional notes, and the calendar to create this event into.</span>
<span class="k">try</span> <span class="n">eventStore</span><span class="o">.</span><span class="nf">save</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="nv">span</span><span class="p">:</span> <span class="o">.</span><span class="n">thisEvent</span><span class="p">,</span> <span class="nv">commit</span><span class="p">:</span> <span class="n">shouldCommit</span><span class="p">)</span></code></pre></figure>

<p>And</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">reminder</span> <span class="o">=</span> <span class="kt">EKReminder</span><span class="p">(</span><span class="nv">eventStore</span><span class="p">:</span> <span class="n">eventStore</span><span class="p">)</span>
<span class="c1">// set the values: title, optional dueDateComponents (which may either be a date or a date with time), notes, and the calendar to create this reminder into.</span>
<span class="k">try</span> <span class="n">eventStore</span><span class="o">.</span><span class="nf">save</span><span class="p">(</span><span class="n">reminder</span><span class="p">,</span> <span class="nv">commit</span><span class="p">:</span> <span class="n">shouldCommit</span><span class="p">)</span></code></pre></figure>

<h3 id="updating-events-or-reminders">Updating Events or Reminders</h3>

<p>Now that we can retrieve a known event or reminder from, updating it’s fairly straightforward:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">update</span><span class="p">(</span><span class="n">_</span> <span class="nv">event</span><span class="p">:</span> <span class="kt">EKEvent</span><span class="p">,</span> <span class="nv">shouldBatch</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">false</span><span class="p">)</span> <span class="k">async</span> <span class="k">throws</span> <span class="p">{</span>
	<span class="k">try</span> <span class="n">eventStore</span><span class="o">.</span><span class="nf">save</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="nv">span</span><span class="p">:</span> <span class="o">.</span><span class="n">thisEvent</span><span class="p">)</span>

	<span class="k">guard</span> <span class="o">!</span><span class="n">shouldBatch</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>

	<span class="k">try</span> <span class="n">eventStore</span><span class="o">.</span><span class="nf">commit</span><span class="p">()</span>
<span class="p">}</span>

<span class="kd">func</span> <span class="nf">update</span><span class="p">(</span><span class="n">_</span> <span class="nv">reminder</span><span class="p">:</span> <span class="kt">EKReminder</span><span class="p">,</span> <span class="nv">shouldBatch</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">false</span><span class="p">)</span> <span class="k">async</span> <span class="k">throws</span> <span class="p">{</span>
	<span class="k">try</span> <span class="n">eventStore</span><span class="o">.</span><span class="nf">save</span><span class="p">(</span><span class="n">reminder</span><span class="p">,</span> <span class="nv">commit</span><span class="p">:</span> <span class="o">!</span><span class="n">shouldBatch</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<h3 id="manual-testing">Manual Testing</h3>

<p>For this week, the only testing that was done was manual testing. Inside the sample code’s <code class="language-plaintext highlighter-rouge">ContentView</code>, you may see the <code class="language-plaintext highlighter-rouge">EKManager</code> being called directly to create reminders and events (as well as the requesting of permissions, creation of calendar, etc… that goes on behind the scenes for that to happen).</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">try</span> <span class="k">await</span> <span class="kt">EKManager</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">create</span><span class="p">(</span><span class="o">.</span><span class="n">reminder</span><span class="p">,</span> <span class="nv">model</span><span class="p">:</span> <span class="n">testData</span><span class="o">.</span><span class="n">reminder</span><span class="p">)</span>
<span class="k">try</span> <span class="k">await</span> <span class="kt">EKManager</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">create</span><span class="p">(</span><span class="o">.</span><span class="n">event</span><span class="p">,</span> <span class="nv">model</span><span class="p">:</span> <span class="n">testData</span><span class="o">.</span><span class="n">event</span><span class="p">)</span></code></pre></figure>

<hr />

<p>Next stop, testing the <code class="language-plaintext highlighter-rouge">EventKit</code> manager without needing to test Apple’s <code class="language-plaintext highlighter-rouge">EventKit</code> itself by using APIs to test our logic.</p>]]></content><author><name>Jp</name></author><category term="coding" /><category term="taskmanager" /><category term="eventkit" /><summary type="html"><![CDATA[Apple’s EventKit can be a pain to work with. There’s specific logic that you need to make sure that you follow, so your app handles the permissions properly. The easier way to do this is to wrap the EventKit functionality in a manager that will manage the requests for permissions and ensure that the actual calls to the services are done properly. All of the code for this blog post is in this sample code repo.]]></summary></entry><entry><title type="html">Models and Extensions</title><link href="https://jp4mobile.com/coding/2024/11/03/Models_and_Extensions.html" rel="alternate" type="text/html" title="Models and Extensions" /><published>2024-11-03T14:00:01+00:00</published><updated>2024-11-03T14:00:01+00:00</updated><id>https://jp4mobile.com/coding/2024/11/03/Models_and_Extensions</id><content type="html" xml:base="https://jp4mobile.com/coding/2024/11/03/Models_and_Extensions.html"><![CDATA[<p>This is where we start getting into the nuts and bolts of the infrastructure for the project by building models, extensions to make the use of these models easier as I put together date utilities to make my life easier.</p>

<p>Luckily, <code class="language-plaintext highlighter-rouge">TaskManager</code> makes things a little easier in terms of dates. We only have two major date formats to convert to and from a <code class="language-plaintext highlighter-rouge">Date</code> foundation class: <code class="language-plaintext highlighter-rouge">"yyyy-MM-dd"</code> and <code class="language-plaintext highlighter-rouge">"yyyy-MM-dd HH:mm"</code> (ie; <code class="language-plaintext highlighter-rouge">"2024-10-31"</code> and <code class="language-plaintext highlighter-rouge">"2024-10-31 10:31"</code>).</p>

<p>All of the code for this blog post is in this <a href="https://github.com/Jp4Mobile/SampleCode/tree/main/posts/projects/Infrastructure-2024-11-03">sample code repo</a>.</p>

<!--more-->

<p>We want to make this as simple for users of these functions as possible, so the API we’d like is this:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">halloween</span> <span class="o">=</span> <span class="kt">Date</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="n">date</span><span class="p">,</span> <span class="s">"2024-10-31"</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">rembembranceDay</span> <span class="o">=</span> <span class="kt">Date</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="n">dateTime</span><span class="p">,</span> <span class="s">"2024-11-11 11:00"</span><span class="p">)</span></code></pre></figure>

<p>And the converse:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">apptDay</span> <span class="o">=</span> <span class="kt">Date</span><span class="p">()</span><span class="o">.</span><span class="nf">string</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="n">date</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">apptDayTime</span> <span class="o">=</span> <span class="kt">Date</span><span class="p">()</span><span class="o">.</span><span class="nf">string</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="n">dateTime</span><span class="p">)</span></code></pre></figure>

<h2 id="gotchas">Gotchas</h2>

<p>An optimistic idea of how to handle this might be to use convert via <code class="language-plaintext highlighter-rouge">DateComponents</code>. We can split the formatted strings into integer components and then convert to a date.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Optimistic, but problematic conversion:</span>
<span class="k">let</span> <span class="nv">date</span> <span class="o">=</span> <span class="kt">Calendar</span><span class="o">.</span><span class="n">current</span><span class="o">.</span><span class="nf">date</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="kt">DateComponents</span><span class="p">(</span><span class="nv">year</span><span class="p">:</span> <span class="mi">2024</span><span class="p">,</span> <span class="nv">month</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span> <span class="nv">day</span><span class="p">:</span> <span class="mi">31</span><span class="p">)</span>
<span class="c1">// This will result in a date for "2024-10-31 00:00"</span></code></pre></figure>

<p>Looks good, right?</p>

<p>Any programmer that has worked with dates will tell you about the perils of dealing with <code class="language-plaintext highlighter-rouge">Date</code> conversions. <em>Leap Day</em> and the <em>Daylight Savings Time</em> are edge cases that need to be dealt with.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Illustrating the problem.</span>
<span class="k">let</span> <span class="nv">leapDay2024</span> <span class="o">=</span> <span class="kt">Calendar</span><span class="o">.</span><span class="n">current</span><span class="o">.</span><span class="nf">date</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="kt">DateComponents</span><span class="p">(</span><span class="nv">year</span><span class="p">:</span> <span class="mi">2024</span><span class="p">,</span> <span class="nv">month</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="nv">day</span><span class="p">:</span> <span class="mi">29</span><span class="p">)</span>
<span class="c1">// This will result in a date for "2024-02-29 00:00"</span>
<span class="k">let</span> <span class="nv">leapDay2023</span> <span class="o">=</span> <span class="kt">Calendar</span><span class="o">.</span><span class="n">current</span><span class="o">.</span><span class="nf">date</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="kt">DateComponents</span><span class="p">(</span><span class="nv">year</span><span class="p">:</span> <span class="mi">2023</span><span class="p">,</span> <span class="nv">month</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="nv">day</span><span class="p">:</span> <span class="mi">29</span><span class="p">)</span>
<span class="c1">// This will result in a date for "2023-03-01 00:00", as there was no leap day in 2023.</span></code></pre></figure>

<p>Apple’s <code class="language-plaintext highlighter-rouge">DateFormatter</code> handles both the string parsing and the validation.</p>

<h2 id="date-extension">Date Extension</h2>

<p><a href="https://github.com/Jp4Mobile/SampleCode/tree/main/posts/projects/Infrastructure-2024-11-03/TaskManager/TaskManager/Utilities/Extensions/DateExtensions.swift">Sample code</a></p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">extension</span> <span class="kt">Date</span> <span class="p">{</span>
    <span class="c1">// MARK: - TMDateFormat</span>
    <span class="c1">/// Standardized TM Date Formats</span>
    <span class="kd">enum</span> <span class="kt">TMDateFormat</span> <span class="p">{</span>
        <span class="c1">/// Date only</span>
        <span class="k">case</span> <span class="n">date</span>
        <span class="c1">/// Date Time</span>
        <span class="k">case</span> <span class="n">dateTime</span>

        <span class="c1">/// Input/Output format string</span>
        <span class="k">var</span> <span class="nv">string</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span>
            <span class="k">switch</span> <span class="k">self</span> <span class="p">{</span>
            <span class="k">case</span> <span class="o">.</span><span class="nv">date</span><span class="p">:</span>
                <span class="k">return</span> <span class="kt">Constants</span><span class="o">.</span><span class="kt">Date</span><span class="o">.</span><span class="kt">YMD</span>
            <span class="k">case</span> <span class="o">.</span><span class="nv">dateTime</span><span class="p">:</span>
                <span class="k">return</span> <span class="kt">Constants</span><span class="o">.</span><span class="kt">Date</span><span class="o">.</span><span class="kt">YMDHM</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// MARK: - Properties</span>
    <span class="kd">private</span> <span class="k">var</span> <span class="nv">dateFormatter</span><span class="p">:</span> <span class="kt">DateFormatter</span> <span class="p">{</span>
        <span class="kt">DateFormatter</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="c1">// MARK: - Standardized Input/Output Transformations</span>
    <span class="c1">// MARK: Output strings</span>
    <span class="kd">func</span> <span class="nf">string</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="kt">TMDateFormat</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">String</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">dateFormatter</span> <span class="o">=</span> <span class="kt">DateFormatter</span><span class="p">()</span>
        <span class="n">dateFormatter</span><span class="o">.</span><span class="n">dateFormat</span> <span class="o">=</span> <span class="n">format</span><span class="o">.</span><span class="n">string</span>

        <span class="k">return</span> <span class="n">dateFormatter</span><span class="o">.</span><span class="nf">string</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="k">self</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="c1">// MARK: Initializers</span>
    <span class="nf">init</span><span class="p">?(</span><span class="nv">format</span><span class="p">:</span> <span class="kt">TMDateFormat</span><span class="p">,</span> <span class="n">_</span> <span class="nv">input</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">dateFormatter</span> <span class="o">=</span> <span class="kt">DateFormatter</span><span class="p">()</span>
        <span class="n">dateFormatter</span><span class="o">.</span><span class="n">dateFormat</span> <span class="o">=</span> <span class="n">format</span><span class="o">.</span><span class="n">string</span>
        
        <span class="k">guard</span> <span class="k">let</span> <span class="nv">date</span> <span class="o">=</span> <span class="n">dateFormatter</span><span class="o">.</span><span class="nf">date</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="n">input</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="k">self</span> <span class="o">=</span> <span class="n">date</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>By setting up the internal enum in a <code class="language-plaintext highlighter-rouge">Date</code> extension, we are able to leverage this model to keep track of our known format strings and ensure that using the <code class="language-plaintext highlighter-rouge">TMDateFormat</code> in the initializer and string conversion functions for a nice and clean API usage.</p>

<h2 id="reminder-needs">Reminder Needs</h2>

<p>With an eye on what we want to do next, we know that we’ll need to convert dates into appropriate <code class="language-plaintext highlighter-rouge">Set&lt;Calendar.Components&gt;</code> for creating reminder entities within <code class="language-plaintext highlighter-rouge">EventKit</code> (this is how it will handle the difference between a day of reminder and a day with time reminder) similar to our <code class="language-plaintext highlighter-rouge">.date</code> and <code class="language-plaintext highlighter-rouge">.dateTime</code> formats.</p>

<p>Adding the <code class="language-plaintext highlighter-rouge">components</code> property to the <code class="language-plaintext highlighter-rouge">TMDateFormat</code> ensures the API will continue to be clear and easy to use.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">/// Date Components</span>
<span class="k">var</span> <span class="nv">components</span><span class="p">:</span> <span class="kt">Set</span><span class="o">&lt;</span><span class="kt">Calendar</span><span class="o">.</span><span class="kt">Component</span><span class="o">&gt;</span> <span class="p">{</span>
	<span class="k">switch</span> <span class="k">self</span> <span class="p">{</span>
	<span class="k">case</span> <span class="o">.</span><span class="nv">date</span><span class="p">:</span>
		<span class="k">return</span> <span class="p">[</span><span class="o">.</span><span class="n">year</span><span class="p">,</span> <span class="o">.</span><span class="n">month</span><span class="p">,</span> <span class="o">.</span><span class="n">day</span><span class="p">]</span>
	<span class="k">case</span> <span class="o">.</span><span class="nv">dateTime</span><span class="p">:</span>
		<span class="k">return</span> <span class="p">[</span><span class="o">.</span><span class="n">year</span><span class="p">,</span> <span class="o">.</span><span class="n">month</span><span class="p">,</span> <span class="o">.</span><span class="n">day</span><span class="p">,</span> <span class="o">.</span><span class="n">hour</span><span class="p">,</span> <span class="o">.</span><span class="n">minute</span><span class="p">]</span>
	<span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>Then in out <code class="language-plaintext highlighter-rouge">Date</code> extension, we can add a function to do the conversion:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">private</span> <span class="k">var</span> <span class="nv">currentCalendar</span><span class="p">:</span> <span class="kt">Calendar</span> <span class="p">{</span>
   <span class="kt">Calendar</span><span class="o">.</span><span class="n">current</span>
<span class="p">}</span>

<span class="c1">// MARK: Component Conversions</span>
<span class="kd">func</span> <span class="nf">toDateComponents</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="kt">TMDateFormat</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">DateComponents</span><span class="p">?</span> <span class="p">{</span>
	<span class="n">currentCalendar</span><span class="o">.</span><span class="nf">dateComponents</span><span class="p">(</span><span class="n">format</span><span class="o">.</span><span class="n">components</span><span class="p">,</span> <span class="nv">from</span><span class="p">:</span> <span class="k">self</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<h2 id="other-conversions">Other Conversions</h2>

<p>The universe of <code class="language-plaintext highlighter-rouge">TaskManager</code> date formats is small and while our <code class="language-plaintext highlighter-rouge">.date</code> and <code class="language-plaintext highlighter-rouge">.dateTime</code> formats are the heart of it. There are some other formats:</p>

<ul>
  <li>A date and time and an end time on the same day:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">yyyy-MM-dd HH:mm-HH:mm</code> (ie; 2024-10-31 10:31-12:30)</li>
    </ul>
  </li>
  <li>A date and time with an end date and time:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">yyyy-MM-dd HH:mm</code>thru<code class="language-plaintext highlighter-rouge">yyyy-MM-dd HH:mm</code> (ie; 2024-10-31 10:31thru2024-11-04 12:30)</li>
    </ul>
  </li>
</ul>

<h3 id="tmdatetype-model">TMDateType Model</h3>

<p>These formats can easily be converted into a date type model:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">enum</span> <span class="kt">TMDateType</span> <span class="p">{</span>
   <span class="k">case</span> <span class="nf">date</span><span class="p">(</span><span class="kt">Date</span><span class="p">)</span>
   <span class="k">case</span> <span class="nf">beginEndDate</span><span class="p">(</span><span class="kt">Date</span><span class="p">,</span> <span class="kt">Date</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<p>We can add some dynamic properties to extract out the start and optional end date to make usage easier:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">var</span> <span class="nv">startDate</span><span class="p">:</span> <span class="kt">Date</span> <span class="p">{</span>
  <span class="k">switch</span> <span class="k">self</span> <span class="p">{</span>
    <span class="k">case</span> <span class="o">.</span><span class="nf">date</span><span class="p">(</span><span class="k">let</span> <span class="nv">date</span><span class="p">):</span>
       <span class="k">return</span> <span class="n">date</span>
    <span class="k">case</span> <span class="o">.</span><span class="nf">beginEndDate</span><span class="p">(</span><span class="k">let</span> <span class="nv">start</span><span class="p">,</span> <span class="n">_</span><span class="p">):</span>
       <span class="k">return</span> <span class="n">start</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="k">var</span> <span class="nv">endDate</span><span class="p">:</span> <span class="kt">Date</span><span class="p">?</span> <span class="p">{</span>
  <span class="k">guard</span> <span class="k">case</span> <span class="o">.</span><span class="nf">beginEndDate</span><span class="p">(</span><span class="n">_</span><span class="p">,</span> <span class="k">let</span> <span class="nv">end</span><span class="p">)</span> <span class="o">=</span> <span class="k">self</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="k">return</span> <span class="n">end</span>
<span class="p">}</span></code></pre></figure>

<p>All of the sample code is here: <a href="https://github.com/Jp4Mobile/SampleCode/tree/main/posts/projects/Infrastructure-2024-11-03/TaskManager/TaskManager/Models/DateParameters.swift"><code class="language-plaintext highlighter-rouge">TMDateType</code></a></p>

<h3 id="other-parameter-models">Other Parameter Models</h3>

<p>This is relatively straightforward, if a bit repetitive.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">DateParameters</span> <span class="p">{</span>
  <span class="k">let</span> <span class="nv">year</span><span class="p">:</span> <span class="kt">Int</span>
  <span class="k">let</span> <span class="nv">month</span><span class="p">:</span> <span class="kt">Int</span>
  <span class="k">let</span> <span class="nv">day</span><span class="p">:</span> <span class="kt">Int</span>
<span class="p">}</span>

<span class="kd">struct</span> <span class="kt">TimeParameters</span> <span class="p">{</span>
  <span class="k">let</span> <span class="nv">hour</span><span class="p">:</span> <span class="kt">Int</span>
  <span class="k">let</span> <span class="nv">minute</span><span class="p">:</span> <span class="kt">Int</span>
<span class="p">}</span>

<span class="kd">struct</span> <span class="kt">DateTimeParameters</span> <span class="p">{</span>
  <span class="k">let</span> <span class="nv">date</span><span class="p">:</span> <span class="kt">DateParameters</span>
  <span class="k">let</span> <span class="nv">time</span><span class="p">:</span> <span class="kt">TimeParameters</span>
<span class="p">}</span>

<span class="kd">struct</span> <span class="kt">DateTimeEndTimeParameters</span> <span class="p">{</span>
  <span class="k">let</span> <span class="nv">date</span><span class="p">:</span> <span class="kt">DateParameters</span>
  <span class="k">let</span> <span class="nv">time</span><span class="p">:</span> <span class="kt">TimeParameters</span>
  <span class="k">let</span> <span class="nv">endTime</span><span class="p">:</span> <span class="kt">TimeParameters</span>
<span class="p">}</span>

<span class="kd">struct</span> <span class="kt">DateTimeDateTimeParameters</span> <span class="p">{</span>
  <span class="k">let</span> <span class="nv">start</span><span class="p">:</span> <span class="kt">DateTimeParameters</span>
  <span class="k">let</span> <span class="nv">end</span><span class="p">:</span> <span class="kt">DateTimeParameters</span>
<span class="p">}</span></code></pre></figure>

<p>But there’s some infrastructure work that will need to convert our various parameter models into formatted strings and our <code class="language-plaintext highlighter-rouge">TMDateType</code> model.</p>

<p>By leveraging protocols and ensuring that each of the parameter models conform to them, we can easily create a uniform API for our parameter models.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">protocol</span> <span class="kt">FormattedDateRepresentable</span> <span class="p">{</span>
  <span class="k">var</span> <span class="nv">formattedDate</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>
<span class="p">}</span>

<span class="kd">protocol</span> <span class="kt">ParameterConvertible</span> <span class="p">{</span>
  <span class="kd">func</span> <span class="nf">toDateType</span><span class="p">()</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">TMDateType</span>
<span class="p">}</span></code></pre></figure>

<p>Then we can convert to formatted strings with a useful extension to <code class="language-plaintext highlighter-rouge">Int</code> for string formatting them with fixed digits:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">private</span> <span class="kd">extension</span> <span class="kt">Int</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">leadingZeroString</span><span class="p">(</span><span class="nv">digits</span><span class="p">:</span> <span class="kt">Int</span> <span class="o">=</span> <span class="mi">2</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">String</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">format</span> <span class="o">=</span> <span class="s">"%0</span><span class="se">\(</span><span class="n">digits</span><span class="se">)</span><span class="s">d"</span>
        <span class="k">return</span> <span class="kt">String</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="n">format</span><span class="p">,</span> <span class="k">self</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>Looking at our <code class="language-plaintext highlighter-rouge">DateParameters</code> and <code class="language-plaintext highlighter-rouge">TimeParameters</code> models:</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">DateParameters</span><span class="p">:</span> <span class="kt">FormattedDateRepresentable</span> <span class="p">{</span>
    <span class="o">...</span>
    <span class="k">var</span> <span class="nv">formattedDate</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span>
        <span class="c1">// yyyy-MM-dd</span>
        <span class="s">"</span><span class="se">\(</span><span class="n">year</span><span class="o">.</span><span class="nf">leadingZeroString</span><span class="p">(</span><span class="nv">digits</span><span class="p">:</span> <span class="mi">4</span><span class="p">)</span><span class="se">)</span><span class="s">-</span><span class="se">\(</span><span class="n">month</span><span class="o">.</span><span class="nf">leadingZeroString</span><span class="p">()</span><span class="se">)</span><span class="s">-</span><span class="se">\(</span><span class="n">day</span><span class="o">.</span><span class="nf">leadingZeroString</span><span class="p">()</span><span class="se">)</span><span class="s">"</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">struct</span> <span class="kt">TimeParameters</span><span class="p">:</span> <span class="kt">FormattedDateRepresentable</span> <span class="p">{</span>
	<span class="o">...</span>
    <span class="k">var</span> <span class="nv">formattedDate</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span>
        <span class="s">"</span><span class="se">\(</span><span class="n">hour</span><span class="o">.</span><span class="nf">leadingZeroString</span><span class="p">()</span><span class="se">)</span><span class="s">:</span><span class="se">\(</span><span class="n">minute</span><span class="o">.</span><span class="nf">leadingZeroString</span><span class="p">()</span><span class="se">)</span><span class="s">"</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>Converting to <code class="language-plaintext highlighter-rouge">TMDateType</code> will use this formatted strings and our <code class="language-plaintext highlighter-rouge">Date</code> extensions. By throwing errors for the failure cases, we can alert callers to incorrect data in our parameter models, when we try to convert them into <code class="language-plaintext highlighter-rouge">Date</code> objects.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">DateParameters</span><span class="p">:</span> <span class="kt">FormattedDateRepresentable</span><span class="p">,</span> <span class="kt">ParameterConvertible</span> <span class="p">{</span>
	<span class="o">...</span>
    <span class="kd">func</span> <span class="nf">toDateType</span><span class="p">()</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">TMDateType</span> <span class="p">{</span>
       <span class="k">guard</span> <span class="k">let</span> <span class="nv">date</span> <span class="o">=</span> <span class="kt">Date</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="n">date</span><span class="p">,</span> <span class="n">formattedDate</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
          <span class="k">throw</span> <span class="kt">TMError</span><span class="o">.</span><span class="nf">invalidFormattedString</span><span class="p">(</span><span class="n">formattedDate</span><span class="p">)</span>
       <span class="p">}</span>
       
       <span class="k">return</span> <span class="o">.</span><span class="nf">date</span><span class="p">(</span><span class="n">date</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>That’s the bulk of the logic. The rest is just stitching parameter models together with some edge case logic to ensure that end dates are always after begin dates.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// DateTimeParameters conversions</span>
<span class="k">var</span> <span class="nv">formattedDate</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span>
	<span class="s">"</span><span class="se">\(</span><span class="n">date</span><span class="o">.</span><span class="n">formattedDate</span><span class="se">)</span><span class="s"> </span><span class="se">\(</span><span class="n">time</span><span class="o">.</span><span class="n">formattedDate</span><span class="se">)</span><span class="s">"</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">toDateType</span><span class="p">()</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">TMDateType</span> <span class="p">{</span>
	<span class="k">guard</span> <span class="k">let</span> <span class="nv">date</span> <span class="o">=</span> <span class="kt">Date</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="n">dateTime</span><span class="p">,</span> <span class="n">formattedDate</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
		<span class="k">throw</span> <span class="kt">TMError</span><span class="o">.</span><span class="nf">invalidFormattedString</span><span class="p">(</span><span class="n">formattedDate</span><span class="p">)</span>
	<span class="p">}</span>

	<span class="k">return</span> <span class="o">.</span><span class="nf">date</span><span class="p">(</span><span class="n">date</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// DateTimeEndTimeParameters conversion</span>
<span class="kd">func</span> <span class="nf">toDateType</span><span class="p">()</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">TMDateType</span> <span class="p">{</span>
	<span class="k">let</span> <span class="nv">startDateModel</span> <span class="o">=</span> <span class="kt">DateTimeParameters</span><span class="p">(</span><span class="nv">date</span><span class="p">:</span> <span class="n">date</span><span class="p">,</span> <span class="nv">time</span><span class="p">:</span> <span class="n">time</span><span class="p">)</span>
	<span class="k">let</span> <span class="nv">endDateModel</span> <span class="o">=</span> <span class="kt">DateTimeParameters</span><span class="p">(</span><span class="nv">date</span><span class="p">:</span> <span class="n">date</span><span class="p">,</span> <span class="nv">time</span><span class="p">:</span> <span class="n">endTime</span><span class="p">)</span>
	<span class="k">guard</span> <span class="k">let</span> <span class="nv">startDate</span> <span class="o">=</span> <span class="kt">Date</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="n">dateTime</span><span class="p">,</span> <span class="n">startDateModel</span><span class="o">.</span><span class="n">formattedDate</span><span class="p">),</span>
		  <span class="k">let</span> <span class="nv">endDate</span> <span class="o">=</span> <span class="kt">Date</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="n">dateTime</span><span class="p">,</span> <span class="n">endDateModel</span><span class="o">.</span><span class="n">formattedDate</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
		<span class="k">throw</span> <span class="kt">TMError</span><span class="o">.</span><span class="nf">invalidFormattedString</span><span class="p">(</span><span class="n">formattedDate</span><span class="p">)</span>
	<span class="p">}</span>
	<span class="k">guard</span> <span class="n">startDate</span> <span class="o">&lt;</span> <span class="n">endDate</span> <span class="k">else</span> <span class="p">{</span>
		<span class="k">throw</span> <span class="kt">TMError</span><span class="o">.</span><span class="nf">endDateNotAfterStartDate</span><span class="p">(</span><span class="n">startDate</span><span class="p">,</span> <span class="n">endDate</span><span class="p">)</span>
	<span class="p">}</span>

	<span class="k">return</span> <span class="o">.</span><span class="nf">beginEndDate</span><span class="p">(</span><span class="n">startDate</span><span class="p">,</span> <span class="n">endDate</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// DateTimeDateTimeParameters conversion</span>
<span class="kd">func</span> <span class="nf">toDateType</span><span class="p">()</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">TMDateType</span> <span class="p">{</span>
	<span class="k">guard</span> <span class="k">let</span> <span class="nv">startDate</span> <span class="o">=</span> <span class="kt">Date</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="n">dateTime</span><span class="p">,</span> <span class="n">start</span><span class="o">.</span><span class="n">formattedDate</span><span class="p">),</span>
		  <span class="k">let</span> <span class="nv">endDate</span> <span class="o">=</span> <span class="kt">Date</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="n">dateTime</span><span class="p">,</span> <span class="n">end</span><span class="o">.</span><span class="n">formattedDate</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
		<span class="k">throw</span> <span class="kt">TMError</span><span class="o">.</span><span class="nf">invalidFormattedString</span><span class="p">(</span><span class="n">formattedDate</span><span class="p">)</span>
	<span class="p">}</span>
	<span class="k">guard</span> <span class="n">startDate</span> <span class="o">&lt;</span> <span class="n">endDate</span> <span class="k">else</span> <span class="p">{</span>
		<span class="k">throw</span> <span class="kt">TMError</span><span class="o">.</span><span class="nf">endDateNotAfterStartDate</span><span class="p">(</span><span class="n">startDate</span><span class="p">,</span> <span class="n">endDate</span><span class="p">)</span>
	<span class="p">}</span>
	
	<span class="k">return</span> <span class="o">.</span><span class="nf">beginEndDate</span><span class="p">(</span><span class="n">startDate</span><span class="p">,</span> <span class="n">endDate</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<p>With that under our belts, we can add to <code class="language-plaintext highlighter-rouge">Date</code> extension, so that we can convert to and from our parameter models.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// MARK: Parameter Model Conversions</span>
<span class="kd">func</span> <span class="nf">toDateParameters</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">DateParameters</span><span class="p">?</span> <span class="p">{</span>
   <span class="k">let</span> <span class="nv">components</span> <span class="o">=</span> <span class="n">currentCalendar</span><span class="o">.</span><span class="nf">dateComponents</span><span class="p">(</span><span class="kt">TMDateFormat</span><span class="o">.</span><span class="n">date</span><span class="o">.</span><span class="n">components</span><span class="p">,</span> <span class="nv">from</span><span class="p">:</span> <span class="k">self</span><span class="p">)</span>

   <span class="k">guard</span> <span class="k">let</span> <span class="nv">year</span> <span class="o">=</span> <span class="n">components</span><span class="o">.</span><span class="n">year</span><span class="p">,</span>
         <span class="k">let</span> <span class="nv">month</span> <span class="o">=</span> <span class="n">components</span><span class="o">.</span><span class="n">month</span><span class="p">,</span>
         <span class="k">let</span> <span class="nv">day</span> <span class="o">=</span> <span class="n">components</span><span class="o">.</span><span class="n">day</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="k">return</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">year</span><span class="p">:</span> <span class="n">year</span><span class="p">,</span> <span class="nv">month</span><span class="p">:</span> <span class="n">month</span><span class="p">,</span> <span class="nv">day</span><span class="p">:</span> <span class="n">day</span><span class="p">)</span>
<span class="p">}</span>

<span class="kd">func</span> <span class="nf">toDateTimeParameters</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">DateTimeParameters</span><span class="p">?</span> <span class="p">{</span>
   <span class="k">let</span> <span class="nv">components</span> <span class="o">=</span> <span class="n">currentCalendar</span><span class="o">.</span><span class="nf">dateComponents</span><span class="p">(</span><span class="kt">TMDateFormat</span><span class="o">.</span><span class="n">dateTime</span><span class="o">.</span><span class="n">components</span><span class="p">,</span> <span class="nv">from</span><span class="p">:</span> <span class="k">self</span><span class="p">)</span>

   <span class="k">guard</span> <span class="k">let</span> <span class="nv">year</span> <span class="o">=</span> <span class="n">components</span><span class="o">.</span><span class="n">year</span><span class="p">,</span>
         <span class="k">let</span> <span class="nv">month</span> <span class="o">=</span> <span class="n">components</span><span class="o">.</span><span class="n">month</span><span class="p">,</span>
         <span class="k">let</span> <span class="nv">day</span> <span class="o">=</span> <span class="n">components</span><span class="o">.</span><span class="n">day</span><span class="p">,</span>
         <span class="k">let</span> <span class="nv">hour</span> <span class="o">=</span> <span class="n">components</span><span class="o">.</span><span class="n">hour</span><span class="p">,</span>
         <span class="k">let</span> <span class="nv">minutes</span> <span class="o">=</span> <span class="n">components</span><span class="o">.</span><span class="n">minute</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="k">return</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">date</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">year</span><span class="p">:</span> <span class="n">year</span><span class="p">,</span>
                            <span class="nv">month</span><span class="p">:</span> <span class="n">month</span><span class="p">,</span>
                            <span class="nv">day</span><span class="p">:</span> <span class="n">day</span><span class="p">),</span>
                <span class="nv">time</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">hour</span><span class="p">:</span> <span class="n">hour</span><span class="p">,</span>
                            <span class="nv">minute</span><span class="p">:</span> <span class="n">minutes</span><span class="p">))</span>
<span class="p">}</span>

<span class="c1">// MARK: Initializers</span>
<span class="nf">init</span><span class="p">?(</span><span class="n">date</span> <span class="nv">parameters</span><span class="p">:</span> <span class="kt">DateParameters</span><span class="p">)</span> <span class="p">{</span>
   <span class="k">self</span><span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="n">date</span><span class="p">,</span> <span class="n">parameters</span><span class="o">.</span><span class="n">formattedDate</span><span class="p">)</span>
<span class="p">}</span>

<span class="nf">init</span><span class="p">?(</span><span class="n">dateTime</span> <span class="nv">parameters</span><span class="p">:</span> <span class="kt">DateTimeParameters</span><span class="p">)</span> <span class="p">{</span>
   <span class="k">self</span><span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="n">dateTime</span><span class="p">,</span> <span class="n">parameters</span><span class="o">.</span><span class="n">formattedDate</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<h2 id="unit-tests">Unit Tests</h2>

<p>One way to ensure that your code is at the level that you want it to be and reduce your bug count is to ensure that you test your code.</p>

<p>Unit tests will also set your code apart if perspective employers are looking over your code sample repos, because they’re an integral part of the day to day business of coding.</p>

<p>Let’s look at some of the unit tests in this sample project:</p>

<h3 id="dateparameters-tests">DateParameters Tests</h3>
<p><a href="https://github.com/Jp4Mobile/SampleCode/tree/main/posts/projects/Infrastructure-2024-11-03/TaskManager/TaskManagerTests/DateParametersTests.swift"><code class="language-plaintext highlighter-rouge">DataParametersTests.swift</code></a></p>

<p>I’d recommend against trying to test everything. Just going with the main logic and verify the likely unhappy paths.</p>

<p>For example, in <code class="language-plaintext highlighter-rouge">DateParameters</code> and the <code class="language-plaintext highlighter-rouge">DateParametersTests</code>, I can do the end to end tests to verify that parameter models convert to and from dates properly and that tests the whole flow.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">var</span> <span class="nv">halloween</span><span class="p">:</span> <span class="kt">Date</span> <span class="p">{</span>
	<span class="kt">Date</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="n">dateTime</span><span class="p">,</span> <span class="s">"2024-10-31 10:31"</span><span class="p">)</span> <span class="p">??</span> <span class="kt">Date</span><span class="p">()</span>
<span class="p">}</span>
<span class="c1">// MARK: - Parameter Convertible Tests</span>
<span class="kd">func</span> <span class="nf">test_dateModel_convertsProperly</span><span class="p">()</span> <span class="k">throws</span> <span class="p">{</span>
	<span class="k">let</span> <span class="nv">model</span> <span class="o">=</span> <span class="k">try</span> <span class="kt">XCTUnwrap</span><span class="p">(</span><span class="n">halloween</span><span class="o">.</span><span class="nf">toDateParameters</span><span class="p">())</span>
	<span class="n">sut</span> <span class="o">=</span> <span class="k">try</span> <span class="n">model</span><span class="o">.</span><span class="nf">toDateType</span><span class="p">()</span>

	<span class="k">let</span> <span class="nv">halloweenDate</span> <span class="o">=</span> <span class="k">try</span> <span class="kt">XCTUnwrap</span><span class="p">(</span><span class="kt">Date</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="n">date</span><span class="p">,</span> <span class="n">model</span><span class="o">.</span><span class="n">formattedDate</span><span class="p">))</span>
	<span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="p">,</span> <span class="o">.</span><span class="nf">date</span><span class="p">(</span><span class="n">halloweenDate</span><span class="p">))</span>
<span class="p">}</span>

<span class="kd">func</span> <span class="nf">test_dateTimeModel_convertsProperly</span><span class="p">()</span> <span class="k">throws</span> <span class="p">{</span>
	<span class="n">sut</span> <span class="o">=</span> <span class="k">try</span> <span class="n">halloween</span><span class="o">.</span><span class="nf">toDateTimeParameters</span><span class="p">()?</span><span class="o">.</span><span class="nf">toDateType</span><span class="p">()</span>
	<span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">sut</span><span class="p">,</span> <span class="o">.</span><span class="nf">date</span><span class="p">(</span><span class="n">halloween</span><span class="p">))</span>
<span class="p">}</span></code></pre></figure>

<p>Flipping through the sample code, you’ll see we also test our formatted data to ensure that functionality as well.</p>

<h3 id="dateextensions-tests">DateExtensions Tests</h3>
<p><a href="https://github.com/Jp4Mobile/SampleCode/tree/main/posts/projects/Infrastructure-2024-11-03/TaskManager/TaskManagerTests/DateExtensionsTests.swift"><code class="language-plaintext highlighter-rouge">DataExtensionsTests.swift</code></a></p>

<p>In <code class="language-plaintext highlighter-rouge">DateExtensionsTests</code>, unfortunately, we have to test more of the invalid paths to ensure that no likely conversion paths were forgotten, such as:</p>

<ul>
  <li>Invalid year</li>
  <li>Invalid month</li>
  <li>Invalid day</li>
  <li>Invalid hour</li>
  <li>Invalid minute</li>
</ul>

<p>The conversions back and forth from formatted <code class="language-plaintext highlighter-rouge">String</code> to <code class="language-plaintext highlighter-rouge">Date</code> and back was relatively simple to code up.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Happy Path testing...</span>
<span class="kd">func</span> <span class="nf">test_init_fromDate_withValidInput_returnsDate</span><span class="p">()</span> <span class="p">{</span>
	<span class="k">let</span> <span class="nv">dateFromYMD</span> <span class="o">=</span> <span class="kt">Date</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="n">date</span><span class="p">,</span> <span class="s">"2024-10-31"</span><span class="p">)</span>
	<span class="k">let</span> <span class="nv">dateFromParameters</span> <span class="o">=</span> <span class="kt">Date</span><span class="p">(</span><span class="nv">date</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">year</span><span class="p">:</span> <span class="mi">2024</span><span class="p">,</span> <span class="nv">month</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span> <span class="nv">day</span><span class="p">:</span> <span class="mi">31</span><span class="p">))</span>
	<span class="kt">XCTAssertEqual</span><span class="p">(</span><span class="n">dateFromYMD</span><span class="p">,</span> <span class="n">dateFromParameters</span><span class="p">)</span>
<span class="p">}</span>
<span class="c1">// Unhappy Path testing...</span>
<span class="kd">func</span> <span class="nf">test_init_fromDate_withInvalidInput_returnsNil</span><span class="p">()</span> <span class="p">{</span>
	<span class="kt">XCTAssertNil</span><span class="p">(</span><span class="kt">Date</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="n">date</span><span class="p">,</span> <span class="s">"0-10-31"</span><span class="p">))</span>
	<span class="kt">XCTAssertNil</span><span class="p">(</span><span class="kt">Date</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="n">date</span><span class="p">,</span> <span class="s">"2024-13-31"</span><span class="p">))</span>
	<span class="kt">XCTAssertNil</span><span class="p">(</span><span class="kt">Date</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="n">date</span><span class="p">,</span> <span class="s">"2024-10-32"</span><span class="p">))</span>
	<span class="kt">XCTAssertNil</span><span class="p">(</span><span class="kt">Date</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="n">date</span><span class="p">,</span> <span class="s">"2024-10-31 12:30"</span><span class="p">))</span>
	<span class="kt">XCTAssertNil</span><span class="p">(</span><span class="kt">Date</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="n">date</span><span class="p">,</span> <span class="s">"unknown with valid 2024-10-31 in it"</span><span class="p">))</span>
	<span class="kt">XCTAssertNil</span><span class="p">(</span><span class="kt">Date</span><span class="p">(</span><span class="nv">date</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">year</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="nv">month</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span> <span class="nv">day</span><span class="p">:</span> <span class="mi">31</span><span class="p">)))</span>
	<span class="kt">XCTAssertNil</span><span class="p">(</span><span class="kt">Date</span><span class="p">(</span><span class="nv">date</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">year</span><span class="p">:</span> <span class="mi">2024</span><span class="p">,</span> <span class="nv">month</span><span class="p">:</span> <span class="mi">13</span><span class="p">,</span> <span class="nv">day</span><span class="p">:</span> <span class="mi">31</span><span class="p">)))</span>
	<span class="kt">XCTAssertNil</span><span class="p">(</span><span class="kt">Date</span><span class="p">(</span><span class="nv">date</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">year</span><span class="p">:</span> <span class="mi">2024</span><span class="p">,</span> <span class="nv">month</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span> <span class="nv">day</span><span class="p">:</span> <span class="mi">32</span><span class="p">)))</span>
<span class="p">}</span></code></pre></figure>

<p>The rest of the unit tests are much of the same, as we continue to test the other conversions.</p>

<hr />

<p>Next stop, the <code class="language-plaintext highlighter-rouge">EventKit</code> manager to ensure that the required logic wrapping the various code is as easy to use as possible.</p>]]></content><author><name>Jp</name></author><category term="coding" /><category term="taskmanager" /><category term="models" /><category term="extensions" /><category term="date-utilities" /><category term="testing" /><summary type="html"><![CDATA[This is where we start getting into the nuts and bolts of the infrastructure for the project by building models, extensions to make the use of these models easier as I put together date utilities to make my life easier. Luckily, TaskManager makes things a little easier in terms of dates. We only have two major date formats to convert to and from a Date foundation class: "yyyy-MM-dd" and "yyyy-MM-dd HH:mm" (ie; "2024-10-31" and "2024-10-31 10:31"). All of the code for this blog post is in this sample code repo.]]></summary></entry><entry><title type="html">The Power of Enums in Swift</title><link href="https://jp4mobile.com/coding/2024/10/27/power-of-enums.html" rel="alternate" type="text/html" title="The Power of Enums in Swift" /><published>2024-10-27T14:00:01+00:00</published><updated>2024-10-27T14:00:01+00:00</updated><id>https://jp4mobile.com/coding/2024/10/27/power-of-enums</id><content type="html" xml:base="https://jp4mobile.com/coding/2024/10/27/power-of-enums.html"><![CDATA[<p>Enums are one of my favorite parts of the Swift language. They’re one of the most powerful value-based objects.</p>

<p>In this post, we’ll be discussing increasingly complex uses of enums.</p>

<p>All of the code from this blog post is in a <a href="https://github.com/Jp4Mobile/SampleCode/tree/main/posts/playgrounds">playground</a>.</p>

<!--more-->

<h2 id="simple-enums">Simple Enums</h2>

<p>At it’s simplest enums are just different known options of something.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>enum ColorSimple {
   case red, orange, yellow, green, blue, purple
}
</code></pre></div></div>

<p>This can be nice, but if the programmer wants to allow the user to select  from the known color choices, this will need display text.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>func name(from color: ColorSimple) -&gt; String {
   switch color {
    case .red:
        return "Red"
    case .orange:
        return "Orange"
    case .yellow:
        return "Yellow"
    case .green:
        return "Green"
    case .blue:
        return "Blue"
    case .purple:
        return "Purple"
   }
}
</code></pre></div></div>

<p>This certainly works, but it’s repetitive and could lead to a confusing code base with functionality having to do with the colors possibly spread over a number of files.</p>

<p>Luckily, enums can have raw values associated with them, as well as functions and dynamic variables.</p>

<h2 id="enums-with-raw-values">Enums With Raw Values</h2>

<p>When dealing with network calls and models, there could be numeric codes for a REST call’s arguments.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>enum UserType: Int {
  case unenrolled = 1
  case subscription // This would have a raw value of 2
  case lifetime = 99
}
</code></pre></div></div>

<p>These could be used in a hypothetical query.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>let users = try await allUsers(of: .unenrolled)
</code></pre></div></div>

<p>Back to the colors example, cleaned up a bit.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>enum ColorRaw: String {
  case red
  case orange
  case yellow
  case green
  case blue
  case purple
  
  /// Name to display
  var displayName: String {
    self.rawValue.capitalized
  }
  
  /// SwiftUI Context Color
  func swiftUIColor() -&gt; Color {
	switch self {
	case .red:
		return .red
	case .orange:
		return .orange
	case .yellow:
		return .yellow
	case .green:
		return .green
	case .blue:
		return .blue
	case .purple:
		return .purple
	}
  }
}
</code></pre></div></div>

<p>Unsurprisingly, this means that enums can be protocol conformant, making them even more powerful.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>enum LoginError: Error {
  case invalidEmail
  case invalidPassword
  case networkError
}
</code></pre></div></div>

<p>And allow developers to use them in increasingly varied ways.</p>

<h2 id="enums-with-associated-values">Enums With Associated Values</h2>

<p>Enums can also have associated values, which may simplify APIs.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>enum DateFormat {
  /// YMD
  /// ie; (yyyy-MM-dd)
  case date(Int, Int, Int)
</code></pre></div></div>
<p>While this works, it’s not hard to see that this could grow to be a problem and more burdensome as the number of arguments grow. Instead it might be easier to handle with specific model structures.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  /// YMD HM YMD HM
  case dateTimeDateTime(DateTimeDateTimeParameters)
}
</code></pre></div></div>

<p>Not only is this easier to read and use, but it allows the ability to use models that may have logic that would allow the normalization and validation within the models.</p>

<h2 id="enums-as-arguments">Enums as arguments</h2>

<p>They can also be used as arguments to clarify APIs.</p>

<p>ie; <code class="language-plaintext highlighter-rouge">print(today.string(format: .date))</code> Which is simple and easily understood as, <em>Print the variable of today as a formatted string using the date format.</em></p>

<p>That can be done rather easily.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>extension Date {
  /// TaskMaster Specific date formats
  enum TMDateFormat {
    /// Date in our YMD format
    /// ie; yyyy-MM-dd
    case date
    /// Date in our YMD H:S format
    /// ie; yyyy-MM-dd HH:mm
    case dateTime
    
    var format: String {
      switch self {
        case .date:
          return "yyyy-MM-dd"
        case .dateTime:
          return "yyyy-MM-dd HH:mm"
      }
    }
  }
  
  /// Create a formatted string with our known formats.
  func string(format: TMDateFormat) -&gt; String {
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = format.format
    return dateFormatter.string(from: self)
  }
}
</code></pre></div></div>

<h2 id="incidental-use-of-enums">Incidental Use of Enums</h2>

<p>Another useful element of enums is that they cannot be instantiated in other ways, too.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>enum Constant {
    enum Feature {
        enum Welcome: FeatureDescribable {
            static let title: String = "Welcome"
        }
        enum Settings: FeatureDescribable {
            static let title: String = "Settings"

            enum Section: String, CaseIterable {
                case license = "Open Source Licenses"
                case privacy = "Privacy Policy"
                case support = "Support"
            }
        }
    }
}
</code></pre></div></div>]]></content><author><name>Jp</name></author><category term="coding" /><category term="taskmanager" /><category term="enums" /><summary type="html"><![CDATA[Enums are one of my favorite parts of the Swift language. They’re one of the most powerful value-based objects. In this post, we’ll be discussing increasingly complex uses of enums. All of the code from this blog post is in a playground.]]></summary></entry><entry><title type="html">TaskManager Overview</title><link href="https://jp4mobile.com/coding/2024/10/20/overview-of-taskmanager.html" rel="alternate" type="text/html" title="TaskManager Overview" /><published>2024-10-20T14:00:01+00:00</published><updated>2024-10-20T14:00:01+00:00</updated><id>https://jp4mobile.com/coding/2024/10/20/overview-of-taskmanager</id><content type="html" xml:base="https://jp4mobile.com/coding/2024/10/20/overview-of-taskmanager.html"><![CDATA[<p>Breaking down a project into milestones and figuring out your roadmap can be a struggle. Let’s walk through an example for the project that I’ll be working on in a series of blog posts.</p>

<!--more-->

<p>I am a big fan of <a href="https://www.TaskPaper.com">TaskPaper</a> by <a href="https://www.HogsBaySoftware.com">Hogs Bay Software</a> and I use it extensively to keep track of my life across multiple devices and platforms. It has a simple yet rich interface.</p>

<figure class="highlight"><pre><code class="language-taskpaper" data-lang="taskpaper">Project:
  Notes on a project.
  TaskPaper uses a hierarchical structure to keep track of parents and
  ownership.
  ie; all of the below items are within this project.

  - Item for that project.
    - Child item with a note.
      Note about that item.
  - Tagged Item @tag
  - Tagged Item where the tag has a payload @tag(payload)</code></pre></figure>

<p>But, and with developers there is almost always a but, I wish it integrated a bit better with some of the built ins for my Apple ecosystem, so that I would only have to check one place to see what I’ve got going on.</p>

<p>That’s the origin of my <code class="language-plaintext highlighter-rouge">TaskManager</code> project, I hope that I can keep the same simplicity of <a href="https://www.TaskPaper.com">TaskPaper</a>, but integrate it with my <code class="language-plaintext highlighter-rouge">Calendar</code> and <code class="language-plaintext highlighter-rouge">Reminder</code> apps built into iOS. (Yes, <a href="https://www.TaskPaper.com">TaskPaper</a> supports exporting and importing into the reminders app, but unfortunately, not in the way I was imagining.)</p>

<p>I have a nodding acquaintance with Apple’s <a href="https://developer.apple.com/documentation/eventkit">EventKit</a> which gives an integration into the calendar(s), appointments, and reminders.</p>

<p>This seems perfect for my use of <a href="https://www.TaskPaper.com">TaskPaper</a>, where I have a number of items with due date tags with the following formats:</p>

<ul>
  <li>Open-ended or no due date:
    <blockquote>
      <p><code class="language-plaintext highlighter-rouge">@due</code></p>
    </blockquote>
  </li>
  <li>Due date:
    <blockquote>
      <p><code class="language-plaintext highlighter-rouge">@due(2024-10-31)</code></p>
    </blockquote>
  </li>
  <li>Due date and specific time:
    <blockquote>
      <p><code class="language-plaintext highlighter-rouge">@due(2024-10-31 12:30)</code></p>
    </blockquote>
  </li>
  <li>Appointments:
    <blockquote>
      <p><code class="language-plaintext highlighter-rouge">@due(2024-10-31 12:30-13:00)</code></p>

      <p><code class="language-plaintext highlighter-rouge">@due(2024-10-31 12:30 thru 13:00)</code></p>
    </blockquote>
  </li>
  <li>Spanning appointments:
    <blockquote>
      <p><code class="language-plaintext highlighter-rouge">@due(2024-10-31 13:00 thru 2024-11-06 15:00)</code></p>
    </blockquote>
  </li>
</ul>

<p>It’s easy to imagine these converting to a calendar event or a reminder in specific lists/calendars.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Groceries:
  - Get candy @home
Fitness:
  - Trainer @gym @due(2024-10-31 07:00-08:00)
    Studio 617
Jp4Mobile:
  - Blog post on enums in Swift @work @due(2024-10-31)
  - Check blog engagement @work @due(2024-11-07)
</code></pre></div></div>

<p>Such as an event: <img src="/img/Event-2024-10-20.png" alt="Event from example" /></p>

<p>Or reminders: <img src="/img/Reminders-2024-10-20.png" alt="Reminder from example" /></p>

<h2 id="project-breakdown">Project Breakdown</h2>

<p>As with any project, much of the work is breaking it down to smaller deliverables, so that you can both see incremental improvements, but also understand some of the concepts.</p>

<p>For this, I see the following milestones:</p>

<ul>
  <li>Infrastructure
    <blockquote>
      <p>This is going to be the behind the scenes things to make everything possible.</p>

      <p>It will include things like:</p>
    </blockquote>
    <ul>
      <li>Models
        <blockquote>
          <p>The data representation around the date structures above.</p>
        </blockquote>
      </li>
      <li>Extensions
        <blockquote>
          <p>These extensions to the Apple <a href="https://developer.apple.com/documentation/foundation/date">Date</a> foundation structure to make life easier for the <code class="language-plaintext highlighter-rouge">TaskManager</code> app.</p>
        </blockquote>
      </li>
      <li><a href="https://developer.apple.com/documentation/eventkit">EventKit</a> Wrapper
        <blockquote>
          <p>From a cursory reading of the Apple documentation, there’s certain logic and repetitive boilerplate checking of permissions that make a manager or some other form of wrapper a necessity, so that callers will have a cleaner interface.</p>
        </blockquote>
      </li>
    </ul>
  </li>
  <li>UI
    <blockquote>
      <p>What will be put on the screen and allow a user to interact with it.</p>
    </blockquote>
    <ul>
      <li>Main Text View
        <blockquote>
          <p>To present the various elements in a <a href="https://www.TaskPaper.com">TaskPaper</a> file(s)</p>

          <p>These could be <code class="language-plaintext highlighter-rouge">Project</code>, <code class="language-plaintext highlighter-rouge">Note</code>, <code class="language-plaintext highlighter-rouge">Item</code>, <code class="language-plaintext highlighter-rouge">Tag</code> on any <code class="language-plaintext highlighter-rouge">Item</code> and include their nested structure.</p>
        </blockquote>
      </li>
      <li><code class="language-plaintext highlighter-rouge">Project</code> Detail View
        <blockquote>
          <p>To present a single <code class="language-plaintext highlighter-rouge">Project</code></p>
        </blockquote>
      </li>
      <li><code class="language-plaintext highlighter-rouge">Note</code> Detail View
        <blockquote>
          <p>To present a single <code class="language-plaintext highlighter-rouge">Note</code></p>
        </blockquote>
      </li>
      <li><code class="language-plaintext highlighter-rouge">Item</code> Detail View
        <blockquote>
          <p>To present a single item</p>
        </blockquote>
      </li>
      <li><code class="language-plaintext highlighter-rouge">Tags</code> Search Results View
        <blockquote>
          <p>To present <code class="language-plaintext highlighter-rouge">Item</code>s that conform to the <code class="language-plaintext highlighter-rouge">Tag</code>(s) searched for.</p>
        </blockquote>
      </li>
      <li>Settings View</li>
    </ul>
  </li>
  <li>Refinement
    <blockquote>
      <p>Nothing is ever perfect out of the gate.</p>

      <p>Interacting with things will help me iterate and make it better (or at least more useful to me).</p>
    </blockquote>
  </li>
</ul>

<h2 id="blog-posts">Blog Posts</h2>

<p>As I work through my deliverables, I’ll be posting about some of the lessons learned, useful bits that I’ve learned, and outlining some of the pain points to try to help others.</p>

<p>Posts will include <a href="https://github.com/Jp4Mobile/SampleCode">sample code</a> in the <a href="https://github.com/Jp4Mobile">Jp4Mobile repo</a>. Feel free to reach out if you have questions.</p>

<p>Next week, we’ll talk about enums for data models.</p>]]></content><author><name>Jp</name></author><category term="coding" /><category term="taskmanager" /><category term="taskpaper" /><category term="productivity" /><summary type="html"><![CDATA[Breaking down a project into milestones and figuring out your roadmap can be a struggle. Let’s walk through an example for the project that I’ll be working on in a series of blog posts.]]></summary></entry></feed>