<?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="http://villyg.com/feed.xml" rel="self" type="application/atom+xml" /><link href="http://villyg.com/" rel="alternate" type="text/html" /><updated>2026-01-23T19:15:50+00:00</updated><id>http://villyg.com/feed.xml</id><title type="html">Villy G’s blog</title><subtitle>Software Craftsman</subtitle><entry><title type="html">No More Off-Season: Building a Garage Golf Simulator - Part 3: Asembling the enclosure</title><link href="http://villyg.com/hobbies/golf/2025/10/09/Building-a-golf-simulator_part-3.html" rel="alternate" type="text/html" title="No More Off-Season: Building a Garage Golf Simulator - Part 3: Asembling the enclosure" /><published>2025-10-09T21:37:16+00:00</published><updated>2025-10-09T21:37:16+00:00</updated><id>http://villyg.com/hobbies/golf/2025/10/09/Building-a-golf-simulator_part-3</id><content type="html" xml:base="http://villyg.com/hobbies/golf/2025/10/09/Building-a-golf-simulator_part-3.html"><![CDATA[<p>Welcome back to my DIY garage simulator build!</p>

<p>In <a href="/hobbies/golf/2025/08/05/Building-a-golf-simulator-part-1.html"><strong>Part 1</strong></a>, I measured my space and laid out the master plan, dealing with my 10-foot width constraint. In <a href="/hobbies/golf/2025/08/09/Building-a-golf-simulator-part-2.html"><strong>Part 2</strong></a>, I agonized over the “brains” of the operation and landed on the Square Golf launch monitor as the perfect blend of performance and value.</p>

<p>Now, it’s time to build the “home” for it all: <strong>the enclosure</strong>. This post will cover everything from the frame and screen to the safety padding and floor.
<!--more--></p>

<h2 id="choosing-the-impact-screen">Choosing the impact screen</h2>

<p>Before I could build the frame, I had to know <em>exactly</em> what I was stretching inside it. The screen’s dimensions and mounting system dictate the entire frame build.</p>

<p>After a lot of research, I landed on the <strong><a href="https://www.virtual-golf-simulator.com/golf-impact-screen-poly-spacer-black-border-with-grommets/">Poly Spacer Impact Screen</a></strong>. I ordered the <strong>(8.5’ x 8.5’)</strong> model.</p>

<p>This screen hit all my key requirements. The “Poly Spacer” material is a premium, 2-layer screen with an internal nylon cushion. Everything I read said this material is a perfect trifecta of:</p>
<ol>
  <li><strong>Durability:</strong> It’s designed to take thousands of real ball impacts.</li>
  <li><strong>Low Noise:</strong> This was a huge one for a garage build. The cushioned layers make the impact a dull, satisfying “thud” instead of a loud “CRACK.”</li>
  <li><strong>Low Bounce-Back:</strong> The material is made to absorb energy, so the ball just drops to the floor instead of firing back at me.</li>
</ol>

<p>The biggest selling point for this DIY build, though, was the finish. It comes with a heavy-duty <strong>3-inch black vinyl border</strong> with <strong>metal grommets</strong> already installed every 12 inches. This meant I wouldn’t have to do any sewing or complex finishing. It was built to be hung on a frame.</p>

<h2 id="building-the-frame">Building the frame</h2>

<p>Just like with the launch monitor, my goals for the enclosure were:</p>
<ol>
  <li><strong>Cost-effective:</strong> No way was I spending thousands on a pre-built frame.</li>
  <li><strong>Sturdy:</strong> It has to withstand thousands of ball impacts.</li>
  <li><strong>DIY-friendly:</strong> I needed to be able to build it myself with basic tools.</li>
</ol>

<p>I settled on the most popular and proven method: building the frame from <strong>1” metal conduit (EMT)</strong>. It’s cheap, rigid, and available at any big-box hardware store.</p>

<h3 id="materials">Materials</h3>

<ul>
  <li><strong>1” EMT (Electrical Metallic Tubing) Conduit:</strong> 9 <a href="https://www.homedepot.com/p/1-in-x-10-ft-Electrical-Metallic-Tubing-EMT-Conduit-550210000/304229415">10-foot lengths</a>.</li>
  <li><strong>Metal Fittings:</strong> This is the secret sauce that makes the project so easy. Instead of trying to weld or bend pipe, I bought 6 <a href="https://tarps.com/products/1-flat-roof-corner-3-way-fitting-f3-1-1">3-way fittings</a> and 2 <a href="https://tarps.com/products/1-l-shape-2-way-fitting-fl-1">2-way fittings</a>. These are heavy-duty, galvanized steel, and have welded connections. The conduit just slides in and is secured with included eyebolts.</li>
  <li><strong>Spray Paint:</strong> For a clean, professional finish i picked the <a href="https://www.lowes.com/pd/Krylon-FUSION-ALL-IN-ONE-Matte-Black-Spray-Paint-and-Primer-In-One-NET-WT-12-oz/1000460293">Krylon paint and primer</a> even though nothing would really be visible from the pipe.</li>
</ul>

<p><img src="/assets/images/2025-10-30-Building-a-golf-simulator_part-3/pipes_unpainted.jpeg" alt="Pipes" /></p>

<h3 id="dimensions">Dimensions</h3>

<p>The most important step was getting the dimensions right. The rule of thumb for a grommet screen is to add about 3 inches on each side for tensioning. I also settled on 5’ depth. The duct canvas (more on it later) that I was going to use for the sides comes in 5’ width so I could just cut the 10’ EMT pipes in half - it was a no brainer.</p>

<ul>
  <li><strong>My Screen Size:</strong> 102” x 102”</li>
  <li><strong>My Frame Size:</strong> (102” + 3”) x (102” + 3”) = <strong>105” x 105” x 60”</strong></li>
</ul>

<h3 id="assembly-and-finishing">Assembly and finishing</h3>

<ol>
  <li><strong>Paint It Black:</strong> Before final assembly, I gave all the cut conduit and fittings a good couple of coats of the black spray paint. This is purely aesthetic, and it really doesn’t make any difference since the pipe would be covered.</li>
  <li><strong>Lay It Out:</strong> Once dry, I laid all my painted pipes and corner fittings on the garage floor in their final shape.</li>
  <li><strong>Assemble:</strong> I slotted the conduit pipes into the 3-way and 2-way corner fittings one by one and tightened the eyebolts.</li>
  <li><strong>Stand It Up:</strong> I stood the frame up in its final position. The result was a solid, professional-looking black frame, built for a fraction of the cost of a pre-made kit.</li>
</ol>

<p><img src="/assets/images/2025-10-30-Building-a-golf-simulator_part-3/pipes_painted.jpeg" alt="Pipes painted" /></p>

<p><img src="/assets/images/2025-10-30-Building-a-golf-simulator_part-3/corner.jpeg" alt="Corner" /></p>

<p><img src="/assets/images/2025-10-30-Building-a-golf-simulator_part-3/enclosure_1.jpeg" alt="Enclosure" /></p>

<h2 id="hanging-the-screen">Hanging the screen</h2>

<p>This is where the planning paid off. That gap on all four sides was the <em>perfect</em> amount of space needed for tensioning.</p>

<p>The installation was incredibly simple:</p>
<ol>
  <li><strong>Get Bungees:</strong> I bought a box of these <a href="https://amzn.to/4pb3sXF">cheap bungee balls</a> from Amazon.</li>
  <li><strong>Start at the Corners:</strong> I attached the four corners of the screen to the four corners of the frame first. This got the screen hanging in place.</li>
  <li><strong>Work Your Way Around:</strong> I then worked my way from the corners to the middle, one bungee at a time. I looped a bungee through every single grommet and wrapped it around the 1” conduit pipe.</li>
</ol>

<p>The goal is to create even tension all the way around, just like a small trampoline. You want it tight enough to get a flat, wrinkle-free surface for the projector, but with just enough “give” to absorb the ball’s impact safely.</p>

<p><img src="/assets/images/2025-10-30-Building-a-golf-simulator_part-3/bungees.jpeg" alt="bungees" /></p>

<p><img src="/assets/images/2025-10-30-Building-a-golf-simulator_part-3/screen_in_a_box.jpeg" alt="screen" /></p>

<h2 id="the-foundation-a-stroke-of-luck">The foundation (a stroke of luck)</h2>

<p>The enclosure was now framed but it was still standing on a cold, hard, concrete garage floor. I was budgeting for some kind of flooring, whether it was foam tiles or a large piece of turf, and it wasn’t going to be cheap but a friend of mine had a giant roll of high-quality artificial turf left over from a landscaping project. He mentioned it was just sitting around, and the best part? It was already cut to a size that was a perfect fit for my simulator bay.</p>

<p>The installation was, without a doubt, the easiest part of the entire build.</p>

<p>I just… put it down.</p>

<p>I unrolled it, flattened it out, and that was it. Instantly, the space was transformed. The turf deadens the sound of dropped balls, looks incredibly professional, and just feels right under your feet.</p>

<p>(To be clear, this isn’t my <em>hitting</em> mat—you’d destroy your wrists hitting off a thin piece of turf on concrete. This is the stance/flooring turf that covers the whole area. The actual hitting mat will sit on top of this.)</p>

<h2 id="the-result">The result</h2>

<p>Stepping back and seeing the screen up for the first time was a huge “wow” moment. It transformed from a pile of black pipes into an actual simulator. Hitting a few gentle test shots confirmed my choice—the sound was a dull, solid ‘thwack,’ and the ball just dropped straight down.</p>

<p><img src="/assets/images/2025-10-30-Building-a-golf-simulator_part-3/enclosure_screen_turf.jpeg" alt="Enclosure" /></p>

<h2 id="whats-next">What’s Next?</h2>

<p>In <strong>Part 4</strong>, I’ll walk you through some serious challenges in getting the protection layer right on all the sides.</p>]]></content><author><name></name></author><category term="hobbies" /><category term="golf" /><category term="golf" /><summary type="html"><![CDATA[Welcome back to my DIY garage simulator build! In Part 1, I measured my space and laid out the master plan, dealing with my 10-foot width constraint. In Part 2, I agonized over the “brains” of the operation and landed on the Square Golf launch monitor as the perfect blend of performance and value. Now, it’s time to build the “home” for it all: the enclosure. This post will cover everything from the frame and screen to the safety padding and floor.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://villyg.com/assets/images/2025-10-30-Building-a-golf-simulator_part-3/mainimage.jpeg" /><media:content medium="image" url="http://villyg.com/assets/images/2025-10-30-Building-a-golf-simulator_part-3/mainimage.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Decoding SwiftUI’s Data Flow: @Environment vs. @EnvironmentObject vs. @StateObject</title><link href="http://villyg.com/posts/decoding-swiftui-data-flow" rel="alternate" type="text/html" title="Decoding SwiftUI’s Data Flow: @Environment vs. @EnvironmentObject vs. @StateObject" /><published>2025-08-27T21:37:16+00:00</published><updated>2025-08-27T21:37:16+00:00</updated><id>http://villyg.com/posts/decoding-swiftui-data-flow</id><content type="html" xml:base="http://villyg.com/posts/decoding-swiftui-data-flow"><![CDATA[<p>If you’ve spent any time with SwiftUI, you know it’s a powerful framework for building user interfaces. But as your apps grow, a common question quickly arises: “What’s the best way to pass data around?” You start with <code class="language-plaintext highlighter-rouge">@State</code> and <code class="language-plaintext highlighter-rouge">@Binding</code> for simple local data. But soon you find yourself needing to share data across many views, and things get complicated. You’ve probably seen <code class="language-plaintext highlighter-rouge">@Environment</code>, <code class="language-plaintext highlighter-rouge">@EnvironmentObject</code>, and <code class="language-plaintext highlighter-rouge">@StateObject</code> mentioned, but the distinction can be blurry. When do you use which? Let’s demystify these three powerful property wrappers and understand their specific roles in your SwiftUI data flow.</p>

<!--more-->

<h2 id="environment-reading-the-systems-mind"><strong>@Environment</strong>: Reading the System’s Mind</h2>

<p>Think of <code class="language-plaintext highlighter-rouge">@Environment</code> as a way for your views to read system-wide settings or values provided by the SwiftUI framework itself. It’s like asking your app, “What’s the current situation?”</p>

<p>You use <code class="language-plaintext highlighter-rouge">@Environment</code> to access values like:</p>

<ul>
  <li>The device’s color scheme (dark or light mode)</li>
  <li>The current locale or calendar</li>
  <li>The size class (compact or regular)</li>
  <li>The presentation mode (for dismissing a modal view)</li>
</ul>

<h3 id="key-characteristics">Key Characteristics:</h3>

<ul>
  <li><strong>Purpose:</strong> To read data provided by the system or a parent view through the environment.</li>
  <li><strong>Scope</strong>: App-wide or hierarchy-wide.</li>
  <li><strong>Ownership</strong>: The view does not own this data. It’s just a subscriber to a value that exists elsewhere.</li>
  <li><strong>Usage</strong>: Mostly for read-only access.</li>
</ul>

<h3 id="example-detecting-dark-mode">Example: Detecting Dark Mode</h3>

<p>Let’s say you want to change a text color based on whether the user is in dark mode. <code class="language-plaintext highlighter-rouge">@Environment</code> makes this trivial.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kd">import</span> <span class="kt">SwiftUI</span>

<span class="kd">struct</span> <span class="kt">EnvironmentDemoView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
    <span class="c1">// Read the colorScheme value from the environment</span>
    <span class="kd">@Environment</span><span class="p">(\</span><span class="o">.</span><span class="n">colorScheme</span><span class="p">)</span> <span class="k">var</span> <span class="nv">colorScheme</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="kt">Text</span><span class="p">(</span><span class="s">"Hello, SwiftUI!"</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">largeTitle</span><span class="p">)</span>
                <span class="o">.</span><span class="nf">foregroundStyle</span><span class="p">(</span><span class="n">colorScheme</span> <span class="o">==</span> <span class="o">.</span><span class="n">dark</span> <span class="p">?</span> <span class="o">.</span><span class="nv">white</span> <span class="p">:</span> <span class="o">.</span><span class="n">black</span><span class="p">)</span>
                <span class="o">.</span><span class="nf">padding</span><span class="p">()</span>
                <span class="o">.</span><span class="nf">background</span><span class="p">(</span><span class="n">colorScheme</span> <span class="o">==</span> <span class="o">.</span><span class="n">dark</span> <span class="p">?</span> <span class="o">.</span><span class="nv">black</span> <span class="p">:</span> <span class="o">.</span><span class="n">white</span><span class="p">)</span>
                <span class="o">.</span><span class="nf">cornerRadius</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
                <span class="o">.</span><span class="nf">shadow</span><span class="p">(</span><span class="nv">radius</span><span class="p">:</span> <span class="mi">5</span><span class="p">)</span>

            <span class="kt">Text</span><span class="p">(</span><span class="n">colorScheme</span> <span class="o">==</span> <span class="o">.</span><span class="n">dark</span> <span class="p">?</span> <span class="s">"You are in Dark Mode"</span> <span class="p">:</span> <span class="s">"You are in Light Mode"</span><span class="p">)</span>
                <span class="o">.</span><span class="nf">padding</span><span class="p">(</span><span class="o">.</span><span class="n">top</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="cp">#Preview {</span>
    <span class="kt">EnvironmentDemoView</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Here, <code class="language-plaintext highlighter-rouge">\.colorScheme</code> is a key path to the value we want to read. SwiftUI automatically updates our view whenever this environment value changes. We didn’t have to pass it down manually; the view simply plucked it out of the air.</p>

<h2 id="environmentobject-the-app-wide-data-sharer"><strong>@EnvironmentObject</strong>: The App-Wide Data Sharer</h2>

<p>What if you have your own custom data that multiple, separate views need to access? For example, user authentication status, app settings, or a shared data model. Passing this data through every single intermediate view would be a nightmare.</p>

<p>This is where <code class="language-plaintext highlighter-rouge">@EnvironmentObject</code> shines. It allows you to inject your own custom object (a class conforming to <code class="language-plaintext highlighter-rouge">ObservableObject</code>) into the view hierarchy, making it available to any child view that asks for it.</p>

<h3 id="key-characteristics-1">Key Characteristics:</h3>

<ul>
  <li><strong>Purpose</strong>: To share your own observable objects across a deep view hierarchy without manual dependency injection.</li>
  <li><strong>Scope</strong>: Any descendant view of where the object is injected.</li>
  <li><strong>Ownership</strong>: The view that declares <code class="language-plaintext highlighter-rouge">@EnvironmentObject</code> does not own the object. It assumes the object has been created and placed in the environment by an ancestor view.</li>
  <li><strong>Usage</strong>: For shared app state that many views need to read and modify.</li>
</ul>

<h3 id="example-sharing-user-settings">Example: Sharing User Settings</h3>

<p>Create the Observable Object:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">import</span> <span class="kt">SwiftUI</span>

<span class="kd">class</span> <span class="kt">UserSettings</span><span class="p">:</span> <span class="kt">ObservableObject</span> <span class="p">{</span>
    <span class="kd">@Published</span> <span class="k">var</span> <span class="nv">username</span><span class="p">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">"Guest"</span>
    <span class="kd">@Published</span> <span class="k">var</span> <span class="nv">isAuthenticated</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">false</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Inject the Object in a Parent View:
You create an instance of your object and inject it into the environment using the <code class="language-plaintext highlighter-rouge">.environmentObject()</code> modifier. A common place to do this is at the root of your app.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">import</span> <span class="kt">SwiftUI</span>

<span class="kd">@main</span>
<span class="kd">struct</span> <span class="kt">MyApp</span><span class="p">:</span> <span class="kt">App</span> <span class="p">{</span>
    <span class="c1">// Create the single source of truth for user settings</span>
    <span class="kd">@StateObject</span> <span class="kd">private</span> <span class="k">var</span> <span class="nv">settings</span> <span class="o">=</span> <span class="kt">UserSettings</span><span class="p">()</span>

    <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">Scene</span> <span class="p">{</span>
        <span class="kt">WindowGroup</span> <span class="p">{</span>
            <span class="kt">ContentView</span><span class="p">()</span>
                <span class="o">.</span><span class="nf">environmentObject</span><span class="p">(</span><span class="n">settings</span><span class="p">)</span> <span class="c1">// Inject it here!</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Access it in any Child View:
Now, any view inside ContentView can easily access UserSettings.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">ProfileView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
    <span class="c1">// Ask for the UserSettings object from the environment</span>
    <span class="kd">@EnvironmentObject</span> <span class="k">var</span> <span class="nv">settings</span><span class="p">:</span> <span class="kt">UserSettings</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="kt">Text</span><span class="p">(</span><span class="s">"Welcome, </span><span class="se">\(</span><span class="n">settings</span><span class="o">.</span><span class="n">username</span><span class="se">)</span><span class="s">!"</span><span class="p">)</span>
            <span class="k">if</span> <span class="n">settings</span><span class="o">.</span><span class="n">isAuthenticated</span> <span class="p">{</span>
                <span class="kt">Button</span><span class="p">(</span><span class="s">"Log Out"</span><span class="p">)</span> <span class="p">{</span>
                    <span class="n">settings</span><span class="o">.</span><span class="n">isAuthenticated</span> <span class="o">=</span> <span class="kc">false</span>
                    <span class="n">settings</span><span class="o">.</span><span class="n">username</span> <span class="o">=</span> <span class="s">"Guest"</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Notice we didn’t have to pass settings into ProfileView. It just works!
Warning: If you forget to inject the object with <code class="language-plaintext highlighter-rouge">.environmentObject()</code>, your app will crash when the view tries to access it.</p>

<h2 id="stateobject-the-owner-and-creator"><strong>@StateObject</strong>: The Owner and Creator</h2>

<p>So, if <code class="language-plaintext highlighter-rouge">@EnvironmentObject</code> is for receiving shared data, where is that data created? In our example above, we used <code class="language-plaintext highlighter-rouge">@StateObject</code>.</p>

<p><code class="language-plaintext highlighter-rouge">@StateObject</code> is the property wrapper you use to instantiate and manage the lifecycle of an observable object within a specific view. SwiftUI ensures that an object declared as a <code class="language-plaintext highlighter-rouge">@StateObject</code> is created only once for the lifetime of that view. Even if the view’s struct is re-created during a redraw, the <code class="language-plaintext highlighter-rouge">@StateObject</code> persists.</p>

<h3 id="key-characteristics-2">Key Characteristics:</h3>

<ul>
  <li><strong>Purpose</strong>: To create and own an instance of an observable object. This view is the source of truth for that object.</li>
  <li><strong>Scope</strong>: The view that declares it.</li>
  <li><strong>Ownership</strong>: The view owns the object. It’s responsible for its creation and destruction.</li>
  <li><strong>Usage</strong>: Use it when a view needs its own persistent, complex state that can be shared with subviews (often via <code class="language-plaintext highlighter-rouge">@ObservedObject</code> or bindings).</li>
</ul>

<h3 id="example-a-view-specific-viewmodel">Example: A View-Specific ViewModel</h3>

<p>Imagine a view that fetches and displays a list of items. This view needs a view model to handle the network request and the resulting data. This view model belongs exclusively to this view.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">import</span> <span class="kt">SwiftUI</span>

<span class="kd">class</span> <span class="kt">ItemListViewModel</span><span class="p">:</span> <span class="kt">ObservableObject</span> <span class="p">{</span>
    <span class="kd">@Published</span> <span class="k">var</span> <span class="nv">items</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="kd">@Published</span> <span class="k">var</span> <span class="nv">isLoading</span> <span class="o">=</span> <span class="kc">false</span>

    <span class="kd">func</span> <span class="nf">fetchItems</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">isLoading</span> <span class="o">=</span> <span class="kc">true</span>
        <span class="c1">// Simulate a network request</span>
        <span class="kt">DispatchQueue</span><span class="o">.</span><span class="n">main</span><span class="o">.</span><span class="nf">asyncAfter</span><span class="p">(</span><span class="nv">deadline</span><span class="p">:</span> <span class="o">.</span><span class="nf">now</span><span class="p">()</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">self</span><span class="o">.</span><span class="n">items</span> <span class="o">=</span> <span class="p">[</span><span class="s">"Apple"</span><span class="p">,</span> <span class="s">"Banana"</span><span class="p">,</span> <span class="s">"Cherry"</span><span class="p">]</span>
            <span class="k">self</span><span class="o">.</span><span class="n">isLoading</span> <span class="o">=</span> <span class="kc">false</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">struct</span> <span class="kt">ItemListView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
    <span class="c1">// This view creates and OWNS its view model.</span>
    <span class="kd">@StateObject</span> <span class="kd">private</span> <span class="k">var</span> <span class="nv">viewModel</span> <span class="o">=</span> <span class="kt">ItemListViewModel</span><span class="p">()</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="n">viewModel</span><span class="o">.</span><span class="n">isLoading</span> <span class="p">{</span>
                <span class="kt">ProgressView</span><span class="p">()</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="kt">List</span><span class="p">(</span><span class="n">viewModel</span><span class="o">.</span><span class="n">items</span><span class="p">,</span> <span class="nv">id</span><span class="p">:</span> <span class="p">\</span><span class="o">.</span><span class="k">self</span><span class="p">)</span> <span class="p">{</span> <span class="n">item</span> <span class="k">in</span>
                    <span class="kt">Text</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="o">.</span><span class="n">onAppear</span> <span class="p">{</span>
            <span class="n">viewModel</span><span class="o">.</span><span class="nf">fetchItems</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Here, <code class="language-plaintext highlighter-rouge">ItemListView</code> is the definitive owner of its viewModel. The viewModel will not be accidentally deallocated or re-created during view updates.</p>

<h2 id="summary-at-a-glance">Summary: At a Glance</h2>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Property Wrapper</th>
      <th style="text-align: left">Purpose</th>
      <th style="text-align: left">Data Type</th>
      <th style="text-align: left">Ownership</th>
      <th style="text-align: left">When to Use</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@Environment</code></td>
      <td style="text-align: left">Read system or framework values.</td>
      <td style="text-align: left">Value types (structs, enums).</td>
      <td style="text-align: left">None. View is a subscriber.</td>
      <td style="text-align: left">Accessing color scheme, locale, size class, etc.</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@EnvironmentObject</code></td>
      <td style="text-align: left">Access a shared custom object from an ancestor.</td>
      <td style="text-align: left">Class (ObservableObject)</td>
      <td style="text-align: left">None. View is a consumer.</td>
      <td style="text-align: left">Accessing app-wide state like user settings or a data manager.</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">@StateObject</code></td>
      <td style="text-align: left">Create and manage the lifecycle of a custom object.</td>
      <td style="text-align: left">Class (ObservableObject)</td>
      <td style="text-align: left">Owner. View creates and holds the object.</td>
      <td style="text-align: left">Initializing a view model for a specific view; creating the source of truth for an object that will be shared via .environmentObject().</td>
    </tr>
  </tbody>
</table>

<h2 id="conclusion">Conclusion</h2>

<p>Understanding the difference between these three property wrappers is fundamental to architecting a clean and scalable SwiftUI application.</p>

<ul>
  <li>Use <code class="language-plaintext highlighter-rouge">@Environment</code> to peek at the system’s state.</li>
  <li>Use <code class="language-plaintext highlighter-rouge">@StateObject</code> to create and own your object in one specific view.</li>
  <li>Use <code class="language-plaintext highlighter-rouge">@EnvironmentObject</code> to receive that object in any other view, no matter how far down the hierarchy it is.</li>
</ul>

<p>Happy coding!</p>]]></content><author><name></name></author><category term="swift" /><category term="ios" /><summary type="html"><![CDATA[If you’ve spent any time with SwiftUI, you know it’s a powerful framework for building user interfaces. But as your apps grow, a common question quickly arises: “What’s the best way to pass data around?” You start with @State and @Binding for simple local data. But soon you find yourself needing to share data across many views, and things get complicated. You’ve probably seen @Environment, @EnvironmentObject, and @StateObject mentioned, but the distinction can be blurry. When do you use which? Let’s demystify these three powerful property wrappers and understand their specific roles in your SwiftUI data flow.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://villyg.com/assets/images/SwiftUI.jpg" /><media:content medium="image" url="http://villyg.com/assets/images/SwiftUI.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Implementing Drag and Drop in UICollectionView and Core Data – Part 2</title><link href="http://villyg.com/projects/gunvault/2025/08/12/Implementing-Drag-and-Drop-in-UICollectionView-and-Core-Data-Part-2.html" rel="alternate" type="text/html" title="Implementing Drag and Drop in UICollectionView and Core Data – Part 2" /><published>2025-08-12T22:30:00+00:00</published><updated>2025-08-12T22:30:00+00:00</updated><id>http://villyg.com/projects/gunvault/2025/08/12/Implementing-Drag-and-Drop-in-UICollectionView-and-Core-Data%E2%80%93Part-2</id><content type="html" xml:base="http://villyg.com/projects/gunvault/2025/08/12/Implementing-Drag-and-Drop-in-UICollectionView-and-Core-Data-Part-2.html"><![CDATA[<p>In <a href="/projects/gunvault/2025/08/04/Implementing-Drag-and-Drop-in-UICollectionView-and-Core-Data-Part-1.html">Part 1</a> of this series, I walked through the process of adding drag-and-drop functionality to a <code class="language-plaintext highlighter-rouge">UICollectionView</code> backed by Core Data. While I got it working, I mentioned that the solution, while functional, wasn’t as modern as it could be. The <code class="language-plaintext highlighter-rouge">NSFetchedResultsControllerDelegate</code> implementation, with its manual block operations, felt a bit clunky and error-prone. This post is about that additional refactoring: moving from the traditional <code class="language-plaintext highlighter-rouge">NSFetchedResultsControllerDelegate</code> to the more modern <code class="language-plaintext highlighter-rouge">UICollectionViewDiffableDataSource</code>.</p>

<!--more-->

<ul>
  <li><a href="#background">Background</a></li>
  <li><a href="#the-plan-a-modern-approach">The Plan: A Modern Approach</a>
    <ul>
      <li><a href="#step-1-laying-the-foundation">Step 1: Laying the Foundation</a></li>
      <li><a href="#step-2-creating-the-diffable-data-source">Step 2: Creating the Diffable Data Source</a></li>
      <li><a href="#step-3-tying-it-to-core-data">Step 3: Tying it to Core Data</a></li>
      <li><a href="#step-4-the-initial-load">Step 4: The Initial Load</a></li>
    </ul>
  </li>
  <li><a href="#the-result-cleaner-simpler-code">The Result: Cleaner, Simpler Code</a></li>
  <li><a href="#conclusion">Conclusion</a></li>
</ul>

<h2 id="background">Background</h2>

<p>To recap, the <code class="language-plaintext highlighter-rouge">PhotoListController</code> was using a <code class="language-plaintext highlighter-rouge">NSFetchedResultsController</code> to manage photos from Core Data. The delegate methods were responsible for updating the <code class="language-plaintext highlighter-rouge">UICollectionView</code> in response to data changes. This involved a lot of manual work with <code class="language-plaintext highlighter-rouge">BlockOperation</code> to batch updates, which, as I discovered in <a href="/projects/gunvault/2025/08/04/Implementing-Drag-and-Drop-in-UICollectionView-and-Core-Data-Part-1.html">Part 1</a>, can be tricky to get right.</p>

<p>The goal of this refactoring was to simplify this process by leveraging <code class="language-plaintext highlighter-rouge">UICollectionViewDiffableDataSource</code>, which Apple introduced in iOS 13. Diffable data sources automatically handle the complexities of updating the UI, which means less code, fewer bugs, and a happier developer (me).</p>

<h2 id="the-plan-a-modern-approach">The Plan: A Modern Approach</h2>

<p>The plan was to replace the existing <code class="language-plaintext highlighter-rouge">UICollectionViewDataSource</code> and <code class="language-plaintext highlighter-rouge">NSFetchedResultsControllerDelegate</code> methods with a <code class="language-plaintext highlighter-rouge">UICollectionViewDiffableDataSource</code>. Here’s how I broke it down.</p>

<h3 id="step-1-laying-the-foundation">Step 1: Laying the Foundation</h3>

<p>First, I needed to define the section and item identifiers for the diffable data source. Since my collection view only has one section, this was straightforward. The item identifier would be the <code class="language-plaintext highlighter-rouge">Photo</code> object itself, because the <code class="language-plaintext highlighter-rouge">NSManagedObject</code> it inherits from already conforms to <code class="language-plaintext highlighter-rouge">Hashable</code>.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// In PhotoListController.swift</span>

<span class="kd">enum</span> <span class="kt">Section</span> <span class="p">{</span>
    <span class="k">case</span> <span class="n">main</span>
<span class="p">}</span>

</code></pre></div></div>

<h3 id="step-2-creating-the-diffable-data-source">Step 2: Creating the Diffable Data Source</h3>

<p>Next, I created a new property for the <code class="language-plaintext highlighter-rouge">UICollectionViewDiffableDataSource</code> in <code class="language-plaintext highlighter-rouge">PhotoListController.swift</code>.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// In PhotoListController.swift</span>

<span class="kd">private</span> <span class="k">var</span> <span class="nv">dataSource</span><span class="p">:</span> <span class="kt">UICollectionViewDiffableDataSource</span><span class="o">&lt;</span><span class="kt">Section</span><span class="p">,</span> <span class="kt">Photo</span><span class="o">&gt;!</span>
</code></pre></div></div>

<p>Then, in <code class="language-plaintext highlighter-rouge">viewDidLoad</code>, I configured the data source. This involved creating the data source and providing a “cell provider” closure, which is responsible for configuring and returning a cell for a given <code class="language-plaintext highlighter-rouge">IndexPath</code> and <code class="language-plaintext highlighter-rouge">Photo</code>.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// In PhotoListController.swift, inside viewDidLoad()</span>

<span class="n">dataSource</span> <span class="o">=</span> <span class="kt">UICollectionViewDiffableDataSource</span><span class="o">&lt;</span><span class="kt">Section</span><span class="p">,</span> <span class="kt">Photo</span><span class="o">&gt;</span><span class="p">(</span><span class="nv">collectionView</span><span class="p">:</span> <span class="n">collectionView</span><span class="o">!</span><span class="p">)</span> <span class="p">{</span>
    <span class="p">(</span><span class="nv">collectionView</span><span class="p">:</span> <span class="kt">UICollectionView</span><span class="p">,</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">,</span> <span class="nv">photo</span><span class="p">:</span> <span class="kt">Photo</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">UICollectionViewCell</span><span class="p">?</span> <span class="k">in</span>
    <span class="k">let</span> <span class="nv">cell</span> <span class="o">=</span> <span class="n">collectionView</span><span class="o">.</span><span class="nf">dequeueReusableCell</span><span class="p">(</span><span class="nv">withReuseIdentifier</span><span class="p">:</span> <span class="kt">PhotoCell</span><span class="o">.</span><span class="n">reuseIdentifer</span><span class="p">,</span> <span class="nv">for</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">)</span> <span class="k">as!</span> <span class="kt">PhotoCell</span>
    <span class="k">if</span> <span class="k">let</span> <span class="nv">data</span> <span class="o">=</span> <span class="n">photo</span><span class="o">.</span><span class="n">thumbnailImage</span><span class="p">?</span><span class="o">.</span><span class="n">imageData</span> <span class="k">as</span> <span class="kt">Data</span><span class="p">?,</span> <span class="k">let</span> <span class="nv">image</span> <span class="o">=</span> <span class="kt">UIImage</span><span class="p">(</span><span class="nv">data</span><span class="p">:</span> <span class="n">data</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">cell</span><span class="o">.</span><span class="n">imageView</span><span class="o">.</span><span class="n">image</span> <span class="o">=</span> <span class="n">image</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">cell</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This code replaces the old <code class="language-plaintext highlighter-rouge">collectionView(_:cellForItemAt:)</code> method.</p>

<h3 id="step-3-tying-it-to-core-data">Step 3: Tying it to Core Data</h3>

<p>This is where the magic happens. The <code class="language-plaintext highlighter-rouge">NSFetchedResultsControllerDelegate</code> methods were significantly simplified. Instead of manually managing <code class="language-plaintext highlighter-rouge">BlockOperation</code>s, I just needed to create a new snapshot of the data and apply it to the data source in the <code class="language-plaintext highlighter-rouge">controller(_:didChangeContentWith:)</code> method.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// In PhotoListController+NSFetchedResultsControllerDelegate.swift</span>

<span class="kd">func</span> <span class="nf">controller</span><span class="p">(</span><span class="n">_</span> <span class="nv">controller</span><span class="p">:</span> <span class="kt">NSFetchedResultsController</span><span class="o">&lt;</span><span class="kt">NSFetchRequestResult</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">didChangeContentWith</span> <span class="nv">snapshot</span><span class="p">:</span> <span class="kt">NSDiffableDataSourceSnapshotReference</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">guard</span> <span class="k">let</span> <span class="nv">dataSource</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">dataSource</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>

    <span class="k">var</span> <span class="nv">newSnapshot</span> <span class="o">=</span> <span class="kt">NSDiffableDataSourceSnapshot</span><span class="o">&lt;</span><span class="kt">Section</span><span class="p">,</span> <span class="kt">Photo</span><span class="o">&gt;</span><span class="p">()</span>
    <span class="n">newSnapshot</span><span class="o">.</span><span class="nf">appendSections</span><span class="p">([</span><span class="o">.</span><span class="n">main</span><span class="p">])</span>
    <span class="n">newSnapshot</span><span class="o">.</span><span class="nf">appendItems</span><span class="p">(</span><span class="n">controller</span><span class="o">.</span><span class="n">fetchedObjects</span> <span class="k">as?</span> <span class="p">[</span><span class="kt">Photo</span><span class="p">]</span> <span class="p">??</span> <span class="p">[])</span>

    <span class="n">dataSource</span><span class="o">.</span><span class="nf">apply</span><span class="p">(</span><span class="n">newSnapshot</span><span class="p">,</span> <span class="nv">animatingDifferences</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>

    <span class="c1">// Refresh empty state and other UI elements</span>
    <span class="k">self</span><span class="o">.</span><span class="n">collectionView</span><span class="p">?</span><span class="o">.</span><span class="nf">reloadEmptyDataSet</span><span class="p">()</span>
    <span class="k">self</span><span class="o">.</span><span class="n">editButtonItem</span><span class="o">.</span><span class="n">isEnabled</span> <span class="o">=</span> <span class="n">controller</span><span class="o">.</span><span class="n">fetchedObjects</span><span class="p">?</span><span class="o">.</span><span class="n">count</span> <span class="o">!=</span> <span class="mi">0</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">controller</span><span class="o">.</span><span class="n">fetchedObjects</span> <span class="o">==</span> <span class="kc">nil</span> <span class="o">||</span> <span class="n">controller</span><span class="o">.</span><span class="n">fetchedObjects</span><span class="p">?</span><span class="o">.</span><span class="n">count</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="nf">setEditing</span><span class="p">(</span><span class="kc">false</span><span class="p">,</span> <span class="nv">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>With this change, I was able to remove the old <code class="language-plaintext highlighter-rouge">controllerWillChangeContent</code>, <code class="language-plaintext highlighter-rouge">controller(_:didChange:at:for:newIndexPath:)</code>, and <code class="language-plaintext highlighter-rouge">controllerDidChangeContent</code> methods. A huge simplification!</p>

<h3 id="step-4-the-initial-load">Step 4: The Initial Load</h3>

<p>Finally, I updated the initial data load in <code class="language-plaintext highlighter-rouge">viewDidLoad</code>. After performing the initial fetch, I just needed to call a helper function to update the data source with the fetched data.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// In PhotoListController.swift, inside viewDidLoad()</span>

<span class="c1">// Fetch data</span>
<span class="k">do</span> <span class="p">{</span>
    <span class="k">try</span> <span class="k">self</span><span class="o">.</span><span class="n">fetchedResultsController</span><span class="o">.</span><span class="nf">performFetch</span><span class="p">()</span>
    <span class="nf">updateDataSource</span><span class="p">()</span>
    <span class="k">self</span><span class="o">.</span><span class="n">editButtonItem</span><span class="o">.</span><span class="n">isEnabled</span> <span class="o">=</span> <span class="n">fetchedResultsController</span><span class="o">.</span><span class="n">fetchedObjects</span><span class="p">?</span><span class="o">.</span><span class="n">count</span> <span class="o">!=</span> <span class="mi">0</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
    <span class="nf">fatalError</span><span class="p">(</span><span class="s">"Error fetching photos: </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="c1">// new helper function</span>
<span class="kd">private</span> <span class="kd">func</span> <span class="nf">updateDataSource</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">snapshot</span> <span class="o">=</span> <span class="kt">NSDiffableDataSourceSnapshot</span><span class="o">&lt;</span><span class="kt">Section</span><span class="p">,</span> <span class="kt">Photo</span><span class="o">&gt;</span><span class="p">()</span>
    <span class="n">snapshot</span><span class="o">.</span><span class="nf">appendSections</span><span class="p">([</span><span class="o">.</span><span class="n">main</span><span class="p">])</span>
    <span class="n">snapshot</span><span class="o">.</span><span class="nf">appendItems</span><span class="p">(</span><span class="n">fetchedResultsController</span><span class="o">.</span><span class="n">fetchedObjects</span> <span class="p">??</span> <span class="p">[])</span>
    <span class="n">dataSource</span><span class="o">.</span><span class="nf">apply</span><span class="p">(</span><span class="n">snapshot</span><span class="p">,</span> <span class="nv">animatingDifferences</span><span class="p">:</span> <span class="kc">false</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="the-result-cleaner-simpler-code">The Result: Cleaner, Simpler Code</h2>

<p>After the refactoring, the <code class="language-plaintext highlighter-rouge">PhotoListController</code> is much cleaner and easier to understand. The <code class="language-plaintext highlighter-rouge">NSFetchedResultsControllerDelegate</code> extension is now tiny, and I was able to delete a lot of the old, complex code for managing collection view updates.</p>

<p>The drag-and-drop functionality still works perfectly, but now it’s built on a much more robust and modern foundation.</p>

<h2 id="conclusion">Conclusion</h2>

<p>This refactoring was a great investment. It not only modernized the codebase but also made it more resilient to bugs and helped me eliminate a ton of code and unit tests. If you’re still using the old <code class="language-plaintext highlighter-rouge">NSFetchedResultsControllerDelegate</code> with <code class="language-plaintext highlighter-rouge">UICollectionView</code>, I highly recommend looking into <code class="language-plaintext highlighter-rouge">UICollectionViewDiffableDataSource</code>. It’s a game-changer.</p>

<p>I hope this two-part series has been helpful. If you have any questions or feedback, feel free to reach out!</p>]]></content><author><name></name></author><category term="projects" /><category term="gunvault" /><category term="swift" /><category term="ios" /><category term="core-data" /><summary type="html"><![CDATA[In Part 1 of this series, I walked through the process of adding drag-and-drop functionality to a UICollectionView backed by Core Data. While I got it working, I mentioned that the solution, while functional, wasn’t as modern as it could be. The NSFetchedResultsControllerDelegate implementation, with its manual block operations, felt a bit clunky and error-prone. This post is about that additional refactoring: moving from the traditional NSFetchedResultsControllerDelegate to the more modern UICollectionViewDiffableDataSource.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://villyg.com/assets/images/UIKit.png" /><media:content medium="image" url="http://villyg.com/assets/images/UIKit.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">No More Off-Season: Building a Garage Golf Simulator - Part 2: Launch Monitor and Software</title><link href="http://villyg.com/hobbies/golf/2025/08/09/Building-a-golf-simulator-part-2.html" rel="alternate" type="text/html" title="No More Off-Season: Building a Garage Golf Simulator - Part 2: Launch Monitor and Software" /><published>2025-08-09T21:37:16+00:00</published><updated>2025-08-09T21:37:16+00:00</updated><id>http://villyg.com/hobbies/golf/2025/08/09/Building-a-golf-simulator-part-2</id><content type="html" xml:base="http://villyg.com/hobbies/golf/2025/08/09/Building-a-golf-simulator-part-2.html"><![CDATA[<p>Welcome back to my DIY golf simulator journey!. In <a href="/hobbies/golf/2025/08/05/Building-a-golf-simulator-part-1.html">Part 1</a>, I talked about measuring my space and figuring out the basics of my simulator setup. Now it’s time to choose the heart of the system: the <strong>launch monitor</strong>. This is the technological brain that translates a physical golf swing into a virtual shot. Making the wrong choice here could mean inaccurate data, endless frustration, or a system that just doesn’t work in my space.
<!--more--></p>

<h2 id="my-shortlist">My Shortlist</h2>

<p>I started by comparing some of the most popular options using these criteria:</p>

<ol>
  <li><strong>Space Requirements</strong> – The deal-breaker for my narrow setup.</li>
  <li><strong>Left/Right-Hand Support</strong> – Ideally without physically moving the monitor.</li>
  <li><strong>Cost</strong> – Definitely cannot break the bank.</li>
  <li><strong>Software Compatibility</strong> – Flexibility to try different virtual courses but best without paid subscriptions.</li>
  <li><strong>Club Face Data Accuracy</strong> – Nice to have real measurements, not estimates.</li>
</ol>

<p>Before diving into specs, let me talk briefly about the core technology, because my garage <strong>10’ width</strong> immediately splits the field in half.</p>

<ul>
  <li><strong>Radar (Doppler) Units:</strong> These sit <em>behind</em> the golfer (R10, MLM2PRO, Mevo+). They need a clear view of the ball traveling forward. In a narrow space, I have to use an “offset” hitting position (not centered) to have enough room to swing. This can make it difficult for a radar unit to get a perfectly straight-on view, potentially affecting accuracy. They also require more room depth to track the ball.</li>
  <li><strong>Photometric (Camera) Units:</strong> These sit <em>to the side</em> of the golfer (Square Golf, SkyTrak+, Bushnell LP) or are mounted to the ceiling (Uneekor). They don’t care where I stand in the room, as long as they are positioned correctly next to the ball. This makes them ideal for my offset setup and for spaces with less depth.</li>
</ul>

<h2 id="stage-1-elimination-why-radar-is-a-no-go">Stage 1 Elimination: Why Radar is a No-Go</h2>

<p>My first decision was easy. I have <strong>eliminated all radar-based launch monitors</strong> from consideration. This includes popular units like the Garmin R10, Rapsodo MLM2PRO, and FlightScope Mevo+.</p>

<p>The reason is simple: Radar units need to sit directly behind the ball to work accurately. In my narrow 10’-wide garage, I must use a dual “offset” hitting position (not centered) to have enough room to swing a driver. The radar units seem to work best when centered and although there is some support for offset, there is absolutely no support that I could find for dual offset to accomodate both lefties and righties. In my opinion this is a huge missed opportunity as I really liked the Mevo+ as far as overall package.</p>

<h2 id="stage-2-elimination-the-dream-vs-reality-check">Stage 2 Elimination: The “Dream vs. Reality” Check</h2>

<p>With my search narrowed to camera systems, I looked at the absolute best solution for my space: an overhead launch monitor.</p>

<p>Units like the <strong>Uneekor EYE MINI</strong> (which can be floor-based or overhead) and the <strong>Uneekor QED</strong> or <strong>EYE XO</strong> are the “dream setup” for a space like mine. They mount to the ceiling, perfectly out of the way. Their biggest advantage is that they provide <strong>seamless support for both right- and left-handed players</strong>. There’s no need to move any equipment; friends can switch from righty to lefty on the fly. This was a huge selling point for me.</p>

<p>So why aren’t I buying one? In a word: <strong>cost</strong>.</p>

<p>These professional-grade overhead systems start at around $4,500 and go up to $10,000 and beyond. As amazing as they are, a $5,000+ price tag for the launch monitor alone is outside the budget for my value-focused DIY build. So, with a heavy heart, I had to set the “dream setup” aside. This project is about creating the best possible experience for a realistic price.</p>

<p>This left me with a final showdown between the three most compelling <em>side-placed</em> camera units on the market.</p>

<p><strong>The Final Contenders:</strong></p>
<ul>
  <li><strong>The Budget Disruptor:</strong> Square Golf</li>
  <li><strong>The Established All-Rounder:</strong> SkyTrak+</li>
  <li><strong>The Professional-Grade Benchmark:</strong> Bushnell Launch Pro</li>
</ul>

<h2 id="the-final-showdown-head-to-head-comparison">The Final Showdown: Head-to-Head Comparison</h2>

<p>Each of these units sits beside the golfer, making them a great fit for my space. Now it’s a battle of performance, features, and the total cost of ownership.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Feature</th>
      <th style="text-align: left"><strong>Square Golf</strong></th>
      <th style="text-align: left"><strong>SkyTrak+</strong></th>
      <th style="text-align: left"><strong>Bushnell Launch Pro</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>Technology</strong></td>
      <td style="text-align: left">Photometric (Camera)</td>
      <td style="text-align: left">Photometric + Doppler</td>
      <td style="text-align: left">Photometric (Camera)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Price (Hardware)</strong></td>
      <td style="text-align: left">~$699</td>
      <td style="text-align: left">~$2,995</td>
      <td style="text-align: left">~$1,999 (Ball Data Only)</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Placement</strong></td>
      <td style="text-align: left">Side of Ball</td>
      <td style="text-align: left">Side of Ball</td>
      <td style="text-align: left">Side of Ball</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>L/R Handed Play</strong></td>
      <td style="text-align: left">Requires moving unit</td>
      <td style="text-align: left">Requires moving unit</td>
      <td style="text-align: left">Requires moving unit</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Measured Club Data</strong></td>
      <td style="text-align: left">Yes (Path, Face, AoA)</td>
      <td style="text-align: left">Yes</td>
      <td style="text-align: left"><strong>Requires Gold Sub</strong></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Data Quality</strong></td>
      <td style="text-align: left">Great for the price</td>
      <td style="text-align: left">Excellent All-Around</td>
      <td style="text-align: left">Tour-Level Ball Data</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Subscription Cost</strong></td>
      <td style="text-align: left"><strong>None</strong></td>
      <td style="text-align: left">$129-$249/year</td>
      <td style="text-align: left">$249-$699/year</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>GSPro Compatible</strong></td>
      <td style="text-align: left"><strong>Yes (No extra fee)</strong></td>
      <td style="text-align: left">Yes (with sub)</td>
      <td style="text-align: left"><strong>Yes (with Gold Sub)</strong></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Total 3-Year Cost</strong></td>
      <td style="text-align: left"><strong>~$699</strong></td>
      <td style="text-align: left">~$3,742 (with Game Play sub)</td>
      <td style="text-align: left">~$4,096 (with Gold sub)</td>
    </tr>
  </tbody>
</table>

<h2 id="breaking-it-down-what-really-matters">Breaking It Down: What Really Matters?</h2>

<p>With the “perfect” overhead solution off the table, my decision comes down to which compromises I’m willing to make with a floor unit.</p>

<h3 id="data-quality--accuracy">Data Quality &amp; Accuracy</h3>
<p>All three provide the measured data I want, but to varying degrees of precision.</p>
<ul>
  <li><strong>Bushnell Launch Pro:</strong> The king of raw ball data accuracy. It’s as close to tour-level as you can get, but you have to pay a premium subscription to unlock its club data capabilities.</li>
  <li><strong>SkyTrak+:</strong> A powerful all-around performer with excellent accuracy on both ball and club data right out of the box.</li>
  <li><strong>Square Golf:</strong> The value champion. It provides the essential measured club data I want, and while it might not be as precise as the other two, it’s a massive leap over any estimated data.</li>
</ul>

<p><strong>My Take:</strong> The data from all three is excellent for my needs. The extra precision of the premium units is nice, but I question if it’s worth a 5x to 6x increase in long-term cost.</p>

<h3 id="the-real-cost-software--subscriptions">The Real Cost: Software &amp; Subscriptions</h3>
<p>This is where the choice becomes crystal clear. My goal is to play GSPro software without being chained to expensive annual fees just to connect.</p>
<ul>
  <li><strong>Bushnell Launch Pro &amp; SkyTrak+:</strong> These are built on the subscription model. To get the features I want and connect to GSPro, I’d be paying hundreds of dollars every year, forever. Over just a few years, the cost of the software subscription would be more than the entire price of the Square Golf unit.</li>
  <li><strong>Square Golf:</strong> The hero of the DIY builder. It has a policy of <strong>no extra fees</strong> to connect to GSPro. The price on the box is the real price to get up and running with the best community software. I can also go and buy Awesome Golf for couple hundred bucks and play for quite a while before I even get a GSPro subscription.</li>
</ul>

<p><strong>My Take:</strong> This isn’t just a feature; it’s a philosophy. The Square Golf model respects the customer, while the others feel designed to maximize recurring revenue.</p>

<h2 id="the-final-decision">The Final Decision</h2>

<p>My decision-making funnel was clear:</p>
<ol>
  <li>Space limitations eliminated all radar units.</li>
  <li>Budget limitations eliminated the “dream” overhead units.</li>
  <li>This left a three-way battle of floor units, where the long-term cost became the deciding factor.</li>
</ol>

<p><strong>My Decision:</strong> I ordered the <strong><a href="https://www.squaregolf.com/launchmonitor">Square Golf launch monitor</a></strong>.</p>

<p><img src="/assets/images/2025-08-09-Building-a-golf-simulator-part-2/Square_Golf_2.jpg" alt="Square Golf" /></p>

<p>After considering the absolute best-case scenario (an overhead unit) and ruling it out on price, the choice became easy. The Square Golf provides the essential features of the far more expensive camera units — a side placement perfect for my space and measured club data for real game improvement — but it does so without the punitive subscription model.</p>

<p>The ability to connect to GSPro for free makes the total cost of ownership incredibly low and aligns perfectly with the spirit of a DIY project. I will have to live with the minor inconvenience of moving the unit for my righty friends, but that is a tiny price to pay to save literally thousands of dollars over the next few years. That savings is what will allow me to build a truly awesome simulator enclosure without breaking the bank.</p>

<h2 id="whats-next">What’s Next?</h2>

<p>With the brains of my operation chosen after a long and thorough search, it’s time to build its home. In <a href="/hobbies/golf/2025/10/09/Building-a-golf-simulator_part-3.html"><strong>Part 3: Asembling the enclosure</strong></a>, I’ll take you through the entire process of designing and building the enclosure—from constructing the frame to selecting the perfect impact screen.</p>]]></content><author><name></name></author><category term="hobbies" /><category term="golf" /><category term="golf" /><summary type="html"><![CDATA[Welcome back to my DIY golf simulator journey!. In Part 1, I talked about measuring my space and figuring out the basics of my simulator setup. Now it’s time to choose the heart of the system: the launch monitor. This is the technological brain that translates a physical golf swing into a virtual shot. Making the wrong choice here could mean inaccurate data, endless frustration, or a system that just doesn’t work in my space.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://villyg.com/assets/images/2025-08-09-Building-a-golf-simulator-part-2/mainimage.jpg" /><media:content medium="image" url="http://villyg.com/assets/images/2025-08-09-Building-a-golf-simulator-part-2/mainimage.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">No More Off-Season: Building a Garage Golf Simulator - Part 1: Introduction</title><link href="http://villyg.com/hobbies/golf/2025/08/05/Building-a-golf-simulator-part-1.html" rel="alternate" type="text/html" title="No More Off-Season: Building a Garage Golf Simulator - Part 1: Introduction" /><published>2025-08-05T21:37:16+00:00</published><updated>2025-08-05T21:37:16+00:00</updated><id>http://villyg.com/hobbies/golf/2025/08/05/Building-a-golf-simulator-part-1</id><content type="html" xml:base="http://villyg.com/hobbies/golf/2025/08/05/Building-a-golf-simulator-part-1.html"><![CDATA[<p>It’s a dream many golfers share: the ability to play a round at Pebble Beach or St Andrews at a moment’s notice, rain or shine, day or night. No tee times, no weather delays, just pure golf. For a while now, I’ve dreamed of having my own golf simulator. This year, I’m finally turning that dream into a reality, and I’m taking you along for my entire journey.
<!--more-->
Welcome to the first post in my multi-part series on building a DIY golf simulator from scratch.</p>

<figure>
    <img src="/assets/images/2025-08-05-Building-a-golf-simulator-part-1/AI.png" alt="AI rendering" />
    <figcaption>AI rendering by Gemini 2.5 Pro</figcaption>
</figure>

<p>This isn’t just about buying a kit and plugging it in. This is about my research, my planning, the inevitable challenges, and the satisfaction of hitting that first perfect shot in a space I built myself. In this series, I’ll cover everything from choosing the right launch monitor to building the enclosure and dialing in the software.</p>

<p align="center"><b>But before I buy a single piece of equipment, it all starts with a plan.</b></p>

<p>Building a golf simulator is a project of passion, but a successful one is built on a foundation of careful planning. Rushing into purchases without a clear design is the fastest way to blow a budget and still end up with a setup that doesn’t work. So, in this first post, I’m focusing on the blueprint and the absolute non-negotiables. These are the fundamentals I have to address for myself before I even think about adding a single item to my shopping cart.</p>

<h2 id="how-much-room-do-i-really-have">How much room do I <em>really</em> have?</h2>

<p>This is the number one, non-negotiable starting point. My project will be built in a standard single-car garage stall. I’m lucky to have a spare bay in my tandem garage, but it comes with its own set of very specific dimensions.</p>

<p><img src="/assets/images/2025-08-05-Building-a-golf-simulator-part-1/Photo_1.jpeg" alt="Garage photo" /></p>

<p>After breaking out the tape measure, here’s what I’m working with and what it means for my build.</p>

<p><img src="/assets/images/2025-08-05-Building-a-golf-simulator-part-1/Floor_plan_1.png" alt="Floor plan" /></p>

<p align="center"><b>My dimensions are: 10' width × 12′ height × 21` depth.</b></p>

<p>Let’s break down what each of these means:</p>

<ul>
  <li>
    <p><strong>Height (12′)</strong>: This is a huge win. My ceiling height is a fantastic 12′. In the world of DIY golf simulators, this is a major luxury. It means I have zero concerns about even my tallest friends swinging a driver and hitting the ceiling. This height also gives me a ton of flexibility for mounting my projector high up and out of the way.</p>
  </li>
  <li>
    <p><strong>Depth (20′)</strong>: This is another strong point for my space. With 20′ of depth, I have plenty of room to create a safe and functional layout. I can have a 1’ behind the impact screen for damage control and pretty much any distance I need for the tee spot or the launch monitor.</p>
  </li>
  <li>
    <p><strong>Width (10′)</strong>: Now for my biggest constraint. The width of the garage stall is exactly 10′. This is widely considered the minimum viable width for a simulator, and it forces some very important decisions right from the start. With only 10′ from wall to wall, I can’t realistically center the hitting area. There simply wouldn’t be enough room for a full, confident swing on both sides of the ball without fear of hitting the side walls. This immediately confirmed that <strong>I must build an “offset” simulator</strong>. I’ll have to push the hitting area to one side of the garage stall to maximize the swing space for a single player orientation. This realization leads directly to the next big question…</p>
  </li>
</ul>

<h2 id="righty-lefty-or-both">Righty, lefty, or both?</h2>

<p>This might seem like a small detail, but it has a huge impact on the project since I am <strong>a lefty</strong> and almost all of my friends are <strong>righties</strong>. 
Based on the width constraint above, I have to offset the tee. However, if I want to have friends and family over - I’ll likely have both right- and left-handed players. This means I must actually have <strong>dual offset</strong> and a tee spot on each side. It also means <strong>I will need a launch monitor that can accomdate the dual offset</strong>.</p>

<h2 id="lets-talk-budget">Let’s talk budget</h2>

<p>A golf simulator can cost anywhere from a few thousand dollars to the price of a luxury car. It was crucial for me to set a realistic budget from the start.</p>

<p>I’ve broken my budget into the following core components:</p>
<ol>
  <li><strong>Launch monitor &amp; software:</strong> The brains of the operation.</li>
  <li><strong>Enclosure &amp; screen:</strong> The physical structure.</li>
  <li><strong>Projector &amp; PC:</strong> The visuals.</li>
  <li><strong>Mat &amp; flooring:</strong> The foundation.</li>
  <li><strong>Climate, lighting and seating:</strong> Creature comfort.</li>
  <li><strong>Final touches</strong></li>
</ol>

<p>My budget for this is extremely low so I’m hoping to allocate it carefully across these categories and to provide a detailed breakdown at the end. I know that the dual offset requirement will affect the cost so if it becomes too much - it might be time for early eject.</p>

<h2 id="whats-next">What’s next?</h2>

<p>Now that I have a blueprint, the real fun begins. In the next post, I’ll dive deep into <a href="/hobbies/golf/2025/08/09/Building-a-golf-simulator-part-2.html"><strong>Part 2: Launch Monitor &amp; Software.</strong></a> I decided to focus on this first because given the non-negotiables above, there is a high probability that I either price myself out or even worse - not be able to find one at all.</p>

<p>This is going to be an incredible project. I hope you’ll join me on this journey, and feel free to drop your own questions and plans in the comments below!</p>]]></content><author><name></name></author><category term="hobbies" /><category term="golf" /><category term="golf" /><summary type="html"><![CDATA[It’s a dream many golfers share: the ability to play a round at Pebble Beach or St Andrews at a moment’s notice, rain or shine, day or night. No tee times, no weather delays, just pure golf. For a while now, I’ve dreamed of having my own golf simulator. This year, I’m finally turning that dream into a reality, and I’m taking you along for my entire journey.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://villyg.com/assets/images/2025-08-05-Building-a-golf-simulator-part-1/mainimage.jpg" /><media:content medium="image" url="http://villyg.com/assets/images/2025-08-05-Building-a-golf-simulator-part-1/mainimage.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Implementing Drag and Drop in UICollectionView and Core Data – Part 1</title><link href="http://villyg.com/projects/gunvault/2025/08/04/Implementing-Drag-and-Drop-in-UICollectionView-and-Core-Data-Part-1.html" rel="alternate" type="text/html" title="Implementing Drag and Drop in UICollectionView and Core Data – Part 1" /><published>2025-08-04T22:30:00+00:00</published><updated>2025-08-04T22:30:00+00:00</updated><id>http://villyg.com/projects/gunvault/2025/08/04/Implementing-Drag-and-Drop-in-UICollectionView-and-Core-Data%E2%80%93Part-1</id><content type="html" xml:base="http://villyg.com/projects/gunvault/2025/08/04/Implementing-Drag-and-Drop-in-UICollectionView-and-Core-Data-Part-1.html"><![CDATA[<p>One of the feature requests I received was to allow users to rearrange gun photos in a custom order. Since I was planning other changes to the photos module, I decided to tackle this request first as it would lay a solid foundation for future updates. What seemed like a simple task turned out to be a bit more complex than expected, so I decided to blog about it.</p>

<!--more-->

<ul>
  <li><a href="#background">Background</a></li>
  <li><a href="#plan">Plan</a>
    <ul>
      <li><a href="#step-1-updating-the-model">Step 1: Updating the model</a></li>
      <li><a href="#step-2-adding-drag-and-drop-support">Step 2: Adding Drag-and-Drop Support</a></li>
      <li><a href="#step-3-assigning-displayorder-to-new-photos">Step 3: Assigning <code class="language-plaintext highlighter-rouge">displayOrder</code> to New Photos</a></li>
    </ul>
  </li>
  <li><a href="#problem">Problem</a></li>
  <li><a href="#root-cause-analysis">Root cause analysis</a></li>
  <li><a href="#solution">Solution</a></li>
  <li><a href="#conclusion">Conclusion</a>
<!-- more --></li>
</ul>

<h2 id="background">Background</h2>

<p>Since this is a Core Data-enabled application, the <code class="language-plaintext highlighter-rouge">PhotoListController</code> is a <code class="language-plaintext highlighter-rouge">UICollectionViewController</code> and also acts as a delegate for an <code class="language-plaintext highlighter-rouge">NSFetchedResultsController</code>. It retrieves photos and displays them in a 3-column grid using <code class="language-plaintext highlighter-rouge">UICollectionViewDelegateFlowLayout</code>. By default, photos are sorted by the date/time they were captured or imported. The controller also handles capture/import, selection, deletion, and exporting, all of which needed to be considered.</p>

<p>The controller’s structure is typical:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">PhotoListController</span><span class="p">:</span> <span class="kt">UICollectionViewController</span> <span class="p">{</span>
    <span class="kd">lazy</span> <span class="k">var</span> <span class="nv">fetchedResultsController</span><span class="p">:</span> <span class="kt">NSFetchedResultsController</span><span class="o">&lt;</span><span class="kt">Photo</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">predicate</span> <span class="o">=</span> <span class="kt">NSPredicate</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="s">"gun == %@"</span><span class="p">,</span> <span class="k">self</span><span class="o">.</span><span class="n">gun</span><span class="p">)</span>
        <span class="k">let</span> <span class="nv">sortDescriptor</span> <span class="o">=</span> <span class="kt">NSSortDescriptor</span><span class="p">(</span><span class="nv">key</span><span class="p">:</span> <span class="s">"createdOn"</span><span class="p">,</span> <span class="nv">ascending</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
        <span class="k">let</span> <span class="nv">fReq</span><span class="p">:</span> <span class="kt">NSFetchRequest</span><span class="o">&lt;</span><span class="kt">Photo</span><span class="o">&gt;</span> <span class="o">=</span> <span class="kt">Photo</span><span class="o">.</span><span class="nf">fetchRequest</span><span class="p">()</span>
        <span class="n">fReq</span><span class="o">.</span><span class="n">sortDescriptors</span> <span class="o">=</span> <span class="p">[</span><span class="n">sortDescriptor</span><span class="p">]</span>
        <span class="n">fReq</span><span class="o">.</span><span class="n">predicate</span> <span class="o">=</span> <span class="n">predicate</span>
        <span class="k">let</span> <span class="nv">frc</span> <span class="o">=</span> <span class="kt">NSFetchedResultsController</span><span class="p">(</span><span class="nv">fetchRequest</span><span class="p">:</span> <span class="n">fReq</span><span class="p">,</span> <span class="nv">managedObjectContext</span><span class="p">:</span> <span class="k">self</span><span class="o">.</span><span class="n">managedObjectContext</span><span class="p">,</span> <span class="nv">sectionNameKeyPath</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="nv">cacheName</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">frc</span>
    <span class="p">}()</span>
    <span class="c1">// More code here...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">NSFetchedResultsControllerDelegate</code> implementation uses block operations and branching logic for different update types:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">PhotoListController</span><span class="p">:</span> <span class="kt">NSFetchedResultsControllerDelegate</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">controllerWillChangeContent</span><span class="p">(</span><span class="n">_</span> <span class="nv">controller</span><span class="p">:</span> <span class="kt">NSFetchedResultsController</span><span class="o">&lt;</span><span class="kt">NSFetchRequestResult</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="n">blockOperations</span><span class="o">.</span><span class="nf">removeAll</span><span class="p">(</span><span class="nv">keepingCapacity</span><span class="p">:</span> <span class="kc">false</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="c1">// ...section change handling...</span>
    <span class="kd">func</span> <span class="nf">controller</span><span class="p">(</span><span class="n">_</span> <span class="nv">controller</span><span class="p">:</span> <span class="kt">NSFetchedResultsController</span><span class="o">&lt;</span><span class="kt">NSFetchRequestResult</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">didChange</span> <span class="nv">anObject</span><span class="p">:</span> <span class="kt">Any</span><span class="p">,</span> <span class="n">at</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">?,</span> <span class="k">for</span> <span class="nv">type</span><span class="p">:</span> <span class="kt">NSFetchedResultsChangeType</span><span class="p">,</span> <span class="nv">newIndexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">?)</span> <span class="p">{</span>
        <span class="c1">// ...insert, update, move, delete logic...</span>
    <span class="p">}</span>
    <span class="kd">func</span> <span class="nf">controllerDidChangeContent</span><span class="p">(</span><span class="n">_</span> <span class="nv">controller</span><span class="p">:</span> <span class="kt">NSFetchedResultsController</span><span class="o">&lt;</span><span class="kt">NSFetchRequestResult</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">collectionView</span><span class="o">!.</span><span class="nf">performBatchUpdates</span><span class="p">({</span>
            <span class="k">for</span> <span class="n">operation</span> <span class="k">in</span> <span class="k">self</span><span class="o">.</span><span class="n">blockOperations</span> <span class="p">{</span>
                <span class="n">operation</span><span class="o">.</span><span class="nf">start</span><span class="p">()</span>
            <span class="p">}</span>
        <span class="p">},</span> <span class="nv">completion</span><span class="p">:</span> <span class="p">{</span> <span class="n">_</span> <span class="k">in</span>
            <span class="k">self</span><span class="o">.</span><span class="n">blockOperations</span><span class="o">.</span><span class="nf">removeAll</span><span class="p">(</span><span class="nv">keepingCapacity</span><span class="p">:</span> <span class="kc">false</span><span class="p">)</span>
            <span class="c1">// Additional logic</span>
        <span class="p">})</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The model is straightforward, with UUID support from the start:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@objc</span><span class="p">(</span><span class="kt">Photo</span><span class="p">)</span>
<span class="kd">class</span> <span class="kt">Photo</span><span class="p">:</span> <span class="kt">NSManagedObject</span> <span class="p">{}</span>

<span class="kd">extension</span> <span class="kt">Photo</span> <span class="p">{</span>
    <span class="kd">@nonobjc</span> <span class="kd">class</span> <span class="kd">func</span> <span class="nf">fetchRequest</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">NSFetchRequest</span><span class="o">&lt;</span><span class="kt">Photo</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">return</span> <span class="kt">NSFetchRequest</span><span class="o">&lt;</span><span class="kt">Photo</span><span class="o">&gt;</span><span class="p">(</span><span class="nv">entityName</span><span class="p">:</span> <span class="s">"Photo"</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="k">override</span> <span class="kd">func</span> <span class="nf">awakeFromInsert</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">super</span><span class="o">.</span><span class="nf">awakeFromInsert</span><span class="p">()</span>
        <span class="n">uniqueId</span> <span class="o">=</span> <span class="kt">NSUUID</span><span class="p">()</span><span class="o">.</span><span class="n">uuidString</span>
        <span class="n">createdOn</span> <span class="o">=</span> <span class="kt">NSDate</span><span class="p">()</span>
    <span class="p">}</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">uniqueId</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">createdOn</span><span class="p">:</span> <span class="kt">NSDate</span><span class="p">?</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">gun</span><span class="p">:</span> <span class="kt">Gun</span><span class="p">?</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">thumbnailImage</span><span class="p">:</span> <span class="kt">PhotoImageThumbnail</span><span class="p">?</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">fullSizeImage</span><span class="p">:</span> <span class="kt">PhotoImageFullSize</span><span class="p">?</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="plan">Plan</h2>

<p>My plan was to add a custom order property to the model, enable drag-and-drop in the <code class="language-plaintext highlighter-rouge">UICollectionViewController</code>, and persist the new order in Core Data. Any changes would be broadcast using the existing plumbing between Core Data and the various <code class="language-plaintext highlighter-rouge">NSFetchedResultsController</code> delegates.</p>

<h3 id="step-1-updating-the-model">Step 1: Updating the model</h3>

<p>To persist custom order, I introduced a new attribute <code class="language-plaintext highlighter-rouge">displayOrder</code>:</p>

<ol>
  <li><strong>Update the model version:</strong> I generated a new model version for seamless migration.</li>
  <li><strong>Update the Photo entity:</strong> Added a new attribute <code class="language-plaintext highlighter-rouge">displayOrder</code> (type: Integer 16) to the <code class="language-plaintext highlighter-rouge">Photo</code> entity.</li>
  <li><strong>Update the Swift class:</strong> Added the <code class="language-plaintext highlighter-rouge">displayOrder</code> property to the Swift class.</li>
</ol>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">Photo</span> <span class="p">{</span>
    <span class="kd">@nonobjc</span> <span class="kd">class</span> <span class="kd">func</span> <span class="nf">fetchRequest</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">NSFetchRequest</span><span class="o">&lt;</span><span class="kt">Photo</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">return</span> <span class="kt">NSFetchRequest</span><span class="o">&lt;</span><span class="kt">Photo</span><span class="o">&gt;</span><span class="p">(</span><span class="nv">entityName</span><span class="p">:</span> <span class="s">"Photo"</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="k">override</span> <span class="kd">func</span> <span class="nf">awakeFromInsert</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">super</span><span class="o">.</span><span class="nf">awakeFromInsert</span><span class="p">()</span>
        <span class="n">uniqueId</span> <span class="o">=</span> <span class="kt">NSUUID</span><span class="p">()</span><span class="o">.</span><span class="n">uuidString</span>
        <span class="n">createdOn</span> <span class="o">=</span> <span class="kt">NSDate</span><span class="p">()</span>
    <span class="p">}</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">uniqueId</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">createdOn</span><span class="p">:</span> <span class="kt">NSDate</span><span class="p">?</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">displayOrder</span><span class="p">:</span> <span class="kt">Int16</span> <span class="c1">// New attribute</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">gun</span><span class="p">:</span> <span class="kt">Gun</span><span class="p">?</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">thumbnailImage</span><span class="p">:</span> <span class="kt">PhotoImageThumbnail</span><span class="p">?</span>
    <span class="kd">@NSManaged</span> <span class="k">var</span> <span class="nv">fullSizeImage</span><span class="p">:</span> <span class="kt">PhotoImageFullSize</span><span class="p">?</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="step-2-adding-drag-and-drop-support">Step 2: Adding Drag-and-Drop Support</h3>

<p>To enable reordering, I modified the controller to implement <code class="language-plaintext highlighter-rouge">UICollectionViewDragDelegate</code> and <code class="language-plaintext highlighter-rouge">UICollectionViewDropDelegate</code>:</p>

<p><strong>Enable Drag and Drop:</strong> In <code class="language-plaintext highlighter-rouge">viewDidLoad</code>, enable drag interactions and set delegates.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">override</span> <span class="kd">func</span> <span class="nf">viewDidLoad</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">super</span><span class="o">.</span><span class="nf">viewDidLoad</span><span class="p">()</span>
    <span class="c1">// ...existing setup...</span>
    <span class="n">collectionView</span><span class="o">.</span><span class="n">dragInteractionEnabled</span> <span class="o">=</span> <span class="kc">true</span>
    <span class="n">collectionView</span><span class="o">.</span><span class="n">dragDelegate</span> <span class="o">=</span> <span class="k">self</span>
    <span class="n">collectionView</span><span class="o">.</span><span class="n">dropDelegate</span> <span class="o">=</span> <span class="k">self</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Update Fetch Request:</strong> Sort photos by <code class="language-plaintext highlighter-rouge">displayOrder</code>.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">lazy</span> <span class="k">var</span> <span class="nv">fetchedResultsController</span><span class="p">:</span> <span class="kt">NSFetchedResultsController</span><span class="o">&lt;</span><span class="kt">Photo</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">fetchRequest</span><span class="p">:</span> <span class="kt">NSFetchRequest</span><span class="o">&lt;</span><span class="kt">Photo</span><span class="o">&gt;</span> <span class="o">=</span> <span class="kt">Photo</span><span class="o">.</span><span class="nf">fetchRequest</span><span class="p">()</span>
    <span class="n">fetchRequest</span><span class="o">.</span><span class="n">predicate</span> <span class="o">=</span> <span class="kt">NSPredicate</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="s">"gun = %@"</span><span class="p">,</span> <span class="k">self</span><span class="o">.</span><span class="n">gun</span><span class="p">)</span>
    <span class="k">let</span> <span class="nv">sortDescriptor</span> <span class="o">=</span> <span class="kt">NSSortDescriptor</span><span class="p">(</span><span class="nv">key</span><span class="p">:</span> <span class="s">"displayOrder"</span><span class="p">,</span> <span class="nv">ascending</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
    <span class="n">fetchRequest</span><span class="o">.</span><span class="n">sortDescriptors</span> <span class="o">=</span> <span class="p">[</span><span class="n">sortDescriptor</span><span class="p">]</span>
    <span class="c1">// ...rest of initialization...</span>
    <span class="k">return</span> <span class="n">frc</span>
<span class="p">}()</span>
</code></pre></div></div>

<p><strong>Implement Drag Delegate:</strong> Specify draggable items.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">PhotoListController</span><span class="p">:</span> <span class="kt">UICollectionViewDragDelegate</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">collectionView</span><span class="p">(</span><span class="n">_</span> <span class="nv">collectionView</span><span class="p">:</span> <span class="kt">UICollectionView</span><span class="p">,</span> <span class="n">itemsForBeginning</span> <span class="nv">session</span><span class="p">:</span> <span class="kt">UIDragSession</span><span class="p">,</span> <span class="n">at</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="p">[</span><span class="kt">UIDragItem</span><span class="p">]</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">photo</span> <span class="o">=</span> <span class="n">fetchedResultsController</span><span class="o">.</span><span class="nf">object</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">)</span>
        <span class="k">let</span> <span class="nv">itemProvider</span> <span class="o">=</span> <span class="kt">NSItemProvider</span><span class="p">(</span><span class="nv">object</span><span class="p">:</span> <span class="n">photo</span><span class="o">.</span><span class="n">uniqueId</span><span class="o">!</span> <span class="k">as</span> <span class="kt">NSString</span><span class="p">)</span>
        <span class="k">let</span> <span class="nv">dragItem</span> <span class="o">=</span> <span class="kt">UIDragItem</span><span class="p">(</span><span class="nv">itemProvider</span><span class="p">:</span> <span class="n">itemProvider</span><span class="p">)</span>
        <span class="n">dragItem</span><span class="o">.</span><span class="n">localObject</span> <span class="o">=</span> <span class="n">photo</span>
        <span class="k">return</span> <span class="p">[</span><span class="n">dragItem</span><span class="p">]</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Implement Drop Delegate:</strong> Handle drop and persist new order.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">PhotoListController</span><span class="p">:</span> <span class="kt">UICollectionViewDropDelegate</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">collectionView</span><span class="p">(</span><span class="n">_</span> <span class="nv">collectionView</span><span class="p">:</span> <span class="kt">UICollectionView</span><span class="p">,</span> <span class="n">canHandle</span> <span class="nv">session</span><span class="p">:</span> <span class="kt">UIDropSession</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Bool</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">session</span><span class="o">.</span><span class="nf">canLoadObjects</span><span class="p">(</span><span class="nv">ofClass</span><span class="p">:</span> <span class="kt">NSString</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="kd">func</span> <span class="nf">collectionView</span><span class="p">(</span><span class="n">_</span> <span class="nv">collectionView</span><span class="p">:</span> <span class="kt">UICollectionView</span><span class="p">,</span> <span class="n">dropSessionDidUpdate</span> <span class="nv">session</span><span class="p">:</span> <span class="kt">UIDropSession</span><span class="p">,</span> <span class="n">withDestinationIndexPath</span> <span class="nv">destinationIndexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">?)</span> <span class="o">-&gt;</span> <span class="kt">UICollectionViewDropProposal</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">collectionView</span><span class="o">.</span><span class="n">hasActiveDrag</span> <span class="p">{</span>
            <span class="k">return</span> <span class="kt">UICollectionViewDropProposal</span><span class="p">(</span><span class="nv">operation</span><span class="p">:</span> <span class="o">.</span><span class="n">move</span><span class="p">,</span> <span class="nv">intent</span><span class="p">:</span> <span class="o">.</span><span class="n">insertAtDestinationIndexPath</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="kt">UICollectionViewDropProposal</span><span class="p">(</span><span class="nv">operation</span><span class="p">:</span> <span class="o">.</span><span class="n">forbidden</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="kd">func</span> <span class="nf">collectionView</span><span class="p">(</span><span class="n">_</span> <span class="nv">collectionView</span><span class="p">:</span> <span class="kt">UICollectionView</span><span class="p">,</span> <span class="n">performDropWith</span> <span class="nv">coordinator</span><span class="p">:</span> <span class="kt">UICollectionViewDropCoordinator</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">guard</span> <span class="k">let</span> <span class="nv">destinationIndexPath</span> <span class="o">=</span> <span class="n">coordinator</span><span class="o">.</span><span class="n">destinationIndexPath</span><span class="p">,</span>
              <span class="k">let</span> <span class="nv">sourceIndexPath</span> <span class="o">=</span> <span class="n">coordinator</span><span class="o">.</span><span class="n">items</span><span class="o">.</span><span class="n">first</span><span class="p">?</span><span class="o">.</span><span class="n">sourceIndexPath</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
        <span class="k">guard</span> <span class="k">var</span> <span class="nv">photos</span> <span class="o">=</span> <span class="n">fetchedResultsController</span><span class="o">.</span><span class="n">fetchedObjects</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
        <span class="k">let</span> <span class="nv">photoToMove</span> <span class="o">=</span> <span class="n">photos</span><span class="o">.</span><span class="nf">remove</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="n">sourceIndexPath</span><span class="o">.</span><span class="n">item</span><span class="p">)</span>
        <span class="n">photos</span><span class="o">.</span><span class="nf">insert</span><span class="p">(</span><span class="n">photoToMove</span><span class="p">,</span> <span class="nv">at</span><span class="p">:</span> <span class="n">destinationIndexPath</span><span class="o">.</span><span class="n">item</span><span class="p">)</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="n">photo</span><span class="p">)</span> <span class="k">in</span> <span class="n">photos</span><span class="o">.</span><span class="nf">enumerated</span><span class="p">()</span> <span class="p">{</span>
            <span class="n">photo</span><span class="o">.</span><span class="n">displayOrder</span> <span class="o">=</span> <span class="kt">Int16</span><span class="p">(</span><span class="n">index</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="k">do</span> <span class="p">{</span>
            <span class="k">try</span> <span class="k">self</span><span class="o">.</span><span class="n">managedObjectContext</span><span class="o">.</span><span class="nf">save</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">"Failed to save photo order: </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></div></div>

<h3 id="step-3-assigning-displayorder-to-new-photos">Step 3: Assigning <code class="language-plaintext highlighter-rouge">displayOrder</code> to New Photos</h3>

<p>I updated the capture/import logic to assign <code class="language-plaintext highlighter-rouge">displayOrder</code> automatically:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">imagePickerController</span><span class="p">(</span><span class="n">_</span> <span class="nv">picker</span><span class="p">:</span> <span class="kt">UIImagePickerController</span><span class="p">,</span> <span class="n">didFinishPickingMediaWithInfo</span> <span class="nv">info</span><span class="p">:</span> <span class="p">[</span><span class="kt">UIImagePickerController</span><span class="o">.</span><span class="kt">InfoKey</span> <span class="p">:</span> <span class="kt">Any</span><span class="p">])</span> <span class="p">{</span>
    <span class="c1">// ...existing code to get image...</span>
    <span class="k">let</span> <span class="nv">photo</span> <span class="o">=</span> <span class="kt">Photo</span><span class="p">(</span><span class="nv">context</span><span class="p">:</span> <span class="n">coreDataStack</span><span class="o">.</span><span class="n">managedContext</span><span class="p">)</span>
    <span class="n">photo</span><span class="o">.</span><span class="n">uuid</span> <span class="o">=</span> <span class="kt">UUID</span><span class="p">()</span>
    <span class="n">photo</span><span class="o">.</span><span class="n">createdAt</span> <span class="o">=</span> <span class="kt">Date</span><span class="p">()</span>
    <span class="n">photo</span><span class="o">.</span><span class="n">gun</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">gun</span>
    <span class="c1">// Assign the next display order</span>
    <span class="k">let</span> <span class="nv">currentCount</span> <span class="o">=</span> <span class="n">fetchedResultsController</span><span class="o">.</span><span class="n">fetchedObjects</span><span class="p">?</span><span class="o">.</span><span class="n">count</span> <span class="p">??</span> <span class="mi">0</span>
    <span class="n">photo</span><span class="o">.</span><span class="n">displayOrder</span> <span class="o">=</span> <span class="kt">Int16</span><span class="p">(</span><span class="n">currentCount</span><span class="p">)</span>
    <span class="c1">// ...rest of the method to set images and save...</span>
    <span class="nf">dismiss</span><span class="p">(</span><span class="nv">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="problem">Problem</h2>

<p>After implementing drag-and-drop, the app crashed with the dreaded <code class="language-plaintext highlighter-rouge">attempt to perform an insert and a move to the same index path</code> error.</p>

<h2 id="root-cause-analysis">Root Cause Analysis</h2>

<p>The issue stemmed from how <code class="language-plaintext highlighter-rouge">NSFetchedResultsControllerDelegate</code> interprets changes. If a <code class="language-plaintext highlighter-rouge">move</code> is treated as a <code class="language-plaintext highlighter-rouge">delete</code> and an <code class="language-plaintext highlighter-rouge">insert</code>, and the <code class="language-plaintext highlighter-rouge">insert</code> targets the same index path as another independent insert, a conflict occurs.</p>

<h2 id="solution">Solution</h2>

<p>I modified the logic to ensure that inserts, deletes, and move targets are distinct within a batch update. Instead of using the combined <code class="language-plaintext highlighter-rouge">move</code> operation, I replaced it with a <code class="language-plaintext highlighter-rouge">delete</code> at the old index path and an <code class="language-plaintext highlighter-rouge">insert</code> at the new index path:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">PhotoListController</span><span class="p">:</span> <span class="kt">NSFetchedResultsControllerDelegate</span> <span class="p">{</span>
    <span class="c1">// ...other logic...</span>
    <span class="kd">func</span> <span class="nf">controller</span><span class="p">(</span><span class="n">_</span> <span class="nv">controller</span><span class="p">:</span> <span class="kt">NSFetchedResultsController</span><span class="o">&lt;</span><span class="kt">NSFetchRequestResult</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">didChange</span> <span class="nv">anObject</span><span class="p">:</span> <span class="kt">Any</span><span class="p">,</span> <span class="n">at</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">?,</span> <span class="k">for</span> <span class="nv">type</span><span class="p">:</span> <span class="kt">NSFetchedResultsChangeType</span><span class="p">,</span> <span class="nv">newIndexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">?)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">type</span> <span class="o">==</span> <span class="o">.</span><span class="n">insert</span> <span class="p">{</span>
            <span class="n">blockOperations</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="kt">BlockOperation</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="k">in</span>
                <span class="k">self</span><span class="p">?</span><span class="o">.</span><span class="n">collectionView</span><span class="p">?</span><span class="o">.</span><span class="nf">insertItems</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="p">[</span><span class="n">newIndexPath</span><span class="o">!</span><span class="p">])</span>
            <span class="p">})</span>
        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">type</span> <span class="o">==</span> <span class="o">.</span><span class="n">update</span> <span class="p">{</span>
            <span class="n">blockOperations</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="kt">BlockOperation</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="k">in</span>
                <span class="k">self</span><span class="p">?</span><span class="o">.</span><span class="n">collectionView</span><span class="p">?</span><span class="o">.</span><span class="nf">reloadItems</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="p">[</span><span class="n">indexPath</span><span class="o">!</span><span class="p">])</span>
            <span class="p">})</span>
        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">type</span> <span class="o">==</span> <span class="o">.</span><span class="n">move</span> <span class="p">{</span>
            <span class="n">blockOperations</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="kt">BlockOperation</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="k">in</span>
                <span class="k">guard</span> <span class="k">let</span> <span class="nv">indexPath</span> <span class="o">=</span> <span class="n">indexPath</span><span class="p">,</span> <span class="k">let</span> <span class="nv">newIndexPath</span> <span class="o">=</span> <span class="n">newIndexPath</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
                <span class="k">self</span><span class="p">?</span><span class="o">.</span><span class="n">collectionView</span><span class="p">?</span><span class="o">.</span><span class="nf">deleteItems</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="p">[</span><span class="n">indexPath</span><span class="p">])</span>
                <span class="k">self</span><span class="p">?</span><span class="o">.</span><span class="n">collectionView</span><span class="p">?</span><span class="o">.</span><span class="nf">insertItems</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="p">[</span><span class="n">newIndexPath</span><span class="p">])</span>
            <span class="p">})</span>
        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">type</span> <span class="o">==</span> <span class="o">.</span><span class="n">delete</span> <span class="p">{</span>
            <span class="n">blockOperations</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="kt">BlockOperation</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="k">in</span>
                <span class="k">self</span><span class="p">?</span><span class="o">.</span><span class="n">collectionView</span><span class="p">?</span><span class="o">.</span><span class="nf">deleteItems</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="p">[</span><span class="n">indexPath</span><span class="o">!</span><span class="p">])</span>
            <span class="p">})</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="conclusion">Conclusion</h2>

<p>With these changes, I had a shippable version. Despite a lengthy debugging session, the refactor was fairly straightforward. The original code, written in 2018, was still in good shape but Apple had since introduced <a href="https://developer.apple.com/documentation/UIKit/updating-collection-views-using-diffable-data-sources">Diffable Data Sources</a>, which would’ve been my go-to for a new project. In a corporate setting, such a refactor of “legacy code” would be considered technical debt and require prioritization and analysis. In my case, I just started a new branch and experimented. More on that in <a href="/projects/gunvault/2025/08/12/Implementing-Drag-and-Drop-in-UICollectionView-and-Core-Data-Part-2.html">Part 2</a>.</p>]]></content><author><name></name></author><category term="projects" /><category term="gunvault" /><category term="swift" /><category term="ios" /><category term="core-data" /><summary type="html"><![CDATA[One of the feature requests I received was to allow users to rearrange gun photos in a custom order. Since I was planning other changes to the photos module, I decided to tackle this request first as it would lay a solid foundation for future updates. What seemed like a simple task turned out to be a bit more complex than expected, so I decided to blog about it.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://villyg.com/assets/images/UIKit.png" /><media:content medium="image" url="http://villyg.com/assets/images/UIKit.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Taming a Herd of View Controllers: Refactoring for Simplicity in Core Data with Swift</title><link href="http://villyg.com/projects/gunvault/2025/07/31/Taming-a-herd-of-view-controllers.html" rel="alternate" type="text/html" title="Taming a Herd of View Controllers: Refactoring for Simplicity in Core Data with Swift" /><published>2025-07-31T21:37:16+00:00</published><updated>2025-07-31T21:37:16+00:00</updated><id>http://villyg.com/projects/gunvault/2025/07/31/Taming-a-herd-of-view-controllers</id><content type="html" xml:base="http://villyg.com/projects/gunvault/2025/07/31/Taming-a-herd-of-view-controllers.html"><![CDATA[<p>If you’ve worked on an iOS app of any significant size, you’ve likely encountered it: the copy-paste monster. It starts innocently. You build a view controller, get it working perfectly, and then you need another one that’s almost the same. You duplicate the file, change a few lines, and move on. But then you need another, and another. Before you know it, you’re the reluctant owner of a herd of nearly identical view controllers, and any change to one means a tedious and error-prone update to all the others. To be fair - there is the <a href="https://en.wikipedia.org/wiki/Rule_of_three_(computer_programming)">Rule of three (three strikes and you refactor)</a>, so some temporary duplication is very much okay as long as it is addressed in a timely manner.</p>

<!--more-->

<ul>
  <li><a href="#data-model">Data model</a></li>
  <li><a href="#workflow">Workflow</a></li>
  <li><a href="#implementation">Implementation</a></li>
  <li><a href="#problem">Problem</a></li>
  <li><a href="#solution">Solution</a></li>
  <li><a href="#conclusion">Conclusion</a></li>
</ul>

<h2 id="data-model">Data model</h2>

<p>Before diving into the details, it is worth talking about the data I am dealing with. This is an abbreviated entity diagram that shows some of it. Each <code class="language-plaintext highlighter-rouge">Gun</code> instance is a parent and it has multiple child attributes: <code class="language-plaintext highlighter-rouge">Make</code>, <code class="language-plaintext highlighter-rouge">Caliber</code>, <code class="language-plaintext highlighter-rouge">Action</code>, <code class="language-plaintext highlighter-rouge">Finish</code>, <code class="language-plaintext highlighter-rouge">Type</code>, etc. Obviusly a <code class="language-plaintext highlighter-rouge">Gun</code> can only have one <code class="language-plaintext highlighter-rouge">Type</code> but a <code class="language-plaintext highlighter-rouge">Type</code> can be associated with multiple <code class="language-plaintext highlighter-rouge">Gun</code> objects. The attributes themselves have just one user-entered property: <code class="language-plaintext highlighter-rouge">Name</code>.</p>

<pre class="mermaid">
   erDiagram
         Gun {
             NSDate manufactureDate
             String model
             String note
            String serialNumber
        }
        GunMake {
            String name
        }
        GunCaliber {
            String name
        }
        GunAction {
            String name
        }
        GunFinish {
            String name
        }
        GunType {
            String name
        }
        Gun }o--|| GunMake : "make"
        Gun }o--|| GunCaliber : "caliber"
        Gun }o--|| GunAction : "action"
        Gun }o--|| GunFinish : "finish"
        Gun }o--|| GunType : "type"

</pre>
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.9.1/dist/mermaid.min.js"></script>

<p>I am very well aware that there are better ways to organize parent-child attributes but given the current context and in the spirit of <a href="https://en.wikipedia.org/wiki/KISS_principle">KISS</a> - this entity structure is sufficient and if need be - it can be migrated later to something better.</p>

<h2 id="workflow">Workflow</h2>

<p>For the Gun-upserting workflow I started with a very basic approach. I settled on using a table view with all the Gun attributes shown in individual cells. Once a cell is selected eg. Type - another view would present itself with a Type list to pick from and a button to add a new one needed. The new Type entry screen is even simpler - just a single textbox to collect name of the Type attribute.</p>

<p><img src="/assets/images/2025-07-31-Taming-a-herd-of-view-controllers/Screenshot_01.png" alt="Upsert view" />
<img src="/assets/images/2025-07-31-Taming-a-herd-of-view-controllers/Screenshot_02.png" alt="Selector view" />
<img src="/assets/images/2025-07-31-Taming-a-herd-of-view-controllers/Screenshot_03.png" alt="New value view" /></p>

<h2 id="implementation">Implementation</h2>

<p>Although this is absolutely not a streamlined way of doing data entry - it has the benefit of being quick and easy to develop. A <code class="language-plaintext highlighter-rouge">GunUpsertController</code>, a <code class="language-plaintext highlighter-rouge">GunTypeEditController</code>, a <code class="language-plaintext highlighter-rouge">DBStringEditController</code> (not shown) along with some <code class="language-plaintext highlighter-rouge">Core Data</code> child branching logic to support cancellation was all that was needed.</p>

<p>Note: this is partial implementation for brevity.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">GunUpsertController</span><span class="p">:</span> <span class="kt">UITableViewController</span> <span class="p">{</span>

    <span class="k">var</span> <span class="nv">gun</span><span class="p">:</span> <span class="kt">Gun</span><span class="o">!</span>
    <span class="k">var</span> <span class="nv">managedObjectContext</span><span class="p">:</span> <span class="kt">NSManagedObjectContext</span><span class="o">!</span>

    <span class="k">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="n">_</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">,</span> <span class="n">cellForRowAt</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">UITableViewCell</span> <span class="p">{</span>

        <span class="k">let</span> <span class="nv">result</span><span class="p">:</span> <span class="kt">UITableViewCell</span> <span class="o">=</span> <span class="kt">UITableViewCell</span><span class="p">(</span><span class="nv">style</span><span class="p">:</span> <span class="o">.</span><span class="n">value1</span><span class="p">,</span> <span class="nv">reuseIdentifier</span><span class="p">:</span> <span class="s">"gunType"</span><span class="p">)</span>
        <span class="n">result</span><span class="o">.</span><span class="n">accessoryType</span> <span class="o">=</span> <span class="o">.</span><span class="n">disclosureIndicator</span>

        <span class="k">if</span> <span class="n">indexPath</span><span class="o">.</span><span class="n">section</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
            <span class="k">if</span> <span class="n">indexPath</span><span class="o">.</span><span class="n">row</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span> <span class="c1">// Type</span>
                <span class="n">result</span><span class="o">.</span><span class="n">textLabel</span><span class="p">?</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="kt">Strings</span><span class="o">.</span><span class="kt">LABEL_GUN_TYPE</span>
                <span class="n">result</span><span class="o">.</span><span class="n">detailTextLabel</span><span class="p">?</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">gun</span><span class="o">.</span><span class="n">type</span><span class="p">?</span><span class="o">.</span><span class="n">name</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="n">result</span>
    <span class="p">}</span>


    <span class="k">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="n">_</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">,</span> <span class="n">didSelectRowAt</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">)</span> <span class="p">{</span>

        <span class="k">if</span> <span class="n">indexPath</span><span class="o">.</span><span class="n">section</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
            <span class="k">if</span> <span class="n">indexPath</span><span class="o">.</span><span class="n">row</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span> <span class="c1">// Type</span>
                <span class="k">let</span> <span class="nv">controller</span> <span class="o">=</span> <span class="kt">DBGunTypeEditController</span><span class="p">(</span><span class="nv">gun</span><span class="p">:</span> <span class="n">gun</span><span class="p">)</span>
                <span class="n">controller</span><span class="o">.</span><span class="n">editPropertyControllerDelegate</span> <span class="o">=</span> <span class="k">self</span>
                <span class="k">let</span> <span class="nv">navigationController</span> <span class="o">=</span> <span class="kt">UINavigationController</span><span class="p">(</span><span class="nv">rootViewController</span><span class="p">:</span> <span class="n">controller</span><span class="p">)</span>
                <span class="nf">present</span><span class="p">(</span><span class="n">navigationController</span><span class="p">,</span> <span class="nv">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nv">completion</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>

<span class="p">}</span>
</code></pre></div></div>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">DBGunTypeEditController</span><span class="p">:</span> <span class="kt">UITableViewController</span> <span class="p">{</span>

    <span class="k">var</span> <span class="nv">gun</span><span class="p">:</span> <span class="kt">Gun</span><span class="o">!</span>

    <span class="k">var</span> <span class="nv">managedObjectContext</span><span class="p">:</span> <span class="kt">NSManagedObjectContext</span><span class="o">!</span>

    <span class="k">var</span> <span class="nv">editPropertyControllerDelegate</span><span class="p">:</span> <span class="kt">DBEditPropertyControllerDelegate</span><span class="p">?</span>

    <span class="k">var</span> <span class="nv">validationHelper</span><span class="p">:</span> <span class="kt">ValidationHelper</span><span class="o">!</span>

    <span class="kd">lazy</span> <span class="k">var</span> <span class="nv">fetchedResultsControler</span><span class="p">:</span> <span class="kt">NSFetchedResultsController</span><span class="o">&lt;</span><span class="kt">GunType</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">{</span>
        <span class="p">[</span><span class="k">unowned</span> <span class="k">self</span><span class="p">]</span> <span class="k">in</span>
        <span class="k">let</span> <span class="nv">sortDescriptor</span> <span class="o">=</span> <span class="kt">NSSortDescriptor</span><span class="p">(</span><span class="nv">key</span><span class="p">:</span> <span class="s">"name"</span><span class="p">,</span> <span class="nv">ascending</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>

        <span class="k">let</span> <span class="nv">fReq</span><span class="p">:</span> <span class="kt">NSFetchRequest</span> <span class="o">=</span> <span class="kt">GunType</span><span class="o">.</span><span class="nf">fetchRequest</span><span class="p">()</span>
        <span class="n">fReq</span><span class="o">.</span><span class="n">sortDescriptors</span> <span class="o">=</span> <span class="p">[</span><span class="n">sortDescriptor</span><span class="p">]</span>

        <span class="k">let</span> <span class="nv">frc</span> <span class="o">=</span> <span class="kt">NSFetchedResultsController</span><span class="p">(</span><span class="nv">fetchRequest</span><span class="p">:</span> <span class="n">fReq</span><span class="p">,</span> <span class="nv">managedObjectContext</span><span class="p">:</span> <span class="k">self</span><span class="o">.</span><span class="n">managedObjectContext</span><span class="p">,</span> <span class="nv">sectionNameKeyPath</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="nv">cacheName</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>

        <span class="k">return</span> <span class="n">frc</span>
        <span class="p">}()</span>

    <span class="kd">convenience</span> <span class="nf">init</span><span class="p">(</span><span class="nv">gun</span><span class="p">:</span> <span class="kt">Gun</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">style</span><span class="p">:</span> <span class="kt">UITableViewStyle</span><span class="o">.</span><span class="n">grouped</span><span class="p">)</span>
        <span class="k">self</span><span class="o">.</span><span class="n">managedObjectContext</span> <span class="o">=</span> <span class="n">gun</span><span class="o">.</span><span class="n">managedObjectContext</span>
        <span class="k">self</span><span class="o">.</span><span class="n">gun</span> <span class="o">=</span> <span class="n">gun</span>
        <span class="k">self</span><span class="o">.</span><span class="n">validationHelper</span> <span class="o">=</span> <span class="kt">ValidationHelper</span><span class="o">.</span><span class="n">sharedInstance</span>
    <span class="p">}</span>

    <span class="k">override</span> <span class="kd">func</span> <span class="nf">viewDidLoad</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">super</span><span class="o">.</span><span class="nf">viewDidLoad</span><span class="p">()</span>

        <span class="k">self</span><span class="o">.</span><span class="n">tableView</span><span class="o">.</span><span class="nf">register</span><span class="p">(</span><span class="kt">UITableViewCell</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forCellReuseIdentifier</span><span class="p">:</span> <span class="s">"cell"</span><span class="p">)</span>

        <span class="k">let</span> <span class="nv">cancelButton</span> <span class="o">=</span> <span class="kt">UIBarButtonItem</span><span class="p">(</span><span class="nv">barButtonSystemItem</span><span class="p">:</span> <span class="o">.</span><span class="n">cancel</span><span class="p">,</span> <span class="nv">target</span><span class="p">:</span> <span class="k">self</span><span class="p">,</span> <span class="nv">action</span><span class="p">:</span> <span class="kd">#selector(</span><span class="nf">cancelButtonTapped(sender:)</span><span class="kd">)</span><span class="p">)</span>
        <span class="n">navigationItem</span><span class="o">.</span><span class="n">leftBarButtonItem</span> <span class="o">=</span> <span class="n">cancelButton</span>

        <span class="k">let</span> <span class="nv">addBarButton</span> <span class="o">=</span> <span class="kt">UIBarButtonItem</span><span class="p">(</span><span class="nv">barButtonSystemItem</span><span class="p">:</span> <span class="o">.</span><span class="n">add</span><span class="p">,</span> <span class="nv">target</span><span class="p">:</span> <span class="k">self</span><span class="p">,</span> <span class="nv">action</span><span class="p">:</span> <span class="kd">#selector(</span><span class="nf">addButtonTapped(sender:)</span><span class="kd">)</span><span class="p">)</span>
        <span class="n">navigationItem</span><span class="o">.</span><span class="n">rightBarButtonItem</span> <span class="o">=</span> <span class="n">addBarButton</span>

        <span class="n">navigationItem</span><span class="o">.</span><span class="n">title</span> <span class="o">=</span> <span class="kt">Strings</span><span class="o">.</span><span class="kt">TITLE_SELECT_GUN_TYPE</span>

        <span class="n">fetchedResultsControler</span><span class="o">.</span><span class="n">delegate</span> <span class="o">=</span> <span class="k">self</span>

        <span class="k">do</span> <span class="p">{</span>

            <span class="k">try</span> <span class="n">fetchedResultsControler</span><span class="o">.</span><span class="nf">performFetch</span><span class="p">()</span>

        <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
            <span class="nf">fatalError</span><span class="p">(</span><span class="s">"Error fetching guns: </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="k">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="n">_</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">,</span> <span class="n">numberOfRowsInSection</span> <span class="nv">section</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Int</span> <span class="p">{</span>

        <span class="k">return</span> <span class="n">fetchedResultsControler</span><span class="o">.</span><span class="n">sections</span><span class="o">!</span><span class="p">[</span><span class="n">section</span><span class="p">]</span><span class="o">.</span><span class="n">numberOfObjects</span>

    <span class="p">}</span>

    <span class="k">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="n">_</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">,</span> <span class="n">cellForRowAt</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">UITableViewCell</span> <span class="p">{</span>

        <span class="k">guard</span> <span class="k">let</span> <span class="nv">result</span> <span class="o">=</span> <span class="n">tableView</span><span class="o">.</span><span class="nf">dequeueReusableCell</span><span class="p">(</span><span class="nv">withIdentifier</span><span class="p">:</span> <span class="s">"cell"</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">return</span> <span class="kt">UITableViewCell</span><span class="p">()</span>
        <span class="p">}</span>

        <span class="k">let</span> <span class="nv">entity</span> <span class="o">=</span> <span class="n">fetchedResultsControler</span><span class="o">.</span><span class="nf">object</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">)</span>

        <span class="n">result</span><span class="o">.</span><span class="n">accessoryType</span> <span class="o">=</span> <span class="o">.</span><span class="n">disclosureIndicator</span>
        <span class="n">result</span><span class="o">.</span><span class="n">textLabel</span><span class="p">?</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">entity</span><span class="o">.</span><span class="n">name</span>

        <span class="k">return</span> <span class="n">result</span>

    <span class="p">}</span>

    <span class="k">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="n">_</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">,</span> <span class="n">titleForHeaderInSection</span> <span class="nv">section</span><span class="p">:</span> <span class="kt">Int</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="k">if</span> <span class="k">let</span> <span class="nv">fetchedObjects</span> <span class="o">=</span> <span class="n">fetchedResultsControler</span><span class="o">.</span><span class="n">fetchedObjects</span> <span class="p">{</span>
            <span class="k">if</span> <span class="n">section</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">fetchedObjects</span><span class="o">.</span><span class="n">count</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="p">{</span>
                <span class="k">return</span> <span class="kt">Strings</span><span class="o">.</span><span class="kt">MESSAGE_EDIT_GUN_TYPE_INSTRUCTIONS</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="kc">nil</span>

    <span class="p">}</span>

    <span class="k">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="n">_</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">,</span> <span class="n">didSelectRowAt</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">)</span> <span class="p">{</span>

        <span class="k">let</span> <span class="nv">selectedEntity</span> <span class="o">=</span> <span class="n">fetchedResultsControler</span><span class="o">.</span><span class="nf">object</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">)</span>

        <span class="n">gun</span><span class="o">.</span><span class="n">type</span> <span class="o">=</span> <span class="n">selectedEntity</span>
        <span class="n">selectedEntity</span><span class="o">.</span><span class="nf">addToGuns</span><span class="p">(</span><span class="n">gun</span><span class="p">)</span>

        <span class="n">editPropertyControllerDelegate</span><span class="p">?</span><span class="o">.</span><span class="nf">controllerDidFinish</span><span class="p">(</span><span class="k">self</span><span class="p">)</span>

    <span class="p">}</span>

    <span class="kd">@objc</span> <span class="kd">func</span> <span class="nf">addButtonTapped</span><span class="p">(</span><span class="nv">sender</span><span class="p">:</span> <span class="kt">UIBarButtonItem</span><span class="p">)</span> <span class="p">{</span>

        <span class="k">let</span> <span class="nv">childContext</span> <span class="o">=</span> <span class="kt">NSManagedObjectContext</span><span class="p">(</span><span class="nv">concurrencyType</span><span class="p">:</span> <span class="o">.</span><span class="n">mainQueueConcurrencyType</span><span class="p">)</span>
        <span class="n">childContext</span><span class="o">.</span><span class="n">parent</span> <span class="o">=</span> <span class="n">managedObjectContext</span>
        <span class="k">let</span> <span class="nv">childEntity</span> <span class="o">=</span> <span class="kt">GunType</span><span class="p">(</span><span class="nv">context</span><span class="p">:</span> <span class="n">childContext</span><span class="p">)</span>

        <span class="k">let</span> <span class="nv">controller</span><span class="p">:</span> <span class="kt">DBStringEditController</span> <span class="o">=</span> <span class="kt">DBStringEditController</span><span class="p">(</span><span class="nv">managedObject</span><span class="p">:</span> <span class="n">childEntity</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="s">"name"</span><span class="p">)</span>
        <span class="n">controller</span><span class="o">.</span><span class="n">delegate</span> <span class="o">=</span> <span class="k">self</span>
        <span class="n">controller</span><span class="o">.</span><span class="n">navigationItem</span><span class="o">.</span><span class="n">title</span> <span class="o">=</span> <span class="kt">Strings</span><span class="o">.</span><span class="kt">TITLE_NEW_GUN_TYPE</span>
        <span class="n">controller</span><span class="o">.</span><span class="n">textField</span><span class="o">.</span><span class="n">placeholder</span> <span class="o">=</span> <span class="kt">Strings</span><span class="o">.</span><span class="kt">LABEL_NAME</span>
        <span class="k">let</span> <span class="nv">navigationController</span><span class="p">:</span> <span class="kt">UINavigationController</span> <span class="o">=</span> <span class="kt">UINavigationController</span><span class="p">(</span><span class="nv">rootViewController</span><span class="p">:</span> <span class="n">controller</span><span class="p">)</span>

        <span class="nf">present</span><span class="p">(</span><span class="n">navigationController</span><span class="p">,</span> <span class="nv">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nv">completion</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>

    <span class="p">}</span>

    <span class="kd">@objc</span> <span class="kd">func</span> <span class="nf">cancelButtonTapped</span><span class="p">(</span><span class="nv">sender</span><span class="p">:</span> <span class="kt">UIBarButtonItem</span><span class="p">)</span> <span class="p">{</span>

        <span class="n">editPropertyControllerDelegate</span><span class="p">?</span><span class="o">.</span><span class="nf">contorllerDidCancel</span><span class="p">(</span><span class="k">self</span><span class="p">)</span>

    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="problem">Problem</h2>

<p>Obviously a gun has more than one attribute so I quickly replicated the code to add support for <code class="language-plaintext highlighter-rouge">Action</code> and <code class="language-plaintext highlighter-rouge">Caliber</code>. Unfortunately, I still had to account for <code class="language-plaintext highlighter-rouge">Make</code> and <code class="language-plaintext highlighter-rouge">Type</code> and I also wanted to add another module later for maintaining an <code class="language-plaintext highlighter-rouge">Ammo</code> inventory that would follow the same workflow so it was time to honor the <a href="https://en.wikipedia.org/wiki/Rule_of_three_(computer_programming)">Rule of three</a> and refactor before things get out of control.</p>

<h2 id="solution">Solution</h2>

<p>The solution was to apply the <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">Don’t Repeat Yourself (DRY)</a> principle by abstracting the common code into basic reusable classes.</p>

<p>The core logic in each replicated controller was responsible for:</p>

<ol>
  <li>Setting up a <code class="language-plaintext highlighter-rouge">UITableView</code>.</li>
  <li>Configuring an <code class="language-plaintext highlighter-rouge">NSFetchedResultsController</code> to fetch and display a lookup dataset from Core Data.</li>
  <li>Handling table view updates via the <code class="language-plaintext highlighter-rouge">NSFetchedResultsControllerDelegate</code>.</li>
  <li>Implementing <code class="language-plaintext highlighter-rouge">DZNEmptyDataSetSource</code> and <code class="language-plaintext highlighter-rouge">DZNEmptyDataSetDelegate</code> to show a user-friendly message
when the table was empty.</li>
  <li>Adding a “plus” button to the navigation bar to create a new entry.</li>
  <li>Implementing the swipe-to-delete functionality.</li>
</ol>

<p>I decided to create a generic <code class="language-plaintext highlighter-rouge">DBEntityPropertyEditController</code>.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">DBEntityPropertyEditController</span><span class="o">&lt;</span><span class="kt">T</span><span class="p">:</span> <span class="kt">NSManagedObject</span><span class="o">&gt;</span><span class="p">:</span> <span class="kt">UITableViewController</span><span class="p">,</span> <span class="kt">NSFetchedResultsControllerDelegate</span><span class="p">,</span> <span class="kt">DZNEmptyDataSetSource</span><span class="p">,</span> <span class="kt">DZNEmptyDataSetDelegate</span><span class="p">,</span> <span class="kt">DBEditPropertyControllerDelegate</span> <span class="p">{</span>

    <span class="k">var</span> <span class="nv">parentObject</span><span class="p">:</span> <span class="kt">NSManagedObject</span><span class="o">!</span>
    <span class="k">var</span> <span class="nv">managedObjectContext</span><span class="p">:</span> <span class="kt">NSManagedObjectContext</span><span class="o">!</span>
    <span class="k">var</span> <span class="nv">editPropertyControllerDelegate</span><span class="p">:</span> <span class="kt">DBEditPropertyControllerDelegate</span><span class="p">?</span>
    <span class="k">var</span> <span class="nv">validationHelper</span><span class="p">:</span> <span class="kt">ValidationHelper</span><span class="o">!</span>

    <span class="k">var</span> <span class="nv">propertyEntityName</span><span class="p">:</span> <span class="kt">String</span><span class="o">!</span>
    <span class="k">var</span> <span class="nv">navigationItemTitle</span><span class="p">:</span> <span class="kt">String</span><span class="o">!</span>
    <span class="k">var</span> <span class="nv">addNewTitle</span><span class="p">:</span> <span class="kt">String</span><span class="o">!</span>
    <span class="k">var</span> <span class="nv">instructionsText</span><span class="p">:</span> <span class="kt">String</span><span class="o">!</span>
    <span class="k">var</span> <span class="nv">parentRelationshipKeyPath</span><span class="p">:</span> <span class="kt">String</span><span class="o">!</span>
    <span class="k">var</span> <span class="nv">propertyInverseRelationshipKeyPath</span><span class="p">:</span> <span class="kt">String</span><span class="o">!</span>

    <span class="kd">lazy</span> <span class="k">var</span> <span class="nv">fetchedResultsControler</span><span class="p">:</span> <span class="kt">NSFetchedResultsController</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">{</span>
        <span class="p">[</span><span class="k">unowned</span> <span class="k">self</span><span class="p">]</span> <span class="k">in</span>
        <span class="k">let</span> <span class="nv">sortDescriptor</span> <span class="o">=</span> <span class="kt">NSSortDescriptor</span><span class="p">(</span><span class="nv">key</span><span class="p">:</span> <span class="s">"name"</span><span class="p">,</span> <span class="nv">ascending</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>

        <span class="k">let</span> <span class="nv">fReq</span><span class="p">:</span> <span class="kt">NSFetchRequest</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span> <span class="o">=</span> <span class="kt">NSFetchRequest</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span><span class="p">(</span><span class="nv">entityName</span><span class="p">:</span> <span class="k">self</span><span class="o">.</span><span class="n">propertyEntityName</span><span class="p">)</span>
        <span class="n">fReq</span><span class="o">.</span><span class="n">sortDescriptors</span> <span class="o">=</span> <span class="p">[</span><span class="n">sortDescriptor</span><span class="p">]</span>

        <span class="k">let</span> <span class="nv">frc</span> <span class="o">=</span> <span class="kt">NSFetchedResultsController</span><span class="p">(</span><span class="nv">fetchRequest</span><span class="p">:</span> <span class="n">fReq</span><span class="p">,</span> <span class="nv">managedObjectContext</span><span class="p">:</span> <span class="k">self</span><span class="o">.</span><span class="n">managedObjectContext</span><span class="p">,</span> <span class="nv">sectionNameKeyPath</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="nv">cacheName</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>

        <span class="k">return</span> <span class="n">frc</span>
    <span class="p">}()</span>


    <span class="kd">convenience</span> <span class="nf">init</span><span class="p">(</span>
        <span class="nv">parentObject</span><span class="p">:</span> <span class="kt">NSManagedObject</span><span class="p">,</span>
        <span class="nv">propertyEntityName</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span>
        <span class="nv">navigationItemTitle</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span>
        <span class="nv">addNewTitle</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span>
        <span class="nv">instructionsText</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span>
        <span class="nv">parentRelationshipKeyPath</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span>
        <span class="nv">propertyInverseRelationshipKeyPath</span><span class="p">:</span> <span class="kt">String</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">style</span><span class="p">:</span> <span class="kt">UITableView</span><span class="o">.</span><span class="kt">Style</span><span class="o">.</span><span class="n">grouped</span><span class="p">)</span>
        <span class="k">self</span><span class="o">.</span><span class="n">managedObjectContext</span> <span class="o">=</span> <span class="n">parentObject</span><span class="o">.</span><span class="n">managedObjectContext</span>
        <span class="k">self</span><span class="o">.</span><span class="n">parentObject</span> <span class="o">=</span> <span class="n">parentObject</span>
        <span class="k">self</span><span class="o">.</span><span class="n">validationHelper</span> <span class="o">=</span> <span class="kt">ValidationHelper</span><span class="o">.</span><span class="n">sharedInstance</span>
        <span class="k">self</span><span class="o">.</span><span class="n">propertyEntityName</span> <span class="o">=</span> <span class="n">propertyEntityName</span>
        <span class="k">self</span><span class="o">.</span><span class="n">navigationItemTitle</span> <span class="o">=</span> <span class="n">navigationItemTitle</span>
        <span class="k">self</span><span class="o">.</span><span class="n">addNewTitle</span> <span class="o">=</span> <span class="n">addNewTitle</span>
        <span class="k">self</span><span class="o">.</span><span class="n">instructionsText</span> <span class="o">=</span> <span class="n">instructionsText</span>
        <span class="k">self</span><span class="o">.</span><span class="n">parentRelationshipKeyPath</span> <span class="o">=</span> <span class="n">parentRelationshipKeyPath</span>
        <span class="k">self</span><span class="o">.</span><span class="n">propertyInverseRelationshipKeyPath</span> <span class="o">=</span> <span class="n">propertyInverseRelationshipKeyPath</span>
    <span class="p">}</span>

    <span class="k">override</span> <span class="kd">func</span> <span class="nf">viewDidLoad</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">super</span><span class="o">.</span><span class="nf">viewDidLoad</span><span class="p">()</span>

        <span class="k">self</span><span class="o">.</span><span class="n">tableView</span><span class="o">.</span><span class="nf">register</span><span class="p">(</span><span class="kt">UITableViewCell</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">forCellReuseIdentifier</span><span class="p">:</span> <span class="s">"cell"</span><span class="p">)</span>
        <span class="k">self</span><span class="o">.</span><span class="n">tableView</span><span class="o">.</span><span class="n">emptyDataSetSource</span> <span class="o">=</span> <span class="k">self</span>
        <span class="k">self</span><span class="o">.</span><span class="n">tableView</span><span class="o">.</span><span class="n">emptyDataSetDelegate</span> <span class="o">=</span> <span class="k">self</span>

        <span class="k">let</span> <span class="nv">cancelButton</span> <span class="o">=</span> <span class="kt">UIBarButtonItem</span><span class="p">(</span><span class="nv">barButtonSystemItem</span><span class="p">:</span> <span class="o">.</span><span class="n">cancel</span><span class="p">,</span> <span class="nv">target</span><span class="p">:</span> <span class="k">self</span><span class="p">,</span> <span class="nv">action</span><span class="p">:</span> <span class="kd">#selector(</span><span class="nf">cancelButtonTapped(sender:)</span><span class="kd">)</span><span class="p">)</span>
        <span class="n">navigationItem</span><span class="o">.</span><span class="n">leftBarButtonItem</span> <span class="o">=</span> <span class="n">cancelButton</span>

        <span class="k">let</span> <span class="nv">addBarButton</span> <span class="o">=</span> <span class="kt">UIBarButtonItem</span><span class="p">(</span><span class="nv">barButtonSystemItem</span><span class="p">:</span> <span class="o">.</span><span class="n">add</span><span class="p">,</span> <span class="nv">target</span><span class="p">:</span> <span class="k">self</span><span class="p">,</span> <span class="nv">action</span><span class="p">:</span> <span class="kd">#selector(</span><span class="nf">addButtonTapped(sender:)</span><span class="kd">)</span><span class="p">)</span>
        <span class="n">navigationItem</span><span class="o">.</span><span class="n">rightBarButtonItem</span> <span class="o">=</span> <span class="n">addBarButton</span>

        <span class="n">navigationItem</span><span class="o">.</span><span class="n">title</span> <span class="o">=</span> <span class="n">navigationItemTitle</span>

        <span class="n">fetchedResultsControler</span><span class="o">.</span><span class="n">delegate</span> <span class="o">=</span> <span class="k">self</span>

        <span class="k">do</span> <span class="p">{</span>
            <span class="k">try</span> <span class="n">fetchedResultsControler</span><span class="o">.</span><span class="nf">performFetch</span><span class="p">()</span>
        <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
            <span class="nf">fatalError</span><span class="p">(</span><span class="s">"Error fetching: </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="c1">// MARK: - UITableViewDataSource</span>

    <span class="k">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="n">_</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">,</span> <span class="n">numberOfRowsInSection</span> <span class="nv">section</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Int</span> <span class="p">{</span>
        <span class="k">guard</span> <span class="k">let</span> <span class="nv">sectionInfo</span> <span class="o">=</span> <span class="n">fetchedResultsControler</span><span class="o">.</span><span class="n">sections</span><span class="p">?[</span><span class="n">section</span><span class="p">]</span> <span class="k">else</span>  <span class="p">{</span> <span class="nf">fatalError</span><span class="p">(</span><span class="s">"failed to resolve FRC"</span><span class="p">)</span> <span class="p">}</span>
        <span class="k">return</span> <span class="n">sectionInfo</span><span class="o">.</span><span class="n">numberOfObjects</span>
    <span class="p">}</span>

    <span class="k">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="n">_</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">,</span> <span class="n">cellForRowAt</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">UITableViewCell</span> <span class="p">{</span>
        <span class="k">guard</span> <span class="k">let</span> <span class="nv">result</span> <span class="o">=</span> <span class="n">tableView</span><span class="o">.</span><span class="nf">dequeueReusableCell</span><span class="p">(</span><span class="nv">withIdentifier</span><span class="p">:</span> <span class="s">"cell"</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">return</span> <span class="kt">UITableViewCell</span><span class="p">()</span>
        <span class="p">}</span>

        <span class="k">let</span> <span class="nv">entity</span> <span class="o">=</span> <span class="n">fetchedResultsControler</span><span class="o">.</span><span class="nf">object</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">)</span>

        <span class="n">result</span><span class="o">.</span><span class="n">accessoryType</span> <span class="o">=</span> <span class="o">.</span><span class="n">disclosureIndicator</span>
        <span class="n">result</span><span class="o">.</span><span class="n">textLabel</span><span class="p">?</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">entity</span><span class="o">.</span><span class="nf">value</span><span class="p">(</span><span class="nv">forKey</span><span class="p">:</span> <span class="s">"name"</span><span class="p">)</span> <span class="k">as?</span> <span class="kt">String</span>

        <span class="k">return</span> <span class="n">result</span>
    <span class="p">}</span>

    <span class="k">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="n">_</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">,</span> <span class="n">titleForHeaderInSection</span> <span class="nv">section</span><span class="p">:</span> <span class="kt">Int</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="k">if</span> <span class="k">let</span> <span class="nv">fetchedObjects</span> <span class="o">=</span> <span class="n">fetchedResultsControler</span><span class="o">.</span><span class="n">fetchedObjects</span> <span class="p">{</span>
            <span class="k">if</span> <span class="n">section</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">fetchedObjects</span><span class="o">.</span><span class="n">count</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="p">{</span>
                <span class="k">return</span> <span class="n">instructionsText</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="kc">nil</span>
    <span class="p">}</span>

    <span class="c1">// MARK: - UITableViewDelegate</span>

    <span class="k">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="n">_</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">,</span> <span class="n">didSelectRowAt</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">selectedEntity</span> <span class="o">=</span> <span class="n">fetchedResultsControler</span><span class="o">.</span><span class="nf">object</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">)</span>

        <span class="n">parentObject</span><span class="o">.</span><span class="nf">setValue</span><span class="p">(</span><span class="n">selectedEntity</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="n">parentRelationshipKeyPath</span><span class="p">)</span>

        <span class="k">let</span> <span class="nv">mutableGuns</span> <span class="o">=</span> <span class="n">selectedEntity</span><span class="o">.</span><span class="nf">mutableSetValue</span><span class="p">(</span><span class="nv">forKey</span><span class="p">:</span> <span class="n">propertyInverseRelationshipKeyPath</span><span class="p">)</span>
        <span class="n">mutableGuns</span><span class="o">.</span><span class="nf">add</span><span class="p">(</span><span class="n">parentObject</span><span class="p">)</span>

        <span class="kt">AppDelegate</span><span class="p">()</span><span class="o">.</span><span class="nf">saveContext</span><span class="p">()</span>

        <span class="n">editPropertyControllerDelegate</span><span class="p">?</span><span class="o">.</span><span class="nf">controllerDidFinish</span><span class="p">(</span><span class="k">self</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="kd">@objc</span> <span class="kd">func</span> <span class="nf">addButtonTapped</span><span class="p">(</span><span class="nv">sender</span><span class="p">:</span> <span class="kt">UIBarButtonItem</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">childContext</span> <span class="o">=</span> <span class="kt">NSManagedObjectContext</span><span class="p">(</span><span class="nv">concurrencyType</span><span class="p">:</span> <span class="o">.</span><span class="n">mainQueueConcurrencyType</span><span class="p">)</span>
        <span class="n">childContext</span><span class="o">.</span><span class="n">parent</span> <span class="o">=</span> <span class="n">managedObjectContext</span>

        <span class="k">let</span> <span class="nv">childEntity</span> <span class="o">=</span> <span class="kt">NSEntityDescription</span><span class="o">.</span><span class="nf">insertNewObject</span><span class="p">(</span><span class="nv">forEntityName</span><span class="p">:</span> <span class="n">propertyEntityName</span><span class="p">,</span> <span class="nv">into</span><span class="p">:</span> <span class="n">childContext</span><span class="p">)</span>

        <span class="k">let</span> <span class="nv">controller</span> <span class="o">=</span> <span class="kt">DBStringEditController</span><span class="p">(</span><span class="nv">managedObject</span><span class="p">:</span> <span class="n">childEntity</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="s">"name"</span><span class="p">)</span>
        <span class="n">controller</span><span class="o">.</span><span class="n">delegate</span> <span class="o">=</span> <span class="k">self</span>
        <span class="n">controller</span><span class="o">.</span><span class="n">navigationItem</span><span class="o">.</span><span class="n">title</span> <span class="o">=</span> <span class="n">addNewTitle</span>
        <span class="n">controller</span><span class="o">.</span><span class="n">textField</span><span class="o">.</span><span class="n">placeholder</span> <span class="o">=</span> <span class="kt">Strings</span><span class="o">.</span><span class="kt">LABEL_NAME</span>
        <span class="k">let</span> <span class="nv">navigationController</span> <span class="o">=</span> <span class="kt">UINavigationController</span><span class="p">(</span><span class="nv">rootViewController</span><span class="p">:</span> <span class="n">controller</span><span class="p">)</span>

        <span class="nf">present</span><span class="p">(</span><span class="n">navigationController</span><span class="p">,</span> <span class="nv">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nv">completion</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="kd">@objc</span> <span class="kd">func</span> <span class="nf">cancelButtonTapped</span><span class="p">(</span><span class="nv">sender</span><span class="p">:</span> <span class="kt">UIBarButtonItem</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">editPropertyControllerDelegate</span><span class="p">?</span><span class="o">.</span><span class="nf">contorllerDidCancel</span><span class="p">(</span><span class="k">self</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="c1">// MARK: - DZNEmptyDataSetSource</span>
    <span class="kd">func</span> <span class="nf">title</span><span class="p">(</span><span class="n">forEmptyDataSet</span> <span class="nv">scrollView</span><span class="p">:</span> <span class="kt">UIScrollView</span><span class="o">!</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">NSAttributedString</span><span class="o">!</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">title</span> <span class="o">=</span> <span class="kt">Strings</span><span class="o">.</span><span class="kt">MESSAGE_NO_ITEMS_PRESENT</span>
        <span class="k">let</span> <span class="nv">attributes</span> <span class="o">=</span> <span class="p">[</span><span class="kt">NSAttributedString</span><span class="o">.</span><span class="kt">Key</span><span class="o">.</span><span class="nv">font</span><span class="p">:</span> <span class="kt">UIFont</span><span class="o">.</span><span class="nf">preferredFont</span><span class="p">(</span><span class="nv">forTextStyle</span><span class="p">:</span> <span class="o">.</span><span class="n">headline</span><span class="p">)]</span>
        <span class="k">return</span> <span class="kt">NSAttributedString</span><span class="p">(</span><span class="nv">string</span><span class="p">:</span> <span class="n">title</span><span class="p">,</span> <span class="nv">attributes</span><span class="p">:</span> <span class="n">attributes</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">description</span><span class="p">(</span><span class="n">forEmptyDataSet</span> <span class="nv">scrollView</span><span class="p">:</span> <span class="kt">UIScrollView</span><span class="o">!</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">NSAttributedString</span><span class="o">!</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">title</span> <span class="o">=</span> <span class="kt">Strings</span><span class="o">.</span><span class="kt">MESSAGE_NO_ITEMS_PRESENT</span>
        <span class="k">let</span> <span class="nv">attributes</span> <span class="o">=</span> <span class="p">[</span><span class="kt">NSAttributedString</span><span class="o">.</span><span class="kt">Key</span><span class="o">.</span><span class="nv">font</span><span class="p">:</span> <span class="kt">UIFont</span><span class="o">.</span><span class="nf">preferredFont</span><span class="p">(</span><span class="nv">forTextStyle</span><span class="p">:</span> <span class="o">.</span><span class="n">body</span><span class="p">)]</span>
        <span class="k">return</span> <span class="kt">NSAttributedString</span><span class="p">(</span><span class="nv">string</span><span class="p">:</span> <span class="n">title</span><span class="p">,</span> <span class="nv">attributes</span><span class="p">:</span> <span class="n">attributes</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="c1">// MARK: - DZNEmptyDataSetDelegate</span>
    <span class="kd">func</span> <span class="nf">emptyDataSetShouldDisplay</span><span class="p">(</span><span class="n">_</span> <span class="nv">scrollView</span><span class="p">:</span> <span class="kt">UIScrollView</span><span class="o">!</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Bool</span> <span class="p">{</span>
        <span class="k">return</span> <span class="kc">true</span>
    <span class="p">}</span>

    <span class="c1">// MARK: - NSFetchedResultsControllerDelegate</span>
    <span class="kd">func</span> <span class="nf">controllerWillChangeContent</span><span class="p">(</span><span class="n">_</span> <span class="nv">controller</span><span class="p">:</span> <span class="kt">NSFetchedResultsController</span><span class="o">&lt;</span><span class="kt">NSFetchRequestResult</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">tableView</span><span class="o">.</span><span class="nf">beginUpdates</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">controller</span><span class="p">(</span><span class="n">_</span> <span class="nv">controller</span><span class="p">:</span> <span class="kt">NSFetchedResultsController</span><span class="o">&lt;</span><span class="kt">NSFetchRequestResult</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">didChange</span> <span class="nv">anObject</span><span class="p">:</span> <span class="kt">Any</span><span class="p">,</span> <span class="n">at</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">?,</span> <span class="k">for</span> <span class="nv">type</span><span class="p">:</span> <span class="kt">NSFetchedResultsChangeType</span><span class="p">,</span> <span class="nv">newIndexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">?)</span> <span class="p">{</span>
        <span class="k">switch</span> <span class="n">type</span> <span class="p">{</span>
        <span class="k">case</span> <span class="o">.</span><span class="nv">insert</span><span class="p">:</span>
            <span class="n">tableView</span><span class="o">.</span><span class="nf">insertRows</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="p">[</span><span class="n">newIndexPath</span><span class="o">!</span><span class="p">],</span> <span class="nv">with</span><span class="p">:</span> <span class="o">.</span><span class="n">fade</span><span class="p">)</span>
        <span class="k">case</span> <span class="o">.</span><span class="nv">delete</span><span class="p">:</span>
            <span class="n">tableView</span><span class="o">.</span><span class="nf">deleteRows</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="p">[</span><span class="n">indexPath</span><span class="o">!</span><span class="p">],</span> <span class="nv">with</span><span class="p">:</span> <span class="o">.</span><span class="n">fade</span><span class="p">)</span>
        <span class="k">case</span> <span class="o">.</span><span class="nv">update</span><span class="p">:</span>
            <span class="n">tableView</span><span class="o">.</span><span class="nf">reloadRows</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="p">[</span><span class="n">indexPath</span><span class="o">!</span><span class="p">],</span> <span class="nv">with</span><span class="p">:</span> <span class="o">.</span><span class="n">fade</span><span class="p">)</span>
        <span class="k">case</span> <span class="o">.</span><span class="nv">move</span><span class="p">:</span>
            <span class="n">tableView</span><span class="o">.</span><span class="nf">moveRow</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="n">indexPath</span><span class="o">!</span><span class="p">,</span> <span class="nv">to</span><span class="p">:</span> <span class="n">newIndexPath</span><span class="o">!</span><span class="p">)</span>
        <span class="kd">@unknown</span> <span class="k">default</span><span class="p">:</span>
            <span class="nf">fatalError</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">controllerDidChangeContent</span><span class="p">(</span><span class="n">_</span> <span class="nv">controller</span><span class="p">:</span> <span class="kt">NSFetchedResultsController</span><span class="o">&lt;</span><span class="kt">NSFetchRequestResult</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">tableView</span><span class="o">.</span><span class="nf">endUpdates</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="c1">// MARK: - DBEditPropertyControllerDelegate</span>
    <span class="kd">func</span> <span class="nf">controllerDidFinish</span><span class="p">(</span><span class="n">_</span> <span class="nv">controller</span><span class="p">:</span> <span class="kt">UIViewController</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nv">context</span><span class="p">:</span> <span class="kt">NSManagedObjectContext</span> <span class="o">=</span> <span class="p">(</span><span class="n">controller</span> <span class="k">as!</span> <span class="kt">DBStringEditController</span><span class="p">)</span><span class="o">.</span><span class="n">managedObjectContext</span><span class="p">,</span> <span class="n">context</span><span class="o">.</span><span class="n">hasChanges</span> <span class="p">{</span>
            <span class="k">do</span> <span class="p">{</span>
                <span class="k">try</span> <span class="n">context</span><span class="o">.</span><span class="nf">save</span><span class="p">()</span>
                <span class="nf">dismiss</span><span class="p">(</span><span class="nv">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nv">completion</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
            <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
                <span class="k">let</span> <span class="nv">nserror</span> <span class="o">=</span> <span class="n">error</span> <span class="k">as</span> <span class="kt">NSError</span>
                <span class="n">validationHelper</span><span class="o">.</span><span class="nf">showCoreDataValidationErrors</span><span class="p">(</span><span class="nv">error</span><span class="p">:</span> <span class="n">nserror</span><span class="p">,</span> <span class="nv">controller</span><span class="p">:</span> <span class="n">controller</span><span class="p">)</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">contorllerDidCancel</span><span class="p">(</span><span class="n">_</span> <span class="nv">controller</span><span class="p">:</span> <span class="kt">UIViewController</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">controller</span><span class="o">.</span><span class="nf">dismiss</span><span class="p">(</span><span class="nv">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="benefits">Benefits</h2>

<ul>
  <li>I went from multiple copies of almost the same code to only one class.</li>
  <li>I deleted numerous unit tests that were no longer needed.</li>
  <li>I left the door open for further subclassing and customization while still maintaining a very tight codebase. In example - I could derive a separate controller that deals only with <code class="language-plaintext highlighter-rouge">Gun</code> attributes or one only with <code class="language-plaintext highlighter-rouge">Ammo</code> attributes.</li>
</ul>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">GunPropertyEditController</span><span class="o">&lt;</span><span class="kt">T</span><span class="p">:</span> <span class="kt">NSManagedObject</span><span class="o">&gt;</span><span class="p">:</span> <span class="kt">DBEntityPropertyEditController</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span> <span class="p">{</span>

    <span class="kd">convenience</span> <span class="nf">init</span><span class="p">(</span>
        <span class="nv">gun</span><span class="p">:</span> <span class="kt">Gun</span><span class="p">,</span>
        <span class="nv">entityName</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span>
        <span class="nv">navigationItemTitle</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span>
        <span class="nv">addNewTitle</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span>
        <span class="nv">instructionsText</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span>
        <span class="nv">gunRelationshipKeyPath</span><span class="p">:</span> <span class="kt">String</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">parentObject</span><span class="p">:</span> <span class="n">gun</span><span class="p">,</span>
            <span class="nv">propertyEntityName</span><span class="p">:</span> <span class="n">entityName</span><span class="p">,</span>
            <span class="nv">navigationItemTitle</span><span class="p">:</span> <span class="n">navigationItemTitle</span><span class="p">,</span>
            <span class="nv">addNewTitle</span><span class="p">:</span> <span class="n">addNewTitle</span><span class="p">,</span>
            <span class="nv">instructionsText</span><span class="p">:</span> <span class="n">instructionsText</span><span class="p">,</span>
            <span class="nv">parentRelationshipKeyPath</span><span class="p">:</span> <span class="n">gunRelationshipKeyPath</span><span class="p">,</span>
            <span class="nv">propertyInverseRelationshipKeyPath</span><span class="p">:</span> <span class="s">"guns"</span>
        <span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>I could also go a step further and subclass one more time per attribute in case I wanted to do something different.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">GunTypeEditController</span><span class="p">:</span> <span class="kt">GunPropertyEditController</span><span class="o">&lt;</span><span class="kt">GunType</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="kd">convenience</span> <span class="nf">init</span><span class="p">(</span><span class="nv">gun</span><span class="p">:</span> <span class="kt">Gun</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">gun</span><span class="p">:</span> <span class="n">gun</span><span class="p">,</span>
            <span class="nv">entityName</span><span class="p">:</span> <span class="s">"GunType"</span><span class="p">,</span>
            <span class="nv">navigationItemTitle</span><span class="p">:</span> <span class="kt">Strings</span><span class="o">.</span><span class="kt">TITLE_SELECT_GUN_TYPE</span><span class="p">,</span>
            <span class="nv">addNewTitle</span><span class="p">:</span> <span class="kt">Strings</span><span class="o">.</span><span class="kt">TITLE_NEW_GUN_TYPE</span><span class="p">,</span>
            <span class="nv">instructionsText</span><span class="p">:</span> <span class="kt">Strings</span><span class="o">.</span><span class="kt">MESSAGE_EDIT_GUN_TYPE_INSTRUCTIONS</span><span class="p">,</span>
            <span class="nv">gunRelationshipKeyPath</span><span class="p">:</span> <span class="s">"type"</span>
        <span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Whatever the reason might be, this new base <code class="language-plaintext highlighter-rouge">DBEntityPropertyEditController</code> would handle all the boilerplate work, leaving the subclasses with the minimal task of providing their specific configurations.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Taking the time to refactor duplicated code is always a worthwhile investment. It pays dividends in reduced complexity, improved stability, and faster development down the road. So next time you find yourself reaching for copy-paste, take a moment to consider if a base class or another form of abstraction could save you from a future maintenance nightmare. Your future self will thank you.</p>]]></content><author><name></name></author><category term="projects" /><category term="gunvault" /><category term="swift" /><category term="ios" /><category term="core-data" /><summary type="html"><![CDATA[If you’ve worked on an iOS app of any significant size, you’ve likely encountered it: the copy-paste monster. It starts innocently. You build a view controller, get it working perfectly, and then you need another one that’s almost the same. You duplicate the file, change a few lines, and move on. But then you need another, and another. Before you know it, you’re the reluctant owner of a herd of nearly identical view controllers, and any change to one means a tedious and error-prone update to all the others. To be fair - there is the Rule of three (three strikes and you refactor), so some temporary duplication is very much okay as long as it is addressed in a timely manner.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://villyg.com/assets/images/Swift.jpg" /><media:content medium="image" url="http://villyg.com/assets/images/Swift.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Implementing client credentials grant flow with public/private key pair, client assertion, and JWKS in Node.js</title><link href="http://villyg.com/posts/OAuth-client-credentials-with-certificate-in-NodeJS" rel="alternate" type="text/html" title="Implementing client credentials grant flow with public/private key pair, client assertion, and JWKS in Node.js" /><published>2025-07-17T21:37:16+00:00</published><updated>2025-07-17T21:37:16+00:00</updated><id>http://villyg.com/posts/OAuth-client-credentials-with-certificate-in-NodeJS</id><content type="html" xml:base="http://villyg.com/posts/OAuth-client-credentials-with-certificate-in-NodeJS"><![CDATA[<p>This blog post will walk you through an implementation of the <code class="language-plaintext highlighter-rouge">client credentials</code> grant flow using a <code class="language-plaintext highlighter-rouge">client assertion</code>. We will build a system to create, sign, and validate JSON Web Tokens (JWTs) using public-private key pairs and JSON Web Key Sets (JWKS) in Node.js. We’ll use Node/Express for the server framework, Jose for JWT signing and validation, and Axios for HTTP requests. The system is composed of a bootstrapper and three microservices, orchestrated with Docker Compose for easy setup and execution.</p>

<!--more-->

<p>The workflow looks as follows:</p>

<pre class="mermaid">
sequenceDiagram
        participant Client
        participant Auth
        participant Api
        activate Client
        Client-&gt;&gt;Client: generate client_assertion
        Client-&gt;&gt;Client: sign client_assertion with private key
        Client-&gt;&gt;Auth: Exchange client assertion for access_token
        activate Auth        
        Auth-&gt;&gt;Auth: Verify client_assertion with public key
        Auth-&gt;&gt;Auth: Generate access_token
        Auth-&gt;&gt;Auth: Sign access_token with private key
        Auth-&gt;&gt;Client: return access_token
        deactivate Auth
        Client-&gt;&gt;Api: Get protected resource with access_token
        activate Api
        Api-&gt;&gt;Auth: Get public key from JWKS
        activate Auth
        Auth-&gt;&gt;Api: return public key
        deactivate Auth
        Api-&gt;&gt;Api: Verify access_token with public key
        Api-&gt;&gt;Client: Return protected resource
        deactivate Api
        deactivate Client
</pre>
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.9.1/dist/mermaid.min.js"></script>

<p>The codebase includes:</p>

<ol>
  <li>Bootstrapper: Generates a public-private key pair and a JWKS.</li>
  <li>Auth Server: Exposes routes to serve the JWKS and issue signed access tokens.</li>
  <li>API Server: Protects a route, validating access tokens using the Auth server’s JWKS.</li>
  <li>Client: Signs a client assertion and exchanges it for an access token from the Auth server to access the API’s protected resource.</li>
</ol>

<p>Below are step-by-step instructions, complete with sample code, to set up and run this system.</p>

<ul>
  <li><a href="#step-1-project-setup">Step 1: Project Setup</a></li>
  <li><a href="#step-2-bootstrapper---generating-keys-and-jwks">Step 2: Bootstrapper - Generating Keys and JWKS</a></li>
  <li><a href="#step-3-auth-server---serving-jwks-and-signing-jwts">Step 3: Auth Server - Serving JWKS and Signing JWTs</a></li>
  <li><a href="#step-4-api-server---protecting-a-route-with-jwt-validation">Step 4: API Server - Protecting a Route with JWT Validation</a></li>
  <li><a href="#step-5-client---fetching-token-and-accessing-protected-resource">Step 5: Client - Fetching Token and Accessing Protected Resource</a></li>
  <li><a href="#step-6-docker-compose-setup">Step 6: Docker Compose Setup</a></li>
  <li><a href="#step-7-running-the-system">Step 7: Running the System</a></li>
  <li><a href="#step-8-testing-manually">Step 8: Testing Manually</a></li>
  <li><a href="#conclusion">Conclusion</a></li>
</ul>

<h3 id="prerequisites">Prerequisites</h3>

<ul>
  <li>Node.js (v18 or higher)</li>
  <li>Docker and Docker Compose</li>
  <li>Basic understanding of JWTs, public-private key cryptography, and Node.js.</li>
</ul>

<h2 id="step-1-project-setup">Step 1: Project Setup</h2>

<p>Create a project directory with the following structure (files will be added in the steps below):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>node-jwks-demo/
├── bootstrapper/
│   ├── index.js
│   └── package.json
├── auth/
│   ├── index.js
│   └── package.json
├── api/
│   ├── index.js
│   └── package.json
├── client/
│   ├── index.js
│   └── package.json
├── docker-compose.yml
└── keys/
    ├── private.pem
    ├── public.pem
    └── jwks.json

</code></pre></div></div>

<p>We’ll use Docker Compose to run all services. The <code class="language-plaintext highlighter-rouge">keys/</code> directory will store the generated keys and JWKS.</p>

<h2 id="step-2-bootstrapper---generating-keys-and-jwks">Step 2: Bootstrapper - Generating Keys and JWKS</h2>

<p>The Bootstrapper generates an RSA key pair and a JWKS file using the <code class="language-plaintext highlighter-rouge">jose</code> library.</p>

<h3 id="code-for-bootstrapper">Code for Bootstrapper</h3>

<p>Create <code class="language-plaintext highlighter-rouge">bootstrapper/package.json</code>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bootstrapper"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"index.js"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"dependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"jose"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^6.0.12"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"start"</span><span class="p">:</span><span class="w"> </span><span class="s2">"node index.js"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"lint"</span><span class="p">:</span><span class="w"> </span><span class="s2">"eslint ."</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"module"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"devDependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"@eslint/js"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^9.31.0"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"eslint"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^9.31.0"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"globals"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^16.3.0"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"prettier"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3.6.2"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Create <code class="language-plaintext highlighter-rouge">bootstrapper/index.js</code>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">writeFileSync</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">fs</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">generateKeyPair</span><span class="p">,</span> <span class="nx">exportSPKI</span><span class="p">,</span> <span class="nx">exportPKCS8</span><span class="p">,</span> <span class="nx">exportJWK</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">jose</span><span class="dl">"</span><span class="p">;</span>

<span class="c1">// Define the path where keys will be saved</span>
<span class="kd">const</span> <span class="nx">path</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">../keys/</span><span class="dl">"</span><span class="p">;</span>

<span class="k">async</span> <span class="kd">function</span> <span class="nx">generateAndSaveKeys</span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// Generate a key pair (RSA or EC — we'll use RSA here)</span>
  <span class="kd">const</span> <span class="p">{</span> <span class="nx">publicKey</span><span class="p">,</span> <span class="nx">privateKey</span> <span class="p">}</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">generateKeyPair</span><span class="p">(</span><span class="dl">"</span><span class="s2">RS256</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span>
    <span class="na">modulusLength</span><span class="p">:</span> <span class="mi">2048</span><span class="p">,</span> <span class="c1">// recommended key size</span>
    <span class="na">extractable</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
  <span class="p">});</span>

  <span class="c1">// Export the public/private keys to PEM format</span>
  <span class="kd">const</span> <span class="nx">publicPem</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">exportKeyToPEM</span><span class="p">(</span><span class="nx">publicKey</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">privatePem</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">exportKeyToPEM</span><span class="p">(</span><span class="nx">privateKey</span><span class="p">);</span>

  <span class="c1">// Export the public key to JWK format</span>
  <span class="kd">const</span> <span class="nx">jwk</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">exportKeyToJWK</span><span class="p">(</span><span class="nx">publicKey</span><span class="p">);</span>

  <span class="c1">// 4. Optionally add desired metadata</span>
  <span class="nx">jwk</span><span class="p">.</span><span class="nx">use</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">sig</span><span class="dl">"</span><span class="p">;</span>
  <span class="nx">jwk</span><span class="p">.</span><span class="nx">alg</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">RS256</span><span class="dl">"</span><span class="p">;</span>
  <span class="nx">jwk</span><span class="p">.</span><span class="nx">kid</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">client-key</span><span class="dl">"</span><span class="p">;</span> <span class="c1">// optional, but recommended</span>

  <span class="c1">// Wrap in JWKS format</span>
  <span class="kd">const</span> <span class="nx">jwks</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">keys</span><span class="p">:</span> <span class="p">[</span><span class="nx">jwk</span><span class="p">],</span>
  <span class="p">};</span>

  <span class="c1">// Write keys to files</span>
  <span class="nx">writeFileSync</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">path</span><span class="p">}</span><span class="s2">public_key.pem`</span><span class="p">,</span> <span class="nx">publicPem</span><span class="p">);</span>
  <span class="nx">writeFileSync</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">path</span><span class="p">}</span><span class="s2">private_key.pem`</span><span class="p">,</span> <span class="nx">privatePem</span><span class="p">);</span>
  <span class="nx">writeFileSync</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">path</span><span class="p">}</span><span class="s2">jwks.json`</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">jwks</span><span class="p">));</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span>
    <span class="s2">`✅ Keys generated and saved to </span><span class="p">${</span><span class="nx">path</span><span class="p">}</span><span class="s2"> as public_key.pem and private_key.pem`</span>
  <span class="p">);</span>
<span class="p">}</span>

<span class="k">async</span> <span class="kd">function</span> <span class="nx">exportKeyToPEM</span><span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">pem</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">exportSPKI</span><span class="p">(</span><span class="nx">key</span><span class="p">).</span><span class="k">catch</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">exportPKCS8</span><span class="p">(</span><span class="nx">key</span><span class="p">));</span>
  <span class="k">return</span> <span class="nx">pem</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">async</span> <span class="kd">function</span> <span class="nx">exportKeyToJWK</span><span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">jwk</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">exportJWK</span><span class="p">(</span><span class="nx">key</span><span class="p">);</span>
  <span class="k">return</span> <span class="nx">jwk</span><span class="p">;</span>
<span class="p">}</span>

<span class="nx">generateAndSaveKeys</span><span class="p">().</span><span class="k">catch</span><span class="p">(</span><span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">);</span>
</code></pre></div></div>

<h3 id="explanation">Explanation</h3>

<ul>
  <li>The <code class="language-plaintext highlighter-rouge">jose</code> library generates an RSA key pair with the RS256 algorithm.</li>
  <li>The public and private keys are exported in PEM format and saved to <code class="language-plaintext highlighter-rouge">keys/</code>.</li>
  <li>The public key is converted to a JWK and wrapped in a JWKS object with a <code class="language-plaintext highlighter-rouge">kid</code> (key ID) for identification.</li>
</ul>

<h2 id="step-3-auth-server---serving-jwks-and-signing-jwts">Step 3: Auth Server - Serving JWKS and Signing JWTs</h2>

<p>The Auth server provides two endpoints:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">/jwks.json</code>: Returns the JWKS.</li>
  <li><code class="language-plaintext highlighter-rouge">/token</code>: Signs and returns a JWT.</li>
</ul>

<h3 id="code-for-auth-server">Code for Auth Server</h3>

<p>Create <code class="language-plaintext highlighter-rouge">auth/package.json</code>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"auth"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"index.js"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"dependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"body-parser"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^2.2.0"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"express"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^5.1.0"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"jose"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^6.0.12"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"start"</span><span class="p">:</span><span class="w"> </span><span class="s2">"node index.js"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"module"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"devDependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"@eslint/js"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^9.31.0"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"eslint"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^9.31.0"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"globals"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^16.3.0"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Create <code class="language-plaintext highlighter-rouge">auth/index.js</code>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">jwksjson</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../keys/jwks.json</span><span class="dl">"</span> <span class="kd">with</span> <span class="p">{</span> <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">json</span><span class="dl">"</span> <span class="p">};</span>
<span class="k">import</span> <span class="nx">express</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">express</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">jose</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">jose</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">fs</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">fs</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">bodyParser</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">body-parser</span><span class="dl">"</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">port</span> <span class="o">=</span> <span class="mi">3001</span><span class="p">;</span>

<span class="c1">// create application/x-www-form-urlencoded parser</span>
<span class="kd">const</span> <span class="nx">urlencodedParser</span> <span class="o">=</span> <span class="nx">bodyParser</span><span class="p">.</span><span class="nx">urlencoded</span><span class="p">();</span>

<span class="kd">const</span> <span class="nx">pathToPrivateKeyFile</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URL</span><span class="p">(</span><span class="dl">"</span><span class="s2">../keys/private_key.pem</span><span class="dl">"</span><span class="p">,</span> <span class="k">import</span><span class="p">.</span><span class="nx">meta</span><span class="p">.</span><span class="nx">url</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">pathname</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">privateKeyFile</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="nx">pathToPrivateKeyFile</span><span class="p">,</span> <span class="dl">"</span><span class="s2">utf8</span><span class="dl">"</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">privateKey</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">jose</span><span class="p">.</span><span class="nx">importPKCS8</span><span class="p">(</span><span class="nx">privateKeyFile</span><span class="p">,</span> <span class="dl">"</span><span class="s2">RS256</span><span class="dl">"</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">pathToPublicKeyFile</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URL</span><span class="p">(</span><span class="dl">"</span><span class="s2">../keys/public_key.pem</span><span class="dl">"</span><span class="p">,</span> <span class="k">import</span><span class="p">.</span><span class="nx">meta</span><span class="p">.</span><span class="nx">url</span><span class="p">)</span>
  <span class="p">.</span><span class="nx">pathname</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">publicKeyFile</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="nx">pathToPublicKeyFile</span><span class="p">,</span> <span class="dl">"</span><span class="s2">utf8</span><span class="dl">"</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">publicKey</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">jose</span><span class="p">.</span><span class="nx">importSPKI</span><span class="p">(</span><span class="nx">publicKeyFile</span><span class="p">,</span> <span class="dl">"</span><span class="s2">RS256</span><span class="dl">"</span><span class="p">);</span>

<span class="nx">app</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="dl">"</span><span class="s2">/token</span><span class="dl">"</span><span class="p">,</span> <span class="nx">urlencodedParser</span><span class="p">,</span> <span class="k">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="dl">"</span><span class="s2">Received request to /token endpoint</span><span class="dl">"</span><span class="p">);</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="dl">"</span><span class="s2">Validating request body...</span><span class="dl">"</span><span class="p">);</span>

  <span class="c1">// Check to see if the request body is present</span>
  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">"</span><span class="s2">Request body is missing</span><span class="dl">"</span><span class="p">);</span>
    <span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">400</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="na">error</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Request body is required</span><span class="dl">"</span> <span class="p">});</span>
  <span class="p">}</span>

  <span class="c1">// Check to see if the client_id is present in the request body</span>
  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">client_id</span> <span class="o">&amp;&amp;</span> <span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">client_id</span> <span class="o">!==</span> <span class="dl">"</span><span class="s2">client</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// ADD additional check for valid client_id here if needed</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">"</span><span class="s2">Missing or invalid client_id in request body</span><span class="dl">"</span><span class="p">);</span>
    <span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">400</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="na">error</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Missing or invalid client_id</span><span class="dl">"</span> <span class="p">});</span>
  <span class="p">}</span>

  <span class="c1">// Check to see if the client_assertion_type is present in the request body</span>
  <span class="k">if</span> <span class="p">(</span>
    <span class="o">!</span><span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">client_assertion_type</span> <span class="o">&amp;&amp;</span>
    <span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">client_assertion_type</span> <span class="o">!==</span>
      <span class="dl">"</span><span class="s2">urn:ietf:params:oauth:client-assertion-type:jwt-bearer</span><span class="dl">"</span>
  <span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">"</span><span class="s2">Missing or invalid client_assertion_type in request body</span><span class="dl">"</span><span class="p">);</span>
    <span class="k">return</span> <span class="nx">res</span>
      <span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">400</span><span class="p">)</span>
      <span class="p">.</span><span class="nx">json</span><span class="p">({</span> <span class="na">error</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Missing or invalid client_assertion_type</span><span class="dl">"</span> <span class="p">});</span>
  <span class="p">}</span>

  <span class="c1">// Check to see if the client_assertion is present in the request body</span>
  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">client_assertion</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">"</span><span class="s2">Missing client_assertion in request body</span><span class="dl">"</span><span class="p">);</span>
    <span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">400</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="na">error</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Missing client_assertion</span><span class="dl">"</span> <span class="p">});</span>
  <span class="p">}</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="dl">"</span><span class="s2">Request body validation successful</span><span class="dl">"</span><span class="p">);</span>

  <span class="kd">const</span> <span class="nx">client_assertion</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">client_assertion</span><span class="p">;</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">client_assertion:</span><span class="dl">"</span><span class="p">,</span> <span class="nx">client_assertion</span><span class="p">);</span>

  <span class="c1">// Verify the client_assertion</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="dl">"</span><span class="s2">Verifying client_assertion...</span><span class="dl">"</span><span class="p">);</span>
  <span class="k">try</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">payload</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">jose</span><span class="p">.</span><span class="nx">jwtVerify</span><span class="p">(</span><span class="nx">client_assertion</span><span class="p">,</span> <span class="nx">publicKey</span><span class="p">,</span> <span class="p">{</span>
      <span class="na">issuer</span><span class="p">:</span> <span class="dl">"</span><span class="s2">client</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">audience</span><span class="p">:</span> <span class="dl">"</span><span class="s2">auth</span><span class="dl">"</span><span class="p">,</span>
    <span class="p">});</span>

    <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="dl">"</span><span class="s2">client_assertion successfully verified</span><span class="dl">"</span><span class="p">);</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Verified client_assertion payload:</span><span class="dl">"</span><span class="p">,</span> <span class="nx">payload</span><span class="p">);</span>

    <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="dl">"</span><span class="s2">Generating access token...</span><span class="dl">"</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">access_token</span> <span class="o">=</span> <span class="k">await</span> <span class="k">new</span> <span class="nx">jose</span><span class="p">.</span><span class="nx">SignJWT</span><span class="p">({</span>
      <span class="na">sub</span><span class="p">:</span> <span class="nx">payload</span><span class="p">.</span><span class="nx">sub</span><span class="p">,</span>
      <span class="na">user</span><span class="p">:</span> <span class="dl">"</span><span class="s2">exampleUser</span><span class="dl">"</span><span class="p">,</span>
    <span class="p">})</span>
      <span class="p">.</span><span class="nx">setProtectedHeader</span><span class="p">({</span> <span class="na">alg</span><span class="p">:</span> <span class="dl">"</span><span class="s2">RS256</span><span class="dl">"</span><span class="p">,</span> <span class="na">kid</span><span class="p">:</span> <span class="dl">"</span><span class="s2">client-key</span><span class="dl">"</span> <span class="p">})</span>
      <span class="p">.</span><span class="nx">setIssuedAt</span><span class="p">()</span>
      <span class="p">.</span><span class="nx">setIssuer</span><span class="p">(</span><span class="dl">"</span><span class="s2">auth</span><span class="dl">"</span><span class="p">)</span>
      <span class="p">.</span><span class="nx">setAudience</span><span class="p">(</span><span class="dl">"</span><span class="s2">api</span><span class="dl">"</span><span class="p">)</span>
      <span class="p">.</span><span class="nx">setExpirationTime</span><span class="p">(</span><span class="dl">"</span><span class="s2">2h</span><span class="dl">"</span><span class="p">)</span>
      <span class="p">.</span><span class="nx">sign</span><span class="p">(</span><span class="nx">privateKey</span><span class="p">);</span>

    <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="dl">"</span><span class="s2">Access token generated successfully</span><span class="dl">"</span><span class="p">);</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="dl">"</span><span class="s2">Returning access token to client...</span><span class="dl">"</span><span class="p">);</span>
    <span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">({</span> <span class="na">token</span><span class="p">:</span> <span class="nx">access_token</span> <span class="p">});</span>
  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">"</span><span class="s2">Invalid client_assertion:</span><span class="dl">"</span><span class="p">,</span> <span class="nx">err</span><span class="p">.</span><span class="nx">message</span><span class="p">);</span>
    <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">401</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="na">error</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Invalid client_assertion</span><span class="dl">"</span> <span class="p">});</span>
  <span class="p">}</span>
<span class="p">});</span>

<span class="cm">/**
 * Endpoint to serve the JWKS (JSON Web Key Set) for public key retrieval.
 * This is typically used for verifying JWTs (JSON Web Tokens).
 */</span>
<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">/.well-known/jwks.json</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">res</span><span class="p">.</span><span class="nx">setHeader</span><span class="p">(</span><span class="dl">"</span><span class="s2">Content-Type</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">application/json</span><span class="dl">"</span><span class="p">);</span>
  <span class="nx">res</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="nx">jwksjson</span><span class="p">);</span>
<span class="p">});</span>

<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">/health</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="na">status</span><span class="p">:</span> <span class="dl">"</span><span class="s2">ok</span><span class="dl">"</span> <span class="p">});</span>
<span class="p">});</span>

<span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="nx">port</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Auth server running on port </span><span class="p">${</span><span class="nx">port</span><span class="p">}</span><span class="s2">`</span><span class="p">));</span>
</code></pre></div></div>

<h3 id="explanation-1">Explanation</h3>

<ul>
  <li>The <code class="language-plaintext highlighter-rouge">/.well-known/jwks.json</code> endpoint serves the JWKS file generated by the Bootstrapper.</li>
  <li>The <code class="language-plaintext highlighter-rouge">/token</code> endpoint verifies a <code class="language-plaintext highlighter-rouge">client assertion</code> and then signs and returns an <code class="language-plaintext highlighter-rouge">access token</code> with the private key, including a subject (<code class="language-plaintext highlighter-rouge">sub</code>), scope, and expiration.</li>
  <li>The <code class="language-plaintext highlighter-rouge">kid</code> in the JWT header matches the JWKS for validation.</li>
</ul>

<h2 id="step-4-api-server---protecting-a-route-with-jwt-validation">Step 4: API Server - Protecting a Route with JWT Validation</h2>

<p>The API server validates incoming JWTs using the Auth server’s JWKS.</p>

<h3 id="code-for-api-server">Code for API Server</h3>

<p>Create <code class="language-plaintext highlighter-rouge">api/package.json</code></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"api"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"index.js"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"dependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"express"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^5.1.0"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"jose"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^6.0.12"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"start"</span><span class="p">:</span><span class="w"> </span><span class="s2">"node index.js"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"module"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"devDependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"@eslint/js"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^9.31.0"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"eslint"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^9.31.0"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"globals"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^16.3.0"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Create <code class="language-plaintext highlighter-rouge">api/index.js</code>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">express</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">express</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">jose</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">jose</span><span class="dl">"</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">port</span> <span class="o">=</span> <span class="mi">3002</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">JWKS_URL</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">http://auth:3001/.well-known/jwks.json</span><span class="dl">"</span><span class="p">;</span>

<span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="dl">"</span><span class="s2">Retrieving JWKS...</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">clientJWKSet</span> <span class="o">=</span> <span class="nx">jose</span><span class="p">.</span><span class="nx">createRemoteJWKSet</span><span class="p">(</span><span class="k">new</span> <span class="nx">URL</span><span class="p">(</span><span class="nx">JWKS_URL</span><span class="p">));</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="dl">"</span><span class="s2">JWKS successfully retrieved</span><span class="dl">"</span><span class="p">);</span>

<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">/protected</span><span class="dl">"</span><span class="p">,</span> <span class="k">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">authHeader</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">authorization</span><span class="p">;</span>
  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">authHeader</span> <span class="o">||</span> <span class="o">!</span><span class="nx">authHeader</span><span class="p">.</span><span class="nx">startsWith</span><span class="p">(</span><span class="dl">"</span><span class="s2">Bearer </span><span class="dl">"</span><span class="p">))</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">res</span>
      <span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">401</span><span class="p">)</span>
      <span class="p">.</span><span class="nx">json</span><span class="p">({</span> <span class="na">error</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Missing or invalid Authorization header</span><span class="dl">"</span> <span class="p">});</span>
  <span class="p">}</span>

  <span class="kd">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="nx">authHeader</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">"</span><span class="s2"> </span><span class="dl">"</span><span class="p">)[</span><span class="mi">1</span><span class="p">];</span>
  <span class="k">try</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="p">{</span> <span class="nx">payload</span> <span class="p">}</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">jose</span><span class="p">.</span><span class="nx">jwtVerify</span><span class="p">(</span><span class="nx">token</span><span class="p">,</span> <span class="nx">clientJWKSet</span><span class="p">,</span> <span class="p">{</span>
      <span class="na">algorithms</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">RS256</span><span class="dl">"</span><span class="p">],</span>
    <span class="p">});</span>
    <span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">({</span> <span class="na">message</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Protected resource accessed</span><span class="dl">"</span><span class="p">,</span> <span class="na">user</span><span class="p">:</span> <span class="nx">payload</span><span class="p">.</span><span class="nx">sub</span> <span class="p">});</span>
  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">"</span><span class="s2">JWT verification error:</span><span class="dl">"</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
    <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">401</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="na">error</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Invalid or expired token</span><span class="dl">"</span> <span class="p">});</span>
  <span class="p">}</span>
<span class="p">});</span>

<span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="nx">port</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`API server running on port </span><span class="p">${</span><span class="nx">port</span><span class="p">}</span><span class="s2">`</span><span class="p">));</span>
</code></pre></div></div>

<h3 id="explanation-2">Explanation</h3>

<ul>
  <li>The <code class="language-plaintext highlighter-rouge">/protected</code> endpoint checks for a Bearer token in the Authorization header.</li>
  <li>The <code class="language-plaintext highlighter-rouge">jose</code> library’s <code class="language-plaintext highlighter-rouge">createRemoteJWKSet</code> fetches the JWKS from the Auth server to validate the token.</li>
  <li>If valid, the endpoint returns a success message with the token’s subject.</li>
</ul>

<h2 id="step-5-client---fetching-token-and-accessing-protected-resource">Step 5: Client - Fetching Token and Accessing Protected Resource</h2>

<p>The Client exchanges a signed <code class="language-plaintext highlighter-rouge">client assertion</code> for an <code class="language-plaintext highlighter-rouge">access token</code> from the Auth server and uses it to access the API’s protected route.</p>

<h3 id="code-for-client">Code for Client</h3>

<p>Create <code class="language-plaintext highlighter-rouge">client/package.json</code>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"client"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"index.js"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"dependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"axios"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^1.6.7"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"start"</span><span class="p">:</span><span class="w"> </span><span class="s2">"node index.js"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Create <code class="language-plaintext highlighter-rouge">client/index.js</code>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">axios</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">axios</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">jose</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">jose</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">fs</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">fs</span><span class="dl">"</span><span class="p">;</span>

<span class="k">async</span> <span class="kd">function</span> <span class="nx">runClient</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="dl">"</span><span class="s2">Generating client assertion...</span><span class="dl">"</span><span class="p">);</span>

  <span class="kd">const</span> <span class="nx">pathToPrivateKeyFile</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URL</span><span class="p">(</span>
    <span class="dl">"</span><span class="s2">../keys/private_key.pem</span><span class="dl">"</span><span class="p">,</span>
    <span class="k">import</span><span class="p">.</span><span class="nx">meta</span><span class="p">.</span><span class="nx">url</span>
  <span class="p">).</span><span class="nx">pathname</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">privateKeyFile</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="nx">pathToPrivateKeyFile</span><span class="p">,</span> <span class="dl">"</span><span class="s2">utf8</span><span class="dl">"</span><span class="p">);</span>

  <span class="kd">const</span> <span class="nx">privateKey</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">jose</span><span class="p">.</span><span class="nx">importPKCS8</span><span class="p">(</span><span class="nx">privateKeyFile</span><span class="p">,</span> <span class="dl">"</span><span class="s2">RS256</span><span class="dl">"</span><span class="p">);</span>

  <span class="kd">const</span> <span class="nx">now</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()</span> <span class="o">/</span> <span class="mi">1000</span><span class="p">);</span>

  <span class="kd">const</span> <span class="nx">clientAssertion</span> <span class="o">=</span> <span class="k">await</span> <span class="k">new</span> <span class="nx">jose</span><span class="p">.</span><span class="nx">SignJWT</span><span class="p">({</span> <span class="na">sub</span><span class="p">:</span> <span class="dl">"</span><span class="s2">client</span><span class="dl">"</span> <span class="p">})</span>
    <span class="p">.</span><span class="nx">setProtectedHeader</span><span class="p">({</span> <span class="na">alg</span><span class="p">:</span> <span class="dl">"</span><span class="s2">RS256</span><span class="dl">"</span> <span class="p">})</span>
    <span class="p">.</span><span class="nx">setIssuedAt</span><span class="p">(</span><span class="nx">now</span><span class="p">)</span>
    <span class="p">.</span><span class="nx">setIssuer</span><span class="p">(</span><span class="dl">"</span><span class="s2">client</span><span class="dl">"</span><span class="p">)</span>
    <span class="p">.</span><span class="nx">setAudience</span><span class="p">(</span><span class="dl">"</span><span class="s2">auth</span><span class="dl">"</span><span class="p">)</span>
    <span class="p">.</span><span class="nx">setExpirationTime</span><span class="p">(</span><span class="dl">"</span><span class="s2">5m</span><span class="dl">"</span><span class="p">)</span>
    <span class="p">.</span><span class="nx">sign</span><span class="p">(</span><span class="nx">privateKey</span><span class="p">);</span>

  <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="dl">"</span><span class="s2">Client Assertion generated successfully</span><span class="dl">"</span><span class="p">);</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Client Assertion:</span><span class="dl">"</span><span class="p">,</span> <span class="nx">clientAssertion</span><span class="p">);</span>

  <span class="k">try</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span>
      <span class="dl">"</span><span class="s2">Exchanging client assertion for access_token from Auth server...</span><span class="dl">"</span>
    <span class="p">);</span>

    <span class="kd">const</span> <span class="nx">tokenResponse</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">axios</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span>
      <span class="dl">"</span><span class="s2">http://auth:3001/token</span><span class="dl">"</span><span class="p">,</span>
      <span class="p">{</span>
        <span class="na">client_assertion</span><span class="p">:</span> <span class="nx">clientAssertion</span><span class="p">,</span>
        <span class="na">client_assertion_type</span><span class="p">:</span>
          <span class="dl">"</span><span class="s2">urn:ietf:params:oauth:client-assertion-type:jwt-bearer</span><span class="dl">"</span><span class="p">,</span>
        <span class="na">client_id</span><span class="p">:</span> <span class="dl">"</span><span class="s2">client</span><span class="dl">"</span><span class="p">,</span>
      <span class="p">},</span>
      <span class="p">{</span>
        <span class="na">headers</span><span class="p">:</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">Content-Type</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">application/x-www-form-urlencoded</span><span class="dl">"</span> <span class="p">},</span>
      <span class="p">}</span>
    <span class="p">);</span>

    <span class="kd">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="nx">tokenResponse</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">token</span><span class="p">;</span>

    <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="dl">"</span><span class="s2">Access_token received successfully:</span><span class="dl">"</span><span class="p">);</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Token:</span><span class="dl">"</span><span class="p">,</span> <span class="nx">token</span><span class="p">);</span>

    <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="dl">"</span><span class="s2">Accessing protected API...</span><span class="dl">"</span><span class="p">);</span>

    <span class="kd">const</span> <span class="nx">apiResponse</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">axios</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">http://api:3002/protected</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span>
      <span class="na">headers</span><span class="p">:</span> <span class="p">{</span> <span class="na">Authorization</span><span class="p">:</span> <span class="s2">`Bearer </span><span class="p">${</span><span class="nx">token</span><span class="p">}</span><span class="s2">`</span> <span class="p">},</span>
    <span class="p">});</span>

    <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="dl">"</span><span class="s2">Protected API accessed successfully</span><span class="dl">"</span><span class="p">);</span>

    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">API Response received:</span><span class="dl">"</span><span class="p">,</span> <span class="nx">apiResponse</span><span class="p">.</span><span class="nx">data</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span>
      <span class="dl">"</span><span class="s2">Client error:</span><span class="dl">"</span><span class="p">,</span>
      <span class="nx">error</span><span class="p">.</span><span class="nx">response</span> <span class="p">?</span> <span class="nx">error</span><span class="p">.</span><span class="nx">response</span><span class="p">.</span><span class="nx">data</span> <span class="p">:</span> <span class="nx">error</span><span class="p">.</span><span class="nx">message</span>
    <span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">runClient</span><span class="p">();</span>
</code></pre></div></div>

<h3 id="explanation-3">Explanation</h3>

<ul>
  <li>The Client uses <code class="language-plaintext highlighter-rouge">jose</code> to sign a <code class="language-plaintext highlighter-rouge">client assertion</code>.</li>
  <li>The Client uses <code class="language-plaintext highlighter-rouge">axios</code> to exchange the <code class="language-plaintext highlighter-rouge">client assertion</code> for an <code class="language-plaintext highlighter-rouge">access token</code> from the Auth server’s <code class="language-plaintext highlighter-rouge">/token</code> endpoint.</li>
  <li>It then sends the <code class="language-plaintext highlighter-rouge">access token</code> in the Authorization header to the API’s <code class="language-plaintext highlighter-rouge">/protected</code> endpoint.</li>
</ul>

<h2 id="step-6-docker-compose-setup">Step 6: Docker Compose Setup</h2>

<p>Create <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> in the root directory to orchestrate the services:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">services</span><span class="pi">:</span>
  <span class="na">bootstrapper</span><span class="pi">:</span>
    <span class="na">build</span><span class="pi">:</span> <span class="s">./bootstrapper</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">./keys:/keys</span>
    <span class="na">command</span><span class="pi">:</span> <span class="s">npm start</span>
    <span class="na">profiles</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">bootstrap</span>
  <span class="na">auth</span><span class="pi">:</span>
    <span class="na">build</span><span class="pi">:</span> <span class="s">./auth</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">3001:3001"</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">./keys:/keys</span>
    <span class="na">command</span><span class="pi">:</span> <span class="s">npm start</span>
    <span class="na">profiles</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">stack</span>
  <span class="na">api</span><span class="pi">:</span>
    <span class="na">build</span><span class="pi">:</span> <span class="s">./api</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">3002:3002"</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">./keys:/keys</span>
    <span class="na">depends_on</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">auth</span>
    <span class="na">command</span><span class="pi">:</span> <span class="s">npm start</span>
    <span class="na">profiles</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">stack</span>
  <span class="na">client</span><span class="pi">:</span>
    <span class="na">build</span><span class="pi">:</span> <span class="s">./client</span>
    <span class="na">depends_on</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">api</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">./keys:/keys</span>
    <span class="na">command</span><span class="pi">:</span> <span class="s">npm start</span>
    <span class="na">profiles</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">stack</span>
</code></pre></div></div>

<h3 id="explanation-4">Explanation</h3>

<ul>
  <li>Each service is built from its respective directory.</li>
  <li>The <code class="language-plaintext highlighter-rouge">keys/</code> directory is shared as a volume to persist keys and JWKS.</li>
  <li>The <code class="language-plaintext highlighter-rouge">bootstrap</code> and <code class="language-plaintext highlighter-rouge">stack</code> profiles are used to organize the components.</li>
</ul>

<h2 id="step-7-running-the-system">Step 7: Running the System</h2>

<ul>
  <li>Ensure Docker is running.</li>
  <li>In the <code class="language-plaintext highlighter-rouge">jwt-demo/</code> directory, run the following command to generate the keys:</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-compose <span class="nt">--profile</span> bootstrap up <span class="nt">--build</span> <span class="nt">--force-recreate</span>
</code></pre></div></div>

<ul>
  <li>In the <code class="language-plaintext highlighter-rouge">jwt-demo/</code> directory, run the following command to spin up the stack:</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-compose <span class="nt">--profile</span> stack up <span class="nt">--build</span> <span class="nt">--force-recreate</span>
</code></pre></div></div>

<h3 id="expected-output">Expected Output</h3>

<ul>
  <li>The Bootstrapper logs: <code class="language-plaintext highlighter-rouge">Keys and JWKS generated successfully</code>.</li>
  <li>The Auth server logs: <code class="language-plaintext highlighter-rouge">Auth server running on port 3001</code></li>
  <li>The API server logs: <code class="language-plaintext highlighter-rouge">API server running on port 3002</code></li>
  <li>The Client logs: <code class="language-plaintext highlighter-rouge">API Response: { message: 'Protected resource accessed' }</code></li>
</ul>

<h2 id="step-8-testing-manually">Step 8: Testing Manually</h2>

<p>You can test the system using <code class="language-plaintext highlighter-rouge">curl</code> or a tool like Postman:</p>

<p>Get the JWKS:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl http://localhost:3001/jwks.json
</code></pre></div></div>

<p>Get a token:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl http://localhost:3001/token
</code></pre></div></div>

<p>Access the protected resource:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-H</span> <span class="s2">"Authorization: Bearer &lt;token&gt;"</span> http://localhost:3002/protected
</code></pre></div></div>

<h2 id="conclusion">Conclusion</h2>

<p>This system demonstrates how to use public-private key pairs, JWKS and client assertion for secure JWT handling in a Node.js environment. The <code class="language-plaintext highlighter-rouge">jose</code> library simplifies key management and JWT operations, while Docker Compose ensures easy setup. You can extend this by adding more robust error handling, token refresh mechanisms, or additional protected routes.</p>

<p>For the full codebase, check out the <a href="https://github.com/villyg/node-jwks-demo">GitHub repository</a>.</p>]]></content><author><name></name></author><category term="nodejs" /><category term="security" /><category term="oauth" /><summary type="html"><![CDATA[This blog post will walk you through an implementation of the client credentials grant flow using a client assertion. We will build a system to create, sign, and validate JSON Web Tokens (JWTs) using public-private key pairs and JSON Web Key Sets (JWKS) in Node.js. We’ll use Node/Express for the server framework, Jose for JWT signing and validation, and Axios for HTTP requests. The system is composed of a bootstrapper and three microservices, orchestrated with Docker Compose for easy setup and execution.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://villyg.com/assets/images/NodeJS.png" /><media:content medium="image" url="http://villyg.com/assets/images/NodeJS.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Auto-sizing UIImage on top of UITableView during rotation</title><link href="http://villyg.com/projects/gunvault/2024/12/27/Auto-sizing-uiimage-on-top-of-uitableview-during-rotation.html" rel="alternate" type="text/html" title="Auto-sizing UIImage on top of UITableView during rotation" /><published>2024-12-27T21:37:16+00:00</published><updated>2024-12-27T21:37:16+00:00</updated><id>http://villyg.com/projects/gunvault/2024/12/27/Auto-sizing-uiimage-on-top-of-uitableview-during-rotation</id><content type="html" xml:base="http://villyg.com/projects/gunvault/2024/12/27/Auto-sizing-uiimage-on-top-of-uitableview-during-rotation.html"><![CDATA[<p>As part of version 1.2 of Gun Vault I decided to include the functionality of having the primary image of a gun included on top of the Gun Details screen. I wanted the image to take 1/3 of the screen when the device is in portrait mode …</p>

<!--more-->

<p><img src="/assets/images/2024-12-27-Auto-sizing-uiimage-on-top-of-uitableview-during-rotation/snapshot-portrait.png" alt="Portrait" /></p>

<p>and full screen when in landscape mode.</p>

<p><img src="/assets/images/2024-12-27-Auto-sizing-uiimage-on-top-of-uitableview-during-rotation/snapshot-landscape.png" alt="Full screen" /></p>

<p>I thought this would be a simple task until I ended up spending over 2 hrs trying to fine-tune it so I figured I’d write about it.</p>

<ul>
  <li><a href="#set-up">Set Up</a></li>
  <li><a href="#primary-image">Primary image</a></li>
  <li><a href="#rotation-support">Rotation support</a></li>
</ul>

<h2 id="set-up">Set up</h2>

<p>We’ll keep it very simple - 1 grouped-style table with 3 sections and 5 rows inside each section.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">import</span> <span class="kt">UIKit</span>
<span class="kd">class</span> <span class="kt">ViewController</span><span class="p">:</span> <span class="kt">UITableViewController</span> <span class="p">{</span>

    <span class="kd">convenience</span> <span class="nf">init</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">style</span><span class="p">:</span> <span class="kt">UITableViewStyle</span><span class="o">.</span><span class="n">grouped</span><span class="p">)</span>

    <span class="p">}</span>

    <span class="k">override</span> <span class="kd">func</span> <span class="nf">viewDidLoad</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">super</span><span class="o">.</span><span class="nf">viewDidLoad</span><span class="p">()</span>
        <span class="k">self</span><span class="o">.</span><span class="n">navigationItem</span><span class="o">.</span><span class="n">title</span> <span class="o">=</span> <span class="s">"Gun details"</span>

    <span class="p">}</span>


    <span class="c1">// ***********************************************</span>
    <span class="c1">// MARK: - UITableViewDataSource</span>
    <span class="c1">// ***********************************************</span>



    <span class="k">override</span> <span class="kd">func</span> <span class="nf">numberOfSections</span><span class="p">(</span><span class="k">in</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Int</span> <span class="p">{</span>

        <span class="k">return</span> <span class="mi">3</span>

    <span class="p">}</span>


    <span class="k">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="n">_</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">,</span> <span class="n">numberOfRowsInSection</span> <span class="nv">section</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Int</span> <span class="p">{</span>

        <span class="k">return</span> <span class="mi">5</span>

    <span class="p">}</span>


    <span class="k">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="n">_</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">,</span> <span class="n">cellForRowAt</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">UITableViewCell</span> <span class="p">{</span>

        <span class="c1">// since it is a static view we don't care about recycling at this time</span>
        <span class="k">let</span> <span class="nv">result</span><span class="p">:</span> <span class="kt">UITableViewCell</span> <span class="o">=</span> <span class="kt">UITableViewCell</span><span class="p">(</span><span class="nv">style</span><span class="p">:</span> <span class="o">.</span><span class="n">value1</span><span class="p">,</span> <span class="nv">reuseIdentifier</span><span class="p">:</span> <span class="s">"cell"</span><span class="p">)</span>

        <span class="n">result</span><span class="o">.</span><span class="n">textLabel</span><span class="p">?</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="s">"Text for row at: </span><span class="se">\(</span><span class="n">indexPath</span><span class="o">.</span><span class="n">row</span><span class="se">)</span><span class="s">"</span>
        <span class="n">result</span><span class="o">.</span><span class="n">detailTextLabel</span><span class="p">?</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="s">"Details for row at: </span><span class="se">\(</span><span class="n">indexPath</span><span class="o">.</span><span class="n">row</span><span class="se">)</span><span class="s">"</span>

        <span class="k">return</span> <span class="n">result</span>

    <span class="p">}</span>

<span class="p">}</span>
</code></pre></div></div>

<h2 id="primary-image">Primary image</h2>

<p>In order to add a primary image on top we need to override 2 methods: <code class="language-plaintext highlighter-rouge">heightForHeaderInSection</code> and <code class="language-plaintext highlighter-rouge">viewForHeaderInSection</code>. The first one controlls the height of placeholder we are going to use for the image and the second one provides the actual image to be displayed.</p>

<p>Note that we are not using the device orientation but the statusBar orientation instead. This is because while the device can be rotated - the interface could be locked for rotation in the same time.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="n">_</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">,</span> <span class="n">heightForHeaderInSection</span> <span class="nv">section</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">CGFloat</span> <span class="p">{</span>

    <span class="k">if</span> <span class="n">section</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">orientation</span> <span class="o">=</span> <span class="kt">UIApplication</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="n">statusBarOrientation</span>

        <span class="k">if</span> <span class="n">orientation</span><span class="o">.</span><span class="n">isLandscape</span> <span class="p">{</span>
            <span class="k">return</span> <span class="n">view</span><span class="o">.</span><span class="n">frame</span><span class="o">.</span><span class="n">height</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">return</span> <span class="n">view</span><span class="o">.</span><span class="n">frame</span><span class="o">.</span><span class="n">height</span> <span class="o">/</span> <span class="mi">3</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="kt">UITableViewAutomaticDimension</span>

<span class="p">}</span>
</code></pre></div></div>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">override</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="n">_</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">,</span> <span class="n">viewForHeaderInSection</span> <span class="nv">section</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">UIView</span><span class="p">?</span> <span class="p">{</span>

    <span class="c1">// We'll assume that there is only one section for now.</span>

    <span class="k">if</span> <span class="n">section</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>

        <span class="k">let</span> <span class="nv">imageView</span><span class="p">:</span> <span class="kt">UIImageView</span> <span class="o">=</span> <span class="kt">UIImageView</span><span class="p">()</span>
        <span class="n">imageView</span><span class="o">.</span><span class="n">clipsToBounds</span> <span class="o">=</span> <span class="kc">true</span>
        <span class="n">imageView</span><span class="o">.</span><span class="n">contentMode</span> <span class="o">=</span> <span class="o">.</span><span class="n">scaleAspectFill</span>
        <span class="n">imageView</span><span class="o">.</span><span class="n">image</span> <span class="o">=</span>  <span class="kt">UIImage</span><span class="p">(</span><span class="nv">named</span><span class="p">:</span> <span class="s">"gun"</span><span class="p">)</span><span class="o">!</span>
        <span class="k">return</span> <span class="n">imageView</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="kc">nil</span>

<span class="p">}</span>

</code></pre></div></div>

<h2 id="rotation-support">Rotation support</h2>

<p>The last thing that is left to add it the rotation support. Note that there are 3 phases that allow hooks. For our purpose we’ll use the before rotation and during rotation to invalidate the table layout. My first try was actually adding <code class="language-plaintext highlighter-rouge">tableView.reloadData()</code> instead but it resulted in a flicker of the screen so <code class="language-plaintext highlighter-rouge">beginUpdates()</code> and <code class="language-plaintext highlighter-rouge">endUpdates()</code> ended up being the better choice.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">override</span> <span class="kd">func</span> <span class="nf">viewWillTransition</span><span class="p">(</span><span class="n">to</span> <span class="nv">size</span><span class="p">:</span> <span class="kt">CGSize</span><span class="p">,</span> <span class="n">with</span> <span class="nv">coordinator</span><span class="p">:</span> <span class="kt">UIViewControllerTransitionCoordinator</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">super</span><span class="o">.</span><span class="nf">viewWillTransition</span><span class="p">(</span><span class="nv">to</span><span class="p">:</span> <span class="n">size</span><span class="p">,</span> <span class="nv">with</span><span class="p">:</span> <span class="n">coordinator</span><span class="p">)</span>

    <span class="c1">//        print("will execute before rotation")</span>

    <span class="k">self</span><span class="o">.</span><span class="n">tableView</span><span class="o">.</span><span class="nf">beginUpdates</span><span class="p">()</span>

    <span class="n">coordinator</span><span class="o">.</span><span class="nf">animate</span><span class="p">(</span><span class="nv">alongsideTransition</span><span class="p">:</span> <span class="p">{</span> <span class="p">(</span><span class="nv">context</span><span class="p">:</span> <span class="kt">UIViewControllerTransitionCoordinatorContext</span><span class="p">)</span> <span class="k">in</span>

        <span class="c1">//            print("will execute during rotation")</span>

        <span class="k">self</span><span class="o">.</span><span class="n">tableView</span><span class="o">.</span><span class="nf">endUpdates</span><span class="p">()</span>

    <span class="p">})</span> <span class="p">{</span> <span class="p">(</span><span class="nv">context</span><span class="p">:</span> <span class="kt">UIViewControllerTransitionCoordinatorContext</span><span class="p">)</span> <span class="k">in</span>

        <span class="c1">//            print("will execute after rotation")</span>

    <span class="p">}</span>

<span class="p">}</span>
</code></pre></div></div>

<p>That’s it. Now you have a nice table view with a smart primary image.</p>

<p>For the full codebase, check out the <a href="https://github.com/villyg/AutoSizingSectionViewImage">GitHub repository</a>.</p>]]></content><author><name></name></author><category term="projects" /><category term="gunvault" /><category term="swift" /><category term="ios" /><summary type="html"><![CDATA[As part of version 1.2 of Gun Vault I decided to include the functionality of having the primary image of a gun included on top of the Gun Details screen. I wanted the image to take 1/3 of the screen when the device is in portrait mode …]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://villyg.com/assets/images/UIKit.png" /><media:content medium="image" url="http://villyg.com/assets/images/UIKit.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>