Max Humber's #swift Postshttps://maxhumber.com/swiftPosts tagged with #swift by Max HumberUpdating Sankeyhttps://maxhumber.com/updatingsankey<p>Just released <a href="https://github.com/maxhumber/Sankey/tree/2.0">Sankey 2.0</a>, an open-source package for building Sankey diagrams in SwiftUI.</p> <h4>What?</h4> <p>A Sankey diagram visualizes flow: where stuff is coming from, where it's going, and how much stuff is moving. Thick and thin bands represent the volume of flow. Sankey diagrams are perfect for showing money, user behavior, energy usage—basically anything involving inputs, outputs, and the in-between.</p> <h4>Why + What's New?</h4> <p>I originally built <a href="https://github.com/maxhumber/Sankey/tree/1.0">Sankey 1.0</a> in May 2022 for a contract project (no other options existed at the time). Then I forgot about it—until a couple of weeks ago when I needed a Sankey diagram for a new app.</p> <p>This new project requires offline rendering and Dark Mode support. So, while adding these features to Sankey, I also streamlined the API to make it dead simple to create beautiful charts like this:</p> <p><img alt="" src="https://github.com/maxhumber/Sankey/raw/master/Images/quick.png" /></p> <p>With code that is a simple as this:</p> <div class="highlight"><pre><span></span><code><span class="kd">import</span><span class="w"> </span><span class="nc">Sankey</span> <span class="kd">import</span><span class="w"> </span><span class="nc">SwiftUI</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">ContentView</span><span class="p">:</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">data</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">SankeyData</span><span class="p">(</span> <span class="w"> </span><span class="n">nodes</span><span class="p">:</span><span class="w"> </span><span class="p">[</span> <span class="w"> </span><span class="n">SankeyNode</span><span class="p">(</span><span class="s">&quot;A&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">color</span><span class="p">:</span><span class="w"> </span><span class="p">.</span><span class="n">blue</span><span class="p">),</span> <span class="w"> </span><span class="n">SankeyNode</span><span class="p">(</span><span class="s">&quot;B&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">color</span><span class="p">:</span><span class="w"> </span><span class="p">.</span><span class="n">purple</span><span class="p">),</span> <span class="w"> </span><span class="n">SankeyNode</span><span class="p">(</span><span class="s">&quot;X&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">color</span><span class="p">:</span><span class="w"> </span><span class="p">.</span><span class="n">red</span><span class="p">),</span> <span class="w"> </span><span class="n">SankeyNode</span><span class="p">(</span><span class="s">&quot;Y&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">color</span><span class="p">:</span><span class="w"> </span><span class="p">.</span><span class="n">yellow</span><span class="p">),</span> <span class="w"> </span><span class="n">SankeyNode</span><span class="p">(</span><span class="s">&quot;Z&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">color</span><span class="p">:</span><span class="w"> </span><span class="p">.</span><span class="n">green</span><span class="p">),</span> <span class="w"> </span><span class="p">],</span> <span class="w"> </span><span class="n">links</span><span class="p">:</span><span class="w"> </span><span class="p">[</span> <span class="w"> </span><span class="n">SankeyLink</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span><span class="w"> </span><span class="n">from</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;A&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">to</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;X&quot;</span><span class="p">),</span> <span class="w"> </span><span class="n">SankeyLink</span><span class="p">(</span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="n">from</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;A&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">to</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;Y&quot;</span><span class="p">),</span> <span class="w"> </span><span class="n">SankeyLink</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span><span class="w"> </span><span class="n">from</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;A&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">to</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;Z&quot;</span><span class="p">),</span> <span class="w"> </span><span class="n">SankeyLink</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="n">from</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;B&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">to</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;X&quot;</span><span class="p">),</span> <span class="w"> </span><span class="n">SankeyLink</span><span class="p">(</span><span class="mi">9</span><span class="p">,</span><span class="w"> </span><span class="n">from</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;B&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">to</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;Y&quot;</span><span class="p">),</span> <span class="w"> </span><span class="n">SankeyLink</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="n">from</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;B&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">to</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;Z&quot;</span><span class="p">),</span> <span class="w"> </span><span class="p">]</span> <span class="w"> </span><span class="p">)</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">body</span><span class="p">:</span><span class="w"> </span><span class="n">some</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">SankeyDiagram</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">nodeOpacity</span><span class="p">(</span><span class="mf">0.9</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">linkColorMode</span><span class="p">(.</span><span class="n">gradient</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">padding</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">frame</span><span class="p">(</span><span class="n">height</span><span class="p">:</span><span class="w"> </span><span class="mi">350</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="p">#</span><span class="n">Preview</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">ContentView</span><span class="p">()</span> <span class="p">}</span> </code></pre></div> <h4>All "Old" Code is "Bad" Code</h4> <p><img alt="" src="https://i.redd.it/djajn2o19ca81.jpg" /></p> <p>Revisiting old code is always a fun (and humbling) exercise. Because the code in 1.0 was bad! (If you're not embarrassed by your past work, are you even learning?)</p> <p>While the core feature of Sankey—connecting source nodes to target nodes— remains 90% of the code in 2.0 is new. Mostly, this is a consequence of replacing the <a href="https://developers.google.com/chart">Google Charts</a> rendering engine with <a href="https://d3js.org/">D3.js</a>. But also because I removed a lot of the over-engineered "organization", limited excessive configuration options, and fixed a lot of color handling mistakes.</p> <p>Despite these major changes, I was able to maintain a good bit of backwards compatibility (the original Quickstart still works)!</p> <h4>Too Much Structure!</h4> <p>The Sankey package <em>should</em> be simple. It's just a <code>SankeyDiagram</code> SwiftUI component. And a few other structs that help in its construction. In 1.0, I went overboard trying to organize everything for "future extensibility", creating this convoluted mess:</p> <div class="highlight"><pre><span></span><code>--Package.swift --Sources ----Sankey/ ------Options/ --------Tooltip/ ----------TextStyle/ ------------SankeyOptions.Tooltip.TextStyle.swift ----------SankeyOptions.Tooltip.swift --------Sankey/ ----------SankeyOptions.Sankey.swift ----------Link/ ------------Color/ --------------SankeyOptions.Sankey.Link.Color.swift ------------SankeyOptions.Sankey.Link.swift ------------ColorMode/ --------------SankeyOptions.Sankey.Link.ColorMode.swift ----------Node/ ------------Label/ --------------SankeyOptions.Sankey.Node.Label.swift ------------ColorMode/ --------------SankeyOptions.Sankey.Node.ColorMode.swift ------------SankeyOptions.Sankey.Node.swift --------SankeyOptions.swift --------SankeyOptions+CustomStringConvertible.swift --------SankeyOptions+init.swift ------Diagram/ --------SankeyDiagram.swift --------SankeyDiagram+init.swift ------Link/ --------SankeyLink.swift --------SankeyLink+ExpressibleByArrayLiteral.swift --------SankeyLink+CustomStringConvertible.swift ------Node/ --------SankeyNode.swift </code></pre></div> <p>Now that we're in "the future" I can say that all this "organization" wasn't just unhelpful—it actively hindered my ability add to and update the package! For 2.0, I streamlined as much as possible:</p> <div class="highlight"><pre><span></span><code>--Package.swift --Sources/ ----Deprecated/ ------SankeyDiagram+deprecated.swift ------SankeyLink+deprecated.swift ----Helpers/ ------Color+.swift ------HexColor.swift ----Resources/ ------d3.min.js ------d3-sankey.min.js ----SankeyData.swift ----SankeyDiagram.swift ----SankeyLink.swift ----SankeyNode.swift ----SankeyOptions.swift ----SankeyResources.swift </code></pre></div> <p>Easier to work with. But more importantly, easier to delete! Because, let's be honest—future me will probably throw it all out in another two years!</p> <h4>"WWAD"</h4> <p>While the original "structure" slowed me down, exposing too many configuration options to users was an even bigger mistake. Look at this old init signature:</p> <div class="highlight"><pre><span></span><code><span class="n">SankeyDiagram</span><span class="p">(</span> <span class="w"> </span><span class="kc">_</span><span class="w"> </span><span class="n">data</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="n">SankeyLink</span><span class="p">],</span> <span class="w"> </span><span class="n">nodeColors</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="nb">String</span><span class="p">]?</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span> <span class="w"> </span><span class="n">nodeColorMode</span><span class="p">:</span><span class="w"> </span><span class="n">SankeyOptions</span><span class="p">.</span><span class="n">Sankey</span><span class="p">.</span><span class="n">Node</span><span class="p">.</span><span class="n">ColorMode</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">unique</span><span class="p">,</span> <span class="w"> </span><span class="n">nodeWidth</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">?</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span> <span class="w"> </span><span class="n">nodePadding</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">?</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span> <span class="w"> </span><span class="n">nodeLabelColor</span><span class="p">:</span><span class="w"> </span><span class="nb">String</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;black&quot;</span><span class="p">,</span> <span class="w"> </span><span class="n">nodeLabelFontSize</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">24</span><span class="p">,</span> <span class="w"> </span><span class="n">nodeLabelFontName</span><span class="p">:</span><span class="w"> </span><span class="nb">String</span><span class="p">?</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span> <span class="w"> </span><span class="n">nodeLabelBold</span><span class="p">:</span><span class="w"> </span><span class="nb">Bool</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span> <span class="w"> </span><span class="n">nodeLabelItalic</span><span class="p">:</span><span class="w"> </span><span class="nb">Bool</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span> <span class="w"> </span><span class="n">nodeLabelPadding</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">?</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span> <span class="w"> </span><span class="n">nodeInteractivity</span><span class="p">:</span><span class="w"> </span><span class="nb">Bool</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span> <span class="w"> </span><span class="n">linkColors</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="nb">String</span><span class="p">]?</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span> <span class="w"> </span><span class="n">linkColorMode</span><span class="p">:</span><span class="w"> </span><span class="n">SankeyOptions</span><span class="p">.</span><span class="n">Sankey</span><span class="p">.</span><span class="n">Link</span><span class="p">.</span><span class="n">ColorMode</span><span class="p">?</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span> <span class="w"> </span><span class="n">linkColorFill</span><span class="p">:</span><span class="w"> </span><span class="nb">String</span><span class="p">?</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span> <span class="w"> </span><span class="n">linkColorFillOpacity</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">?</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span> <span class="w"> </span><span class="n">linkColorStroke</span><span class="p">:</span><span class="w"> </span><span class="nb">String</span><span class="p">?</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span> <span class="w"> </span><span class="n">linkColorStrokeWidth</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span> <span class="w"> </span><span class="n">tooltipValueLabel</span><span class="p">:</span><span class="w"> </span><span class="nb">String</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;&quot;</span><span class="p">,</span> <span class="w"> </span><span class="n">tooltipTextColor</span><span class="p">:</span><span class="w"> </span><span class="nb">String</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;black&quot;</span><span class="p">,</span> <span class="w"> </span><span class="n">tooltipTextFontSize</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">24</span><span class="p">,</span> <span class="w"> </span><span class="n">tooltipTextFontName</span><span class="p">:</span><span class="w"> </span><span class="nb">String</span><span class="p">?</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span> <span class="w"> </span><span class="n">tooltipTextBold</span><span class="p">:</span><span class="w"> </span><span class="nb">Bool</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span> <span class="w"> </span><span class="n">tooltipTextItalic</span><span class="p">:</span><span class="w"> </span><span class="nb">Bool</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span> <span class="w"> </span><span class="n">layoutIterations</span><span class="p">:</span><span class="w"> </span><span class="nb">Int</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">32</span> <span class="p">)</span><span class="w"> </span> </code></pre></div> <p>Can you tell I was just blindly trying to recreating someone else's API?</p> <p>Now when I build for SwiftUI I typically ask myself: "What Would Apple Do?" (WWAD). If this piece of code were native Apple component, what would it look and feel like? I must say, I think I nailed it in 2.0:</p> <div class="highlight"><pre><span></span><code><span class="n">SankeyDiagram</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">nodeAlignment</span><span class="p">(.</span><span class="n">justify</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">nodeWidth</span><span class="p">(</span><span class="mi">15</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">nodePadding</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">nodeDefaultColor</span><span class="p">(.</span><span class="n">gray</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">nodeOpacity</span><span class="p">(</span><span class="mf">0.8</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">linkDefaultColor</span><span class="p">(.</span><span class="n">gray</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">linkOpacity</span><span class="p">(</span><span class="mf">0.7</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">linkColorMode</span><span class="p">(</span><span class="kc">nil</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">labelPadding</span><span class="p">(</span><span class="mi">8</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">labelColor</span><span class="p">(.</span><span class="n">primary</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">labelOpacity</span><span class="p">(</span><span class="mf">0.9</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">labelFontSize</span><span class="p">(</span><span class="mi">14</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">labelFontFamily</span><span class="p">(</span><span class="s">&quot;Times&quot;</span><span class="p">)</span> </code></pre></div> <p>All customization options are now exposed as "Modifiers" on the SankeyDiagram object itself. This is enabled by hiding the SankeyOptions struct from the user and exposing modifiers that look like this:</p> <div class="highlight"><pre><span></span><code><span class="c1">// ...</span> <span class="kd">public</span><span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">nodeOpacity</span><span class="p">(</span><span class="kc">_</span><span class="w"> </span><span class="n">value</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">)</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">SankeyDiagram</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">new</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">self</span> <span class="w"> </span><span class="n">new</span><span class="p">.</span><span class="n">options</span><span class="p">.</span><span class="n">nodeOpacity</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">value</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">new</span> <span class="p">}</span> <span class="c1">// ...</span> </code></pre></div> <p>I also replaced all externally facing hex codes with native SwiftUI colors. While implementing this was complex, I thought it was better to handle the complexity myself than burden users with it. Now Dark Mode and using a "color" like <code>Color.primary</code> will just work! </p> <h4>Conclusion</h4> <p>The code in Sankey 2.0 is easier to read, use, and built to be thrown away! While it's not perfect—future me will probably laugh at it in the future—it works well today. And that's all that matters. If you need to visualize flows in any of your apps I hope you give it a try!</p>Wed, 04 Dec 2024 00:00:00 -0000https://maxhumber.com/updatingsankeyThe Case Against TCAhttps://maxhumber.com/tcatca<p>The Composable Architecture (<a href="https://github.com/pointfreeco/swift-composable-architecture">TCA</a>) is an iOS app development framework that promises "better state management", "modular design", and "testable side effects". After doing some consulting with a startup looking to adopt the framework, I can confidently say: TCA creates more problems than it solves.</p> <h4>Pain.</h4> <p>TCA is packed with unnecessary complexity, redundant abstractions, and frustrating quirks. The startup I worked with ultimately abandoned the framework for the following reasons:</p> <ul> <li><strong>Steep Learning Curve</strong>: Requires mastering "reducers" and "stores" making onboarding difficult.</li> <li><strong>Excessive Boilerplate</strong>: Even simple features require excessive code, slowing development.</li> <li><strong>Performance Problems</strong>: Slow builds, indexing, and crashing Xcode previews waste time.</li> <li><strong>Compiler Issues</strong>: Reducers often break the compiler with hard-to-diagnose errors.</li> <li><strong>Maintenance Burden</strong>: Frequent updates break code and demand costly refactoring.</li> <li><strong>Hard to Remove</strong>: Hard to rip out once adopted.</li> <li><strong>Third-Party Risk</strong>: Maintained by essentially two people with no long-term guarantee.</li> <li><strong>Outpaced by SwiftUI</strong>: SwiftUI now handles most problems TCA was built to solve.</li> </ul> <p>I contend that the only good idea in TCA is the <code>DependencyClient</code> pattern. But you don't actually need TCA to implement the pattern, you can do it with just a few lines of swift (see my post on <a href="https://maxhumber.com/clientpattern">the Client Pattern</a>).</p> <h4>Obnoxious Dependencies</h4> <p>Importing TCA brings in 16 additional dependencies, most of which are other Point-Free libraries. While these libraries might have the <code>swift-</code> prefix, only <code>swift-collections</code> and <code>swift-syntax</code> are actually from Apple:</p> <div class="highlight"><pre><span></span><code>swift-composable-architecture 1.16.1 ├── combine-schedulers 1.0.2 ├── swift-case-paths 1.5.5 ├── swift-clocks 1.0.5 ├── swift-collections 1.1.3 ├── swift-concurrency-extras 1.2.0 ├── swift-custom-dump 1.3.3 ├── swift-dependencies 1.4.0 ├── swift-identified-collections 1.1.0 ├── swift-macro-testing 0.5.2 ├── swift-navigation 2.2.2 ├── swift-perception 1.3.5 ├── swift-snapshot-testing 1.17.5 ├── swift-syntax 600.0.0-prerelease-2024-09-04 ├── SwiftDocCPlugin 1.4.3 ├── SymbolKit 1.0.0 └── xctest-dynamic-overlay 1.4.0 </code></pre></div> <p>This level of bloat is overkill for what TCA claims to solve, and the naming convention (<code>swift-navigation</code> instead of <code>point-free-navigation</code>) feels super obnoxious and deliberately disingenuous.</p> <h4>Online Sentiment</h4> <p>Looking through reddit (and elsewhere online) it seems that my frustrations are widely shared:</p> <p><strong><a href="https://www.reddit.com/r/SwiftUI/comments/16pab2x/have_you_used_tca_in_production_whats_your/">Reddit: Have you used TCA in production?</a></strong></p> <ul> <li>"TCA is basically an <strong>extra layer of complexity</strong> in order to do what SwiftUI already does under the hood."</li> <li>"Watch the first three [TCA] videos on how to make a checklist… The solution is <strong>sooooo complicated.</strong>"</li> <li>"[TCA] seems <strong>needlessly complex</strong> to jump through 5+ files just to figure out what’s happening on one simple screen."</li> </ul> <p><strong><a href="https://www.reddit.com/r/iOSProgramming/comments/1c1o5jx/i_hate_the_composable_architecture/">Reddit: I hate the Composable Architecture</a></strong></p> <ul> <li>"[TCA] adds <strong>unnecessary complexity</strong> and a central dependency."</li> <li>"Everything [in TCA] is so tightly connected, <strong>any change leads to changes everywhere else.</strong>"</li> <li>"Working two years with that [TCA] shit was <strong>one of the reasons I quit the project.</strong>"</li> </ul> <p><strong><a href="https://rodschmidt.com/posts/composable-architecture-experience/">Rod Schmidt: Composable Architecture Experience</a></strong></p> <ul> <li>"TCA is a 3rd party framework. This means <strong>Apple doesn’t support it or care about it.</strong>"</li> <li>"You have to <strong>constantly re-learn things</strong> as the [TCA] framework gets updated."</li> <li>"You can be <strong>much more productive with MVVM</strong> and get the same benefits [as TCA]."</li> </ul> <h4>Survey Results</h4> <p>Despite the criticism TCA is pretty popular. So who actually uses the framework? And why do they like it? To find out, I put up a <a href="https://docs.google.com/forms/d/e/1FAIpQLSdvFSCfHlHi3zjX643ZVv8Q0mBiqwBcf9FgBc4PJ-EOeZCvkw/viewanalytics">survey</a> in a few iOS/Swift subreddits and got 100 responses:</p> <div class="highlight"><pre><span></span><code>| TCA Opinion | SwiftUI-first | UIKit-first | | -------------------------------------------------- | ------------- | ----------- | | I dont like it and avoid using it | 8 | 18 | | I like it a lot and prefer it for app architecture | 5 | 19 | | Its acceptable but not my preferred choice | 1 | 19 | | Not applicable (I havent used TCA) | 3 | 27 | </code></pre></div> <p>Key insights:</p> <ul> <li><strong>83%</strong> of survey respondents started with UIKit, while only <strong>17%</strong> started with SwiftUI.</li> <li><strong>47%</strong> of SwiftUI-first developers avoid TCA, compared to only <strong>22%</strong> of UIKit-first developers.</li> </ul> <p>TCA's appeal seems rooted in its familiarity to UIKit-first developers, offering a structured, UIKit-like experience. For SwiftUI-first developers, TCA likely feels unnecessary and redundant, solving problems that SwiftUI already natively solves. </p> <h4>Vanilla</h4> <p>So what's the alternative? Honestly, just vanilla SwiftUI! Frustrated with TCA, I decided to recreate the <a href="https://github.com/pointfreeco/swift-composable-architecture/tree/1.16.1/Examples/SpeechRecognition">SpeechRecognition</a> example to prove that the framework is unnecessary.</p> <p>Check out <a href="https://github.com/maxhumber/VanillaSpeechRecognition/blob/master/VanillaSpeechRecognition/SpeechRecognitionView.swift">my vanilla implementation</a>. Most of the code is in a single file (just for ease of comparison) and works seamlessly with <strong>Xcode 16</strong>, <strong>Swift 6</strong>, and <strong>iOS 18</strong>. Here are the important benchmarks:</p> <div class="highlight"><pre><span></span><code>| Metric | Vanilla | TCA | | --------------------------- | ---------- | --------------- | | Dependencies | 0 | 16 | | &quot;Cold&quot; Build Time (seconds) | 1.1 | 32.4 | | &quot;Warm&quot; Build Time (seconds) | 0.1 | 0.4 | | Indexing Time | Negligible | Several minutes | | Lines of Code | 319 | 579 | </code></pre></div> <p>Given that the vanilla version delivers the same functionality and testing capabilities without the complexity, boilerplate, or quirks, I just don't see the advantage of using TCA.</p> <h4>TL;DR:</h4> <p>The Composable Architecture might have been useful in 2019 when SwiftUI was still immature, but Apple's yearly updates have rendered it obsolete for most apps. TCA is a perfect example of over-engineering that actively makes code worse while claiming to make it better. If you're transitioning to SwiftUI, focus on mastering its native tools in lieu of adopting TCA just because it feels familiar.</p>Wed, 27 Nov 2024 00:00:00 -0000https://maxhumber.com/tcatcaSwiftUI Data Flowhttps://maxhumber.com/swiftuiflow<p>SwiftUI is over 5 years old. While a lot has changed since its introduction at WWDC19, the fundamentals— especially with respect to data flow—remain pretty much the same. Sure, there's some new <a href="https://developer.apple.com/documentation/Observation">Observation</a> framework syntax to learn, but Apple has done a great job at "simplifying" and "unifying" APIs while maintaining backwards compatibility.</p> <p>This post is meant to serve as a reference guide for modern (iOS 17/18+) SwiftUI data flow. It ought to be particularly useful for working with LLMs that might crank out outdated code.</p> <h4>Data Flow</h4> <p>By "data flow" I mean: how data moves through an app and updates the UI. Data flow in SwiftUI is actually quite simple... as long as you keep two principles in mind: views automatically update when their data changes, and every piece of data needs a single source of truth. </p> <p>Since its release Apple has produced hundreds of hours of WWDC videos on SwiftUI. If you just watch these three videos you'll be like 90% up to speed:</p> <ul> <li><a href="https://developer.apple.com/videos/play/wwdc2019/226/">Data Flow Through SwiftUI (WWDC19)</a></li> <li><a href="https://developer.apple.com/videos/play/wwdc2020/10040/">Data Essentials in SwiftUI (WWDC20)</a></li> <li><a href="https://developer.apple.com/videos/play/wwdc2023/10115/">Discover Observation in SwiftUI (WWDC23)</a></li> </ul> <h4>Value vs Reference</h4> <p>I don't want to get too in the weeds, but in order to see and understand how data flow has changed we have to differentiate between value types (📦) and reference types (🔗). </p> <p>Value types like Strings and Ints and Doubles and Bools and structs and enums, create independent copies when assigned. In contrast, reference types, like classes, share the same instance. </p> <p>Apple <a href="https://www.swift.org/documentation/articles/value-and-reference-types.html">strongly recommends</a> using value types for most cases. If we followed this advice more often I wouldn't have even need to write this post as most of the data flow changes in SwiftUI (from iOS 13/14 to iOS 17/18+) are related to reference types!</p> <h4>Timeline</h4> <p>Legend: <code>✅</code> = current, <code>⛔</code> = avoid, <code>📦</code> = value, <code>🔗</code> = reference</p> <p><strong>iOS 13 / WWDC19</strong></p> <ul> <li><code>✅</code> <code>📦</code> <strong><code>@State</code></strong></li> <li><code>✅</code> <code>📦</code> <strong><code>@Binding</code></strong> </li> <li><code>⛔</code> <code>🔗</code> <code>ObservableObject</code> <em>replaced with </em><em><code>@Observable</code></em><em> in iOS 17+</em></li> <li><code>⛔</code> <code>🔗</code> <strong><code>@Published</code></strong> <em>redundant when using </em><em><code>@Observable</code></em><em> in iOS 17+</em></li> <li><code>⛔</code> <code>🔗</code> <strong><code>@ObservedObject</code></strong> <em>replaced with </em><em><code>@Bindable</code></em><em> in iOS 17+</em></li> <li><code>⛔</code> <code>🔗</code> <strong><code>@EnvironmentObject</code></strong> <em>replaced with </em><em><code>@Environment</code></em><em> in iOS 17+</em></li> <li><code>✅</code> <code>📦</code> <strong><code>@Environment</code></strong></li> <li><code>⛔</code> <code>📦</code> <code>EnvironmentKey</code> <em>replaced with <code>@Entry</code> in iOS 18+</em></li> <li><code>⛔</code> <code>PreviewProvider</code> <em>replaced with <code>#Preview</code> in iOS 17+</em></li> </ul> <p><strong>iOS 14 / WWDC20</strong></p> <ul> <li><code>⛔</code> <code>🔗</code> <strong><code>@StateObject</code></strong> <em>replaced with <code>@State</code> in iOS 17+</em></li> </ul> <p><strong>iOS 15 / WWDC21</strong></p> <ul> <li>No significant data flow changes</li> </ul> <p><strong>iOS 16 / WWDC22</strong></p> <ul> <li>No significant data flow changes</li> </ul> <p><strong>iOS 17 / WWDC23</strong></p> <ul> <li><code>✅</code> <code>🔗</code> <strong><code>@Observable</code></strong> <em>replaces <code>ObservableObject</code></em></li> <li><code>✅</code> <code>🔗</code> <strong><code>@State</code></strong> <em>replaces <code>@StateObject</code></em></li> <li><code>✅</code> <code>🔗</code> <strong><code>@Bindable</code></strong>† <em>replaces <code>@ObservedObject</code></em></li> <li><code>✅</code> <code>🔗</code> <strong><code>@Environment</code></strong> <em>replaces <code>@EnvironmentObject</code></em></li> <li><code>✅</code> <code>#Preview</code> <em>replaces <code>PreviewProvider</code></em></li> </ul> <p><strong>iOS 18 / WWDC24</strong></p> <ul> <li><code>✅</code> <code>📦</code> <code>@Entry</code> <em>replaces <code>EnvironmentKey</code></em></li> </ul> <h4>Rosetta Code</h4> <p>Here's a simple, but practical, example of modern data flow in SwiftUI (I'd encourage you to copy-and-paste this block of code into Xcode to experiment with the syntax for yourself):</p> <div class="highlight"><pre><span></span><code><span class="c1">// iOS 17/18+</span> <span class="kd">import</span><span class="w"> </span><span class="nc">SwiftUI</span> <span class="kd">extension</span><span class="w"> </span><span class="nc">EnvironmentValues</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="c1">// ✅ 📦 replaces `EnvironmentKey`</span> <span class="w"> </span><span class="p">@</span><span class="n">Entry</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">lastRefresh</span><span class="p">:</span><span class="w"> </span><span class="n">Date</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">now</span> <span class="p">}</span> <span class="c1">// ✅ 🔗 replaces `ObservableObject`</span> <span class="p">@</span><span class="n">Observable</span> <span class="kd">class</span><span class="w"> </span><span class="nc">UserStore</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">username</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;max&quot;</span><span class="w"> </span><span class="c1">// ✅ 🔗 replaces @Published</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">hasPremium</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">false</span><span class="w"> </span><span class="c1">// ✅ 🔗 replaces @Published</span> <span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">randomize</span><span class="p">()</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">username</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nb">String</span><span class="p">((</span><span class="mf">0.</span><span class="p">.&lt;</span><span class="mi">3</span><span class="p">).</span><span class="bp">map</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kc">_</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="s">&quot;abcdefghijklmnopqrstuvwxyz&quot;</span><span class="p">.</span><span class="n">randomElement</span><span class="p">()</span><span class="o">!</span><span class="w"> </span><span class="p">})</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="c1">// ✅ 🔗 replaces `ObservableObject`</span> <span class="p">@</span><span class="n">Observable</span> <span class="kd">class</span><span class="w"> </span><span class="nc">EmojiStore</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">emoji</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;😀&quot;</span><span class="w"> </span><span class="c1">// ✅ 🔗 replaces @Published</span> <span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">update</span><span class="p">()</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">emoji</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nb">String</span><span class="p">(</span><span class="nb">UnicodeScalar</span><span class="p">(</span><span class="nb">Int</span><span class="p">.</span><span class="n">random</span><span class="p">(</span><span class="k">in</span><span class="p">:</span><span class="w"> </span><span class="mh">0x1F600</span><span class="p">...</span><span class="mh">0x1F64F</span><span class="p">))</span><span class="o">!</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">ParentView</span><span class="p">:</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="p">@</span><span class="n">State</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">lastRefresh</span><span class="p">:</span><span class="w"> </span><span class="n">Date</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">now</span><span class="w"> </span><span class="c1">// ✅ 📦 unchanged</span> <span class="w"> </span><span class="p">@</span><span class="n">State</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">userStore</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">UserStore</span><span class="p">()</span><span class="w"> </span><span class="c1">// ✅ 🔗 replaces `StateObject`</span> <span class="w"> </span><span class="p">@</span><span class="n">State</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">emojiStore</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">EmojiStore</span><span class="p">()</span><span class="w"> </span><span class="c1">// ✅ 🔗 replaces `StateObject`</span> <span class="w"> </span><span class="p">@</span><span class="n">State</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">count</span><span class="p">:</span><span class="w"> </span><span class="nb">Int</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="c1">// ✅ 📦 unchanged</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">body</span><span class="p">:</span><span class="w"> </span><span class="n">some</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">VStack</span><span class="p">(</span><span class="n">spacing</span><span class="p">:</span><span class="w"> </span><span class="mi">15</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">HStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Last Refresh: </span><span class="si">\(</span><span class="n">lastRefresh</span><span class="p">.</span><span class="n">formatted</span><span class="si">(</span><span class="n">date</span><span class="p">:</span><span class="w"> </span><span class="p">.</span><span class="n">omitted</span><span class="p">,</span><span class="w"> </span><span class="n">time</span><span class="p">:</span><span class="w"> </span><span class="p">.</span><span class="n">complete</span><span class="si">))</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;Refresh&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">lastRefresh</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">now</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">HStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Username: </span><span class="si">\(</span><span class="n">userStore</span><span class="p">.</span><span class="n">username</span><span class="si">)</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;Randomize&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">userStore</span><span class="p">.</span><span class="n">randomize</span><span class="p">()</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">HStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Has premium: </span><span class="si">\(</span><span class="n">userStore</span><span class="p">.</span><span class="n">hasPremium</span><span class="w"> </span><span class="p">?</span><span class="w"> </span><span class="s">&quot;Yes&quot;</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s">&quot;No!&quot;</span><span class="si">)</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;Toggle&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">userStore</span><span class="p">.</span><span class="n">hasPremium</span><span class="p">.</span><span class="n">toggle</span><span class="p">()</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">HStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Emoji: </span><span class="si">\(</span><span class="n">emojiStore</span><span class="p">.</span><span class="n">emoji</span><span class="si">)</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;Update&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">emojiStore</span><span class="p">.</span><span class="n">update</span><span class="p">()</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">HStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Count: </span><span class="si">\(</span><span class="bp">count</span><span class="si">)</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;+1&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">count</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;-1&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">count</span><span class="w"> </span><span class="o">-=</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">ChildView</span><span class="p">(</span> <span class="w"> </span><span class="n">emojiStore</span><span class="p">:</span><span class="w"> </span><span class="n">emojiStore</span><span class="p">,</span><span class="w"> </span><span class="c1">// ✅ 🔗 unchanged</span> <span class="w"> </span><span class="bp">count</span><span class="p">:</span><span class="w"> </span><span class="err">$</span><span class="bp">count</span><span class="w"> </span><span class="c1">// ✅ 📦 unchanged</span> <span class="w"> </span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">.</span><span class="n">padding</span><span class="p">()</span> <span class="w"> </span><span class="p">.</span><span class="n">background</span><span class="p">(</span><span class="n">Rectangle</span><span class="p">().</span><span class="n">stroke</span><span class="p">(.</span><span class="n">purple</span><span class="p">,</span><span class="w"> </span><span class="n">lineWidth</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">))</span> <span class="w"> </span><span class="p">.</span><span class="n">environment</span><span class="p">(</span><span class="err">\</span><span class="p">.</span><span class="n">lastRefresh</span><span class="p">,</span><span class="w"> </span><span class="n">lastRefresh</span><span class="p">)</span><span class="w"> </span><span class="c1">// ✅ 📦 unchanged</span> <span class="w"> </span><span class="p">.</span><span class="n">environment</span><span class="p">(</span><span class="n">userStore</span><span class="p">)</span><span class="w"> </span><span class="c1">// ✅ 🔗 replaces `.environmentObject`</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">ChildView</span><span class="p">:</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="p">@</span><span class="n">Environment</span><span class="p">(</span><span class="err">\</span><span class="p">.</span><span class="n">lastRefresh</span><span class="p">)</span><span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">lastRefresh</span><span class="p">:</span><span class="w"> </span><span class="n">Date</span><span class="w"> </span><span class="c1">// ✅ 📦 unchanged</span> <span class="w"> </span><span class="p">@</span><span class="n">Environment</span><span class="p">(</span><span class="n">UserStore</span><span class="p">.</span><span class="kc">self</span><span class="p">)</span><span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">userStore</span><span class="p">:</span><span class="w"> </span><span class="n">UserStore</span><span class="w"> </span><span class="c1">// ✅ 🔗 replaces `@EnvironmentObject`</span> <span class="w"> </span><span class="p">@</span><span class="n">Bindable</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">emojiStore</span><span class="p">:</span><span class="w"> </span><span class="n">EmojiStore</span><span class="w"> </span><span class="c1">// ✅ 🔗 replaces `@ObservedObject`</span> <span class="w"> </span><span class="p">@</span><span class="n">Binding</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">count</span><span class="p">:</span><span class="w"> </span><span class="nb">Int</span><span class="w"> </span><span class="c1">// ✅ 📦 unchanged</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">body</span><span class="p">:</span><span class="w"> </span><span class="n">some</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">VStack</span><span class="p">(</span><span class="n">spacing</span><span class="p">:</span><span class="w"> </span><span class="mi">15</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">HStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Last Refresh: </span><span class="si">\(</span><span class="n">lastRefresh</span><span class="p">.</span><span class="n">formatted</span><span class="si">(</span><span class="n">date</span><span class="p">:</span><span class="w"> </span><span class="p">.</span><span class="n">omitted</span><span class="p">,</span><span class="w"> </span><span class="n">time</span><span class="p">:</span><span class="w"> </span><span class="p">.</span><span class="n">complete</span><span class="si">))</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">HStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Edit username:&quot;</span><span class="p">)</span> <span class="w"> </span><span class="p">@</span><span class="n">Bindable</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">userStore</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">userStore</span><span class="w"> </span><span class="c1">// ⚠️ 🔗 FRUSTRATING!</span> <span class="w"> </span><span class="n">TextField</span><span class="p">(</span><span class="s">&quot;&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">text</span><span class="p">:</span><span class="w"> </span><span class="err">$</span><span class="n">userStore</span><span class="p">.</span><span class="n">username</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">textInputAutocapitalization</span><span class="p">(.</span><span class="n">never</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">fixedSize</span><span class="p">()</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">HStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Has premium: </span><span class="si">\(</span><span class="n">userStore</span><span class="p">.</span><span class="n">hasPremium</span><span class="w"> </span><span class="p">?</span><span class="w"> </span><span class="s">&quot;Yes&quot;</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s">&quot;No!&quot;</span><span class="si">)</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;Toggle&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">userStore</span><span class="p">.</span><span class="n">hasPremium</span><span class="p">.</span><span class="n">toggle</span><span class="p">()</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">HStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Emoji: </span><span class="si">\(</span><span class="n">emojiStore</span><span class="p">.</span><span class="n">emoji</span><span class="si">)</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;Update&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">emojiStore</span><span class="p">.</span><span class="n">update</span><span class="p">()</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">HStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Count: </span><span class="si">\(</span><span class="bp">count</span><span class="si">)</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;+1&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">count</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;-1&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">count</span><span class="w"> </span><span class="o">-=</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">.</span><span class="n">padding</span><span class="p">()</span> <span class="w"> </span><span class="p">.</span><span class="n">background</span><span class="p">(</span><span class="n">Rectangle</span><span class="p">().</span><span class="n">stroke</span><span class="p">(.</span><span class="n">mint</span><span class="p">,</span><span class="w"> </span><span class="n">lineWidth</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">))</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="c1">// ✅ replaces `PreviewProvider`</span> <span class="p">#</span><span class="n">Preview</span><span class="p">(</span><span class="s">&quot;Parent&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">ParentView</span><span class="p">()</span> <span class="p">}</span> <span class="c1">// ✅ replaces `PreviewProvider`</span> <span class="p">#</span><span class="n">Preview</span><span class="p">(</span><span class="s">&quot;Child&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="p">@</span><span class="n">Previewable</span><span class="w"> </span><span class="p">@</span><span class="n">State</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">userStore</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">UserStore</span><span class="p">()</span><span class="w"> </span><span class="c1">// ✅ 🔗 replaces `StateObject`</span> <span class="w"> </span><span class="p">@</span><span class="n">Previewable</span><span class="w"> </span><span class="p">@</span><span class="n">State</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">emojiStore</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">EmojiStore</span><span class="p">()</span><span class="w"> </span><span class="c1">// ✅ 🔗 replaces `StateObject`</span> <span class="w"> </span><span class="p">@</span><span class="n">Previewable</span><span class="w"> </span><span class="p">@</span><span class="n">State</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">count</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="c1">// ✅ 📦 unchanged</span> <span class="w"> </span><span class="n">ChildView</span><span class="p">(</span><span class="n">emojiStore</span><span class="p">:</span><span class="w"> </span><span class="n">emojiStore</span><span class="p">,</span><span class="w"> </span><span class="bp">count</span><span class="p">:</span><span class="w"> </span><span class="err">$</span><span class="bp">count</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">environment</span><span class="p">(</span><span class="err">\</span><span class="p">.</span><span class="n">lastRefresh</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">now</span><span class="p">)</span><span class="w"> </span><span class="c1">// ✅ 📦 unchanged</span> <span class="w"> </span><span class="p">.</span><span class="n">environment</span><span class="p">(</span><span class="n">userStore</span><span class="p">)</span><span class="w"> </span><span class="c1">// ✅ 🔗 replaces `.environmentObject`</span> <span class="p">}</span> </code></pre></div> <p>And here's the same example as above, just painted with the old, outdated syntax:</p> <div class="highlight"><pre><span></span><code><span class="c1">// iOS 13/14/15/16</span> <span class="kd">import</span><span class="w"> </span><span class="nc">SwiftUI</span> <span class="c1">// ⛔️ 📦 replaced with `@Entry`</span> <span class="kd">private</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="nc">LastRefreshKey</span><span class="p">:</span><span class="w"> </span><span class="n">EnvironmentKey</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">defaultValue</span><span class="p">:</span><span class="w"> </span><span class="n">Date</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">now</span> <span class="p">}</span> <span class="kd">extension</span><span class="w"> </span><span class="nc">EnvironmentValues</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="c1">// ⛔️ 📦 replaced with `@Entry`</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">lastRefresh</span><span class="p">:</span><span class="w"> </span><span class="n">Date</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kr">get</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kc">self</span><span class="p">[</span><span class="n">LastRefreshKey</span><span class="p">.</span><span class="kc">self</span><span class="p">]</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="kr">set</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kc">self</span><span class="p">[</span><span class="n">LastRefreshKey</span><span class="p">.</span><span class="kc">self</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">newValue</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="c1">// ⛔️ 🔗 replaced by `@Observable`</span> <span class="kd">class</span><span class="w"> </span><span class="nc">UserStore</span><span class="p">:</span><span class="w"> </span><span class="n">ObservableObject</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="p">@</span><span class="n">Published</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">username</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;max&quot;</span><span class="w"> </span><span class="c1">// ⛔️ 🔗 redundant when using `@Observable`</span> <span class="w"> </span><span class="p">@</span><span class="n">Published</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">hasPremium</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">false</span><span class="w"> </span><span class="c1">// ⛔️ 🔗 redundant when using `@Observable`</span> <span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">randomize</span><span class="p">()</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">username</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nb">String</span><span class="p">((</span><span class="mf">0.</span><span class="p">.&lt;</span><span class="mi">3</span><span class="p">).</span><span class="bp">map</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kc">_</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="s">&quot;abcdefghijklmnopqrstuvwxyz&quot;</span><span class="p">.</span><span class="n">randomElement</span><span class="p">()</span><span class="o">!</span><span class="w"> </span><span class="p">})</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="c1">// ⛔️ 🔗 replaced by `@Observable`</span> <span class="kd">class</span><span class="w"> </span><span class="nc">EmojiStore</span><span class="p">:</span><span class="w"> </span><span class="n">ObservableObject</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="p">@</span><span class="n">Published</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">emoji</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;😀&quot;</span><span class="w"> </span><span class="c1">// ⛔️ 🔗 redundant when using `@Observable`</span> <span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">update</span><span class="p">()</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">emoji</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nb">String</span><span class="p">(</span><span class="nb">UnicodeScalar</span><span class="p">(</span><span class="nb">Int</span><span class="p">.</span><span class="n">random</span><span class="p">(</span><span class="k">in</span><span class="p">:</span><span class="w"> </span><span class="mh">0x1F600</span><span class="p">...</span><span class="mh">0x1F64F</span><span class="p">))</span><span class="o">!</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">ParentView</span><span class="p">:</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="p">@</span><span class="n">State</span><span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">lastRefresh</span><span class="p">:</span><span class="w"> </span><span class="n">Date</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">now</span><span class="w"> </span><span class="c1">// ✅ 📦 unchanged</span> <span class="w"> </span><span class="p">@</span><span class="n">StateObject</span><span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">userStore</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">UserStore</span><span class="p">()</span><span class="w"> </span><span class="c1">// ⛔️ 🔗 replaced with `@State`</span> <span class="w"> </span><span class="p">@</span><span class="n">StateObject</span><span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">emojiStore</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">EmojiStore</span><span class="p">()</span><span class="w"> </span><span class="c1">// ⛔️ 🔗 replaced with `@State`</span> <span class="w"> </span><span class="p">@</span><span class="n">State</span><span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">count</span><span class="p">:</span><span class="w"> </span><span class="nb">Int</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="c1">// ✅ 📦 unchanged</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">body</span><span class="p">:</span><span class="w"> </span><span class="n">some</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">VStack</span><span class="p">(</span><span class="n">spacing</span><span class="p">:</span><span class="w"> </span><span class="mi">15</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">HStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Last Refresh: </span><span class="si">\(</span><span class="n">lastRefresh</span><span class="p">.</span><span class="n">formatted</span><span class="si">(</span><span class="n">date</span><span class="p">:</span><span class="w"> </span><span class="p">.</span><span class="n">omitted</span><span class="p">,</span><span class="w"> </span><span class="n">time</span><span class="p">:</span><span class="w"> </span><span class="p">.</span><span class="n">complete</span><span class="si">))</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;Refresh&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">lastRefresh</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">now</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">HStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Username: </span><span class="si">\(</span><span class="n">userStore</span><span class="p">.</span><span class="n">username</span><span class="si">)</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;Randomize&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">userStore</span><span class="p">.</span><span class="n">randomize</span><span class="p">()</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">HStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Has premium: </span><span class="si">\(</span><span class="n">userStore</span><span class="p">.</span><span class="n">hasPremium</span><span class="w"> </span><span class="p">?</span><span class="w"> </span><span class="s">&quot;Yes&quot;</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s">&quot;No!&quot;</span><span class="si">)</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;Toggle&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">userStore</span><span class="p">.</span><span class="n">hasPremium</span><span class="p">.</span><span class="n">toggle</span><span class="p">()</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">HStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Emoji: </span><span class="si">\(</span><span class="n">emojiStore</span><span class="p">.</span><span class="n">emoji</span><span class="si">)</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;Update&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">emojiStore</span><span class="p">.</span><span class="n">update</span><span class="p">()</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">HStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Count: </span><span class="si">\(</span><span class="bp">count</span><span class="si">)</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;+1&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">count</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;-1&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">count</span><span class="w"> </span><span class="o">-=</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">ChildView</span><span class="p">(</span> <span class="w"> </span><span class="n">emojiStore</span><span class="p">:</span><span class="w"> </span><span class="n">emojiStore</span><span class="p">,</span><span class="w"> </span><span class="c1">// ✅ 📦 unchanged</span> <span class="w"> </span><span class="bp">count</span><span class="p">:</span><span class="w"> </span><span class="err">$</span><span class="bp">count</span><span class="w"> </span><span class="c1">// ✅ 📦 unchanged</span> <span class="w"> </span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">.</span><span class="n">padding</span><span class="p">()</span> <span class="w"> </span><span class="p">.</span><span class="n">background</span><span class="p">(</span><span class="n">Rectangle</span><span class="p">().</span><span class="n">stroke</span><span class="p">(</span><span class="n">Color</span><span class="p">.</span><span class="n">purple</span><span class="p">,</span><span class="w"> </span><span class="n">lineWidth</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">))</span> <span class="w"> </span><span class="p">.</span><span class="n">environment</span><span class="p">(</span><span class="err">\</span><span class="p">.</span><span class="n">lastRefresh</span><span class="p">,</span><span class="w"> </span><span class="n">lastRefresh</span><span class="p">)</span><span class="w"> </span><span class="c1">// ✅ 📦 unchanged</span> <span class="w"> </span><span class="p">.</span><span class="n">environmentObject</span><span class="p">(</span><span class="n">userStore</span><span class="p">)</span><span class="w"> </span><span class="c1">// ⛔️ 🔗 replaced with `.environment`</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">ChildView</span><span class="p">:</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="p">@</span><span class="n">Environment</span><span class="p">(</span><span class="err">\</span><span class="p">.</span><span class="n">lastRefresh</span><span class="p">)</span><span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">lastRefresh</span><span class="p">:</span><span class="w"> </span><span class="n">Date</span><span class="w"> </span><span class="c1">// ✅ 📦 unchanged</span> <span class="w"> </span><span class="p">@</span><span class="n">EnvironmentObject</span><span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">userStore</span><span class="p">:</span><span class="w"> </span><span class="n">UserStore</span><span class="w"> </span><span class="c1">// ⛔️ 🔗 replaced with `@Environment`</span> <span class="w"> </span><span class="p">@</span><span class="n">ObservedObject</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">emojiStore</span><span class="p">:</span><span class="w"> </span><span class="n">EmojiStore</span><span class="w"> </span><span class="c1">// ⛔️ 🔗 replaced with `@Bindable`</span> <span class="w"> </span><span class="p">@</span><span class="n">Binding</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">count</span><span class="p">:</span><span class="w"> </span><span class="nb">Int</span><span class="w"> </span><span class="c1">// ✅ 📦 unchanged</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">body</span><span class="p">:</span><span class="w"> </span><span class="n">some</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">VStack</span><span class="p">(</span><span class="n">spacing</span><span class="p">:</span><span class="w"> </span><span class="mi">15</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">HStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Last Refresh: </span><span class="si">\(</span><span class="n">lastRefresh</span><span class="p">.</span><span class="n">formatted</span><span class="si">(</span><span class="n">date</span><span class="p">:</span><span class="w"> </span><span class="p">.</span><span class="n">omitted</span><span class="p">,</span><span class="w"> </span><span class="n">time</span><span class="p">:</span><span class="w"> </span><span class="p">.</span><span class="n">complete</span><span class="si">))</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">HStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Edit username:&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">TextField</span><span class="p">(</span><span class="s">&quot;&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">text</span><span class="p">:</span><span class="w"> </span><span class="err">$</span><span class="n">userStore</span><span class="p">.</span><span class="n">username</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">textInputAutocapitalization</span><span class="p">(.</span><span class="n">never</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">fixedSize</span><span class="p">()</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">HStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Has premium: </span><span class="si">\(</span><span class="n">userStore</span><span class="p">.</span><span class="n">hasPremium</span><span class="w"> </span><span class="p">?</span><span class="w"> </span><span class="s">&quot;Yes&quot;</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s">&quot;No!&quot;</span><span class="si">)</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;Toggle&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">userStore</span><span class="p">.</span><span class="n">hasPremium</span><span class="p">.</span><span class="n">toggle</span><span class="p">()</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">HStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Emoji: </span><span class="si">\(</span><span class="n">emojiStore</span><span class="p">.</span><span class="n">emoji</span><span class="si">)</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;Update&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">emojiStore</span><span class="p">.</span><span class="n">update</span><span class="p">()</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">HStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Count: </span><span class="si">\(</span><span class="bp">count</span><span class="si">)</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;+1&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">count</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;-1&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">count</span><span class="w"> </span><span class="o">-=</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">.</span><span class="n">padding</span><span class="p">()</span> <span class="w"> </span><span class="p">.</span><span class="n">background</span><span class="p">(</span><span class="n">Rectangle</span><span class="p">().</span><span class="n">stroke</span><span class="p">(</span><span class="n">Color</span><span class="p">.</span><span class="n">mint</span><span class="p">,</span><span class="w"> </span><span class="n">lineWidth</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">))</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="c1">// ⛔️ replaced with `#Preview`</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">ParentView_Previews</span><span class="p">:</span><span class="w"> </span><span class="n">PreviewProvider</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">previews</span><span class="p">:</span><span class="w"> </span><span class="n">some</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">ParentView</span><span class="p">()</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="c1">// ⛔️ replaced with `#Preview`</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">ChildView_Previews</span><span class="p">:</span><span class="w"> </span><span class="n">PreviewProvider</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">previews</span><span class="p">:</span><span class="w"> </span><span class="n">some</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Preview</span><span class="p">()</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="nc">Preview</span><span class="p">:</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="p">@</span><span class="n">StateObject</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">userStore</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">UserStore</span><span class="p">()</span><span class="w"> </span><span class="c1">// ⛔️ 🔗 replaced with `@State`</span> <span class="w"> </span><span class="p">@</span><span class="n">StateObject</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">emojiStore</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">EmojiStore</span><span class="p">()</span><span class="w"> </span><span class="c1">// ⛔️ 🔗 replaced with `@State`</span> <span class="w"> </span><span class="p">@</span><span class="n">State</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">count</span><span class="p">:</span><span class="w"> </span><span class="nb">Int</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="c1">// ✅ 📦 unchanged</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">body</span><span class="p">:</span><span class="w"> </span><span class="n">some</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">ChildView</span><span class="p">(</span><span class="n">emojiStore</span><span class="p">:</span><span class="w"> </span><span class="n">emojiStore</span><span class="p">,</span><span class="w"> </span><span class="bp">count</span><span class="p">:</span><span class="w"> </span><span class="err">$</span><span class="bp">count</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">environment</span><span class="p">(</span><span class="err">\</span><span class="p">.</span><span class="n">lastRefresh</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">now</span><span class="p">)</span><span class="w"> </span><span class="c1">// ✅ 📦 unchanged</span> <span class="w"> </span><span class="p">.</span><span class="n">environmentObject</span><span class="p">(</span><span class="n">userStore</span><span class="p">)</span><span class="w"> </span><span class="c1">// ⛔️ 🔗 replaced with `.environment`</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> </code></pre></div> <h4>Between the Lines</h4> <p>As you can see, the two blocks of code are largely the same. The only real differences are in the reference type data flow. Thanks to Observation and <code>@Observable</code> things are a little more simple and a little better aligned with how value types have been handled since iOS 13:</p> <p><strong>1. Source of Truth</strong></p> <ul> <li>📦 <code>@State</code> </li> <li>🔗 <code>@State</code> <em>replacing <code>@StateObject</code></em></li> </ul> <p><strong>2. Read/Write Access</strong></p> <ul> <li>📦 <code>@Binding</code></li> <li>🔗 <code>@Bindable</code> <em>replacing <code>@ObservedObject</code></em></li> </ul> <p><strong>3. Global Access</strong></p> <ul> <li>📦 <code>@Environment</code></li> <li>🔗 <code>@Environment</code> <em>replacing <code>@EnvironmentObject</code></em></li> </ul> <h4>Rough Edges</h4> <p>Observation framework is a great improvement to data flow in SwiftUI, but there are still some awkward parts. The most notable is that we need two different two-way read-write binding mechanisms (<code>@Binding</code> and <code>@Bindable</code>) instead of one unified approach. Even more frustrating is the explicit "rebinding" requirement for working with environment objects:</p> <div class="highlight"><pre><span></span><code><span class="p">@</span><span class="n">Environment</span><span class="p">(</span><span class="n">UserStore</span><span class="p">.</span><span class="kc">self</span><span class="p">)</span><span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">userStore</span> <span class="c1">// ...</span> <span class="p">@</span><span class="n">Bindable</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">userStore</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">userStore</span><span class="w"> </span><span class="c1">// Required &quot;rebinding&quot;</span> </code></pre></div> <p>Hopefully Apple unifies these binding mechanisms in the future, similar to how they've deftly consolidated State and Environment handling!</p>Tue, 26 Nov 2024 00:00:00 -0000https://maxhumber.com/swiftuiflowThe Client Patternhttps://maxhumber.com/clientpattern<p>I recently wrapped up some work on a client project that used <a href="https://github.com/pointfreeco/swift-composable-architecture">TCA</a>. While I didn't love the framework, I'm thankful for the opportunity because it introduced me to something brilliant: the Client Pattern.</p> <h4>The Client Pattern</h4> <p>The thing that I'm calling the Client Pattern is really just a simplified version of Point-Free's <code>DependencyClient</code> from their <a href="https://github.com/pointfreeco/swift-dependencies"><code>swift-dependencies</code></a> library. While the benefits are well articulated in <a href="https://www.pointfree.co/blog/posts/120-macro-bonanza-dependencies">this blog post</a>, you can get most of the value with just a few lines of Swift:</p> <div class="highlight"><pre><span></span><code><span class="kd">import</span><span class="w"> </span><span class="nc">Foundation</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">MyClient</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">fetch</span><span class="p">:</span><span class="w"> </span><span class="p">@</span><span class="n">Sendable</span><span class="w"> </span><span class="p">(</span><span class="kc">_</span><span class="w"> </span><span class="n">value</span><span class="p">:</span><span class="w"> </span><span class="nb">Int</span><span class="p">)</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kr">throws</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="nb">String</span> <span class="p">}</span> <span class="kd">extension</span><span class="w"> </span><span class="nc">MyClient</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">live</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">Self</span><span class="p">(</span> <span class="w"> </span><span class="n">fetch</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">value</span><span class="w"> </span><span class="k">in</span> <span class="w"> </span><span class="c1">// Implementation here...</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="s">&quot;Fetched value: </span><span class="si">\(</span><span class="n">value</span><span class="si">)</span><span class="s">&quot;</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">)</span> <span class="p">}</span> <span class="kd">func</span><span class="w"> </span><span class="nf">fetch</span><span class="p">(</span><span class="n">with</span><span class="w"> </span><span class="n">client</span><span class="p">:</span><span class="w"> </span><span class="n">MyClient</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">live</span><span class="p">)</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kr">throws</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">result</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="n">client</span><span class="p">.</span><span class="n">fetch</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="w"> </span><span class="bp">print</span><span class="p">(</span><span class="n">result</span><span class="p">)</span> <span class="p">}</span> </code></pre></div> <p>No protocols. No abstractions. No third-party dependencies to manage... dependencies. Just structs and closures to make testing downstream dead simple.</p> <h4>A "Real" Example</h4> <p>To demonstrate the real power of the Client Pattern, let's build a client for the <a href="https://sunrise-sunset.org/api">Sunrise-Sunset API</a>.</p> <p>First, define the response type:</p> <div class="highlight"><pre><span></span><code><span class="kd">import</span><span class="w"> </span><span class="nc">Foundation</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">SuntimesResponse</span><span class="p">:</span><span class="w"> </span><span class="n">Decodable</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">results</span><span class="p">:</span><span class="w"> </span><span class="n">Results</span> <span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="nc">Results</span><span class="p">:</span><span class="w"> </span><span class="n">Decodable</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">sunrise</span><span class="p">:</span><span class="w"> </span><span class="n">Date</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">sunset</span><span class="p">:</span><span class="w"> </span><span class="n">Date</span> <span class="w"> </span><span class="kd">enum</span><span class="w"> </span><span class="nc">CodingKeys</span><span class="p">:</span><span class="w"> </span><span class="nb">String</span><span class="p">,</span><span class="w"> </span><span class="n">CodingKey</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="n">sunrise</span> <span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="n">sunset</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> </code></pre></div> <p>Then create the client interface:</p> <div class="highlight"><pre><span></span><code><span class="kd">struct</span><span class="w"> </span><span class="nc">SuntimesClient</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">fetchSunrise</span><span class="p">:</span><span class="w"> </span><span class="p">@</span><span class="n">Sendable</span><span class="w"> </span><span class="p">(</span><span class="kc">_</span><span class="w"> </span><span class="n">latitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">,</span><span class="w"> </span><span class="kc">_</span><span class="w"> </span><span class="n">longitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">)</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kr">throws</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">Date</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">fetchSunset</span><span class="p">:</span><span class="w"> </span><span class="p">@</span><span class="n">Sendable</span><span class="w"> </span><span class="p">(</span><span class="kc">_</span><span class="w"> </span><span class="n">latitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">,</span><span class="w"> </span><span class="kc">_</span><span class="w"> </span><span class="n">longitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">)</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kr">throws</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">Date</span> <span class="p">}</span> </code></pre></div> <p>Extend the client with a <code>.live</code> implementation (to do the actual networking):</p> <div class="highlight"><pre><span></span><code><span class="kd">extension</span><span class="w"> </span><span class="nc">SuntimesClient</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">live</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">Self</span><span class="p">(</span> <span class="w"> </span><span class="n">fetchSunrise</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">latitude</span><span class="p">,</span><span class="w"> </span><span class="n">longitude</span><span class="w"> </span><span class="k">in</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">result</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="kc">Self</span><span class="p">.</span><span class="n">fetchSuntimes</span><span class="p">(</span><span class="n">latitude</span><span class="p">:</span><span class="w"> </span><span class="n">latitude</span><span class="p">,</span><span class="w"> </span><span class="n">longitude</span><span class="p">:</span><span class="w"> </span><span class="n">longitude</span><span class="p">)</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">result</span><span class="p">.</span><span class="n">sunrise</span> <span class="w"> </span><span class="p">},</span> <span class="w"> </span><span class="n">fetchSunset</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">latitude</span><span class="p">,</span><span class="w"> </span><span class="n">longitude</span><span class="w"> </span><span class="k">in</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">result</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="kc">Self</span><span class="p">.</span><span class="n">fetchSuntimes</span><span class="p">(</span><span class="n">latitude</span><span class="p">:</span><span class="w"> </span><span class="n">latitude</span><span class="p">,</span><span class="w"> </span><span class="n">longitude</span><span class="p">:</span><span class="w"> </span><span class="n">longitude</span><span class="p">)</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">result</span><span class="p">.</span><span class="n">sunset</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">)</span> <span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">fetchSuntimes</span><span class="p">(</span><span class="n">latitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">,</span><span class="w"> </span><span class="n">longitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">)</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kr">throws</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">SuntimesResponse</span><span class="p">.</span><span class="n">Results</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">components</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">URLComponents</span><span class="p">(</span><span class="n">string</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;https://api.sunrise-sunset.org/json&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">components</span><span class="p">?.</span><span class="n">queryItems</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[</span> <span class="w"> </span><span class="n">URLQueryItem</span><span class="p">(</span><span class="n">name</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;lat&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">value</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;</span><span class="si">\(</span><span class="n">latitude</span><span class="si">)</span><span class="s">&quot;</span><span class="p">),</span> <span class="w"> </span><span class="n">URLQueryItem</span><span class="p">(</span><span class="n">name</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;lng&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">value</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;</span><span class="si">\(</span><span class="n">longitude</span><span class="si">)</span><span class="s">&quot;</span><span class="p">),</span> <span class="w"> </span><span class="n">URLQueryItem</span><span class="p">(</span><span class="n">name</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;formatted&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">value</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;0&quot;</span><span class="p">)</span> <span class="w"> </span><span class="p">]</span> <span class="w"> </span><span class="k">guard</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">url</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">components</span><span class="p">?.</span><span class="n">url</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="n">URLError</span><span class="p">(.</span><span class="n">badURL</span><span class="p">)</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="p">(</span><span class="n">data</span><span class="p">,</span><span class="w"> </span><span class="kc">_</span><span class="p">)</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="n">URLSession</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">data</span><span class="p">(</span><span class="n">from</span><span class="p">:</span><span class="w"> </span><span class="n">url</span><span class="p">)</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">decoder</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">JSONDecoder</span><span class="p">()</span> <span class="w"> </span><span class="n">decoder</span><span class="p">.</span><span class="n">dateDecodingStrategy</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">iso8601</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">response</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="n">decoder</span><span class="p">.</span><span class="n">decode</span><span class="p">(</span><span class="n">SuntimesResponse</span><span class="p">.</span><span class="kc">self</span><span class="p">,</span><span class="w"> </span><span class="n">from</span><span class="p">:</span><span class="w"> </span><span class="n">data</span><span class="p">)</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">response</span><span class="p">.</span><span class="n">results</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> </code></pre></div> <p>Use the <code>@Entry</code> macro to inject the <code>.live</code> implementation into the SwiftUI environment:</p> <div class="highlight"><pre><span></span><code><span class="kd">import</span><span class="w"> </span><span class="nc">SwiftUI</span> <span class="kd">extension</span><span class="w"> </span><span class="nc">EnvironmentValues</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="p">@</span><span class="n">Entry</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">suntimesClient</span><span class="p">:</span><span class="w"> </span><span class="n">SuntimesClient</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">live</span> <span class="p">}</span> </code></pre></div> <p>And create a view that picks up the injected client:</p> <div class="highlight"><pre><span></span><code><span class="kd">struct</span><span class="w"> </span><span class="nc">SuntimesClientView</span><span class="p">:</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="p">@</span><span class="n">Environment</span><span class="p">(</span><span class="err">\</span><span class="p">.</span><span class="n">suntimesClient</span><span class="p">)</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">client</span><span class="p">:</span><span class="w"> </span><span class="n">SuntimesClient</span> <span class="w"> </span><span class="p">@</span><span class="n">State</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">sunset</span><span class="p">:</span><span class="w"> </span><span class="n">Date</span><span class="p">?</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">body</span><span class="p">:</span><span class="w"> </span><span class="n">some</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">VStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="n">sunset</span><span class="p">?.</span><span class="n">formatted</span><span class="p">()</span><span class="w"> </span><span class="p">??</span><span class="w"> </span><span class="s">&quot;&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;Fetch Sunset&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Task</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="n">fetchSunset</span><span class="p">()</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">fetchSunset</span><span class="p">()</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="k">do</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">sunset</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="n">client</span><span class="p">.</span><span class="n">fetchSunset</span><span class="p">(</span><span class="mf">43.6532</span><span class="p">,</span><span class="w"> </span><span class="o">-</span><span class="mf">79.3832</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="bp">print</span><span class="p">(</span><span class="n">error</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="p">#</span><span class="n">Preview</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">SuntimesClientView</span><span class="p">()</span> <span class="p">}</span> </code></pre></div> <p>With the Client Pattern in place, testing and previews become incredibly simple. Just define a mock client:</p> <div class="highlight"><pre><span></span><code><span class="kd">extension</span><span class="w"> </span><span class="nc">SuntimesClient</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">preview</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">Self</span><span class="p">(</span> <span class="w"> </span><span class="n">fetchSunrise</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kc">_</span><span class="p">,</span><span class="w"> </span><span class="kc">_</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="n">URLError</span><span class="p">(.</span><span class="n">badServerResponse</span><span class="p">)</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="c1">// Simulates an error</span> <span class="w"> </span><span class="n">fetchSunset</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kc">_</span><span class="p">,</span><span class="w"> </span><span class="kc">_</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="p">.</span><span class="n">now</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="c1">// Returns the current time for sunset</span> <span class="w"> </span><span class="p">)</span> <span class="p">}</span> </code></pre></div> <p>And inject it into previews:</p> <div class="highlight"><pre><span></span><code><span class="p">#</span><span class="n">Preview</span><span class="p">(</span><span class="s">&quot;Client Pattern (Mock)&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">SuntimesClientView</span><span class="p">()</span> <span class="w"> </span><span class="p">.</span><span class="n">environment</span><span class="p">(</span><span class="err">\</span><span class="p">.</span><span class="n">suntimesClient</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">preview</span><span class="p">)</span><span class="w"> </span><span class="c1">// Injects mock client</span> <span class="p">}</span> </code></pre></div> <h4>Copy-and-paste</h4> <p>Here's all of the code in a single block that you can copy-and-paste directly into Xcode to try out the Client Pattern for yourself:</p> <div class="highlight"><pre><span></span><code><span class="kd">import</span><span class="w"> </span><span class="nc">SwiftUI</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">SuntimesResponse</span><span class="p">:</span><span class="w"> </span><span class="n">Decodable</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">results</span><span class="p">:</span><span class="w"> </span><span class="n">Results</span> <span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="nc">Results</span><span class="p">:</span><span class="w"> </span><span class="n">Decodable</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">sunrise</span><span class="p">:</span><span class="w"> </span><span class="n">Date</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">sunset</span><span class="p">:</span><span class="w"> </span><span class="n">Date</span> <span class="w"> </span><span class="kd">enum</span><span class="w"> </span><span class="nc">CodingKeys</span><span class="p">:</span><span class="w"> </span><span class="nb">String</span><span class="p">,</span><span class="w"> </span><span class="n">CodingKey</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="n">sunrise</span> <span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="n">sunset</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="c1">// MARK: - Client Definition</span> <span class="c1">// Provides async closures for fetching sunrise and sunset data (Swift 6 ready!)</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">SuntimesClient</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">fetchSunrise</span><span class="p">:</span><span class="w"> </span><span class="p">@</span><span class="n">Sendable</span><span class="w"> </span><span class="p">(</span><span class="kc">_</span><span class="w"> </span><span class="n">latitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">,</span><span class="w"> </span><span class="kc">_</span><span class="w"> </span><span class="n">longitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">)</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kr">throws</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">Date</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">fetchSunset</span><span class="p">:</span><span class="w"> </span><span class="p">@</span><span class="n">Sendable</span><span class="w"> </span><span class="p">(</span><span class="kc">_</span><span class="w"> </span><span class="n">latitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">,</span><span class="w"> </span><span class="kc">_</span><span class="w"> </span><span class="n">longitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">)</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kr">throws</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">Date</span> <span class="p">}</span> <span class="c1">// MARK: - Live Implementation</span> <span class="c1">// Implements live API calls for sunrise and sunset</span> <span class="kd">extension</span><span class="w"> </span><span class="nc">SuntimesClient</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">live</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">Self</span><span class="p">(</span> <span class="w"> </span><span class="n">fetchSunrise</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">latitude</span><span class="p">,</span><span class="w"> </span><span class="n">longitude</span><span class="w"> </span><span class="k">in</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">result</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="kc">Self</span><span class="p">.</span><span class="n">fetchSuntimes</span><span class="p">(</span><span class="n">latitude</span><span class="p">:</span><span class="w"> </span><span class="n">latitude</span><span class="p">,</span><span class="w"> </span><span class="n">longitude</span><span class="p">:</span><span class="w"> </span><span class="n">longitude</span><span class="p">)</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">result</span><span class="p">.</span><span class="n">sunrise</span> <span class="w"> </span><span class="p">},</span> <span class="w"> </span><span class="n">fetchSunset</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">latitude</span><span class="p">,</span><span class="w"> </span><span class="n">longitude</span><span class="w"> </span><span class="k">in</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">result</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="kc">Self</span><span class="p">.</span><span class="n">fetchSuntimes</span><span class="p">(</span><span class="n">latitude</span><span class="p">:</span><span class="w"> </span><span class="n">latitude</span><span class="p">,</span><span class="w"> </span><span class="n">longitude</span><span class="p">:</span><span class="w"> </span><span class="n">longitude</span><span class="p">)</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">result</span><span class="p">.</span><span class="n">sunset</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">)</span> <span class="w"> </span><span class="c1">// Helper: Makes the API call to retrieve sunrise and sunset data</span> <span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">fetchSuntimes</span><span class="p">(</span><span class="n">latitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">,</span><span class="w"> </span><span class="n">longitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">)</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kr">throws</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">SuntimesResponse</span><span class="p">.</span><span class="n">Results</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">components</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">URLComponents</span><span class="p">(</span><span class="n">string</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;https://api.sunrise-sunset.org/json&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">components</span><span class="p">?.</span><span class="n">queryItems</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[</span> <span class="w"> </span><span class="n">URLQueryItem</span><span class="p">(</span><span class="n">name</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;lat&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">value</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;</span><span class="si">\(</span><span class="n">latitude</span><span class="si">)</span><span class="s">&quot;</span><span class="p">),</span> <span class="w"> </span><span class="n">URLQueryItem</span><span class="p">(</span><span class="n">name</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;lng&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">value</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;</span><span class="si">\(</span><span class="n">longitude</span><span class="si">)</span><span class="s">&quot;</span><span class="p">),</span> <span class="w"> </span><span class="n">URLQueryItem</span><span class="p">(</span><span class="n">name</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;formatted&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">value</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;0&quot;</span><span class="p">)</span> <span class="w"> </span><span class="p">]</span> <span class="w"> </span><span class="k">guard</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">url</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">components</span><span class="p">?.</span><span class="n">url</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="n">URLError</span><span class="p">(.</span><span class="n">badURL</span><span class="p">)</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="p">(</span><span class="n">data</span><span class="p">,</span><span class="w"> </span><span class="kc">_</span><span class="p">)</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="n">URLSession</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">data</span><span class="p">(</span><span class="n">from</span><span class="p">:</span><span class="w"> </span><span class="n">url</span><span class="p">)</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">decoder</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">JSONDecoder</span><span class="p">()</span> <span class="w"> </span><span class="n">decoder</span><span class="p">.</span><span class="n">dateDecodingStrategy</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">iso8601</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">response</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="n">decoder</span><span class="p">.</span><span class="n">decode</span><span class="p">(</span><span class="n">SuntimesResponse</span><span class="p">.</span><span class="kc">self</span><span class="p">,</span><span class="w"> </span><span class="n">from</span><span class="p">:</span><span class="w"> </span><span class="n">data</span><span class="p">)</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">response</span><span class="p">.</span><span class="n">results</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="c1">// MARK: - Preview Implementation</span> <span class="c1">// Simulates sunrise and sunset fetching for previews and testing</span> <span class="kd">extension</span><span class="w"> </span><span class="nc">SuntimesClient</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">preview</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">Self</span><span class="p">(</span> <span class="w"> </span><span class="n">fetchSunrise</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kc">_</span><span class="p">,</span><span class="w"> </span><span class="kc">_</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="n">URLError</span><span class="p">(.</span><span class="n">badServerResponse</span><span class="p">)</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="c1">// Simulates an error</span> <span class="w"> </span><span class="n">fetchSunset</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kc">_</span><span class="p">,</span><span class="w"> </span><span class="kc">_</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="p">.</span><span class="n">now</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="c1">// Returns the current time for sunset</span> <span class="w"> </span><span class="p">)</span> <span class="p">}</span> <span class="c1">// MARK: - Environment Values Extension</span> <span class="c1">// Adds `@Environment(\.suntimesClient) var suntimesClient` to the SwiftUI environment (default to .live)</span> <span class="kd">extension</span><span class="w"> </span><span class="nc">EnvironmentValues</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="p">@</span><span class="n">Entry</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">suntimesClient</span><span class="p">:</span><span class="w"> </span><span class="n">SuntimesClient</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">live</span> <span class="p">}</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">SuntimesClientView</span><span class="p">:</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="p">@</span><span class="n">Environment</span><span class="p">(</span><span class="err">\</span><span class="p">.</span><span class="n">suntimesClient</span><span class="p">)</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">client</span><span class="p">:</span><span class="w"> </span><span class="n">SuntimesClient</span> <span class="w"> </span><span class="p">@</span><span class="n">State</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">sunset</span><span class="p">:</span><span class="w"> </span><span class="n">Date</span><span class="p">?</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">body</span><span class="p">:</span><span class="w"> </span><span class="n">some</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">VStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="n">sunset</span><span class="p">?.</span><span class="n">formatted</span><span class="p">()</span><span class="w"> </span><span class="p">??</span><span class="w"> </span><span class="s">&quot;&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;Fetch Sunset&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Task</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="n">fetchSunset</span><span class="p">()</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="c1">// Fetches sunset data asynchronously and updates the state</span> <span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">fetchSunset</span><span class="p">()</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="k">do</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">sunset</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="n">client</span><span class="p">.</span><span class="n">fetchSunset</span><span class="p">(</span><span class="mf">43.6532</span><span class="p">,</span><span class="w"> </span><span class="o">-</span><span class="mf">79.3832</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="bp">print</span><span class="p">(</span><span class="n">error</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="c1">// MARK: - Previews</span> <span class="p">#</span><span class="n">Preview</span><span class="p">(</span><span class="s">&quot;Client Pattern (Live)&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">SuntimesClientView</span><span class="p">()</span> <span class="p">}</span> <span class="p">#</span><span class="n">Preview</span><span class="p">(</span><span class="s">&quot;Client Pattern (Mock)&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">SuntimesClientView</span><span class="p">()</span> <span class="w"> </span><span class="p">.</span><span class="n">environment</span><span class="p">(</span><span class="err">\</span><span class="p">.</span><span class="n">suntimesClient</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">preview</span><span class="p">)</span><span class="w"> </span><span class="c1">// Injects mock client</span> <span class="p">}</span> </code></pre></div> <h4>The Old Way (Don't Do This)</h4> <p>For comparison, here's the protocol-based <a href="https://medium.com/livefront/creating-a-service-layer-in-swift-ea771088fb66">Service Pattern</a> I've previously used for app networking:</p> <div class="highlight"><pre><span></span><code><span class="kd">import</span><span class="w"> </span><span class="nc">SwiftUI</span> <span class="kd">import</span><span class="w"> </span><span class="nc">Foundation</span> <span class="c1">// MARK: - Protocol Definition</span> <span class="c1">// Enables mocking and testing of sunrise/sunset fetching</span> <span class="kd">protocol</span><span class="w"> </span><span class="nc">SuntimesServiceable</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">fetchSunrise</span><span class="p">(</span><span class="n">latitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">,</span><span class="w"> </span><span class="n">longitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">)</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kr">throws</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">Date</span> <span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">fetchSunset</span><span class="p">(</span><span class="n">latitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">,</span><span class="w"> </span><span class="n">longitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">)</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kr">throws</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">Date</span> <span class="p">}</span> <span class="c1">// MARK: - Live Service Implementation</span> <span class="c1">// Implements the SuntimesServiceable protocol for actual API calls</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">SuntimesService</span><span class="p">:</span><span class="w"> </span><span class="n">SuntimesServiceable</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">fetchSunrise</span><span class="p">(</span><span class="n">latitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">,</span><span class="w"> </span><span class="n">longitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">)</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kr">throws</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">Date</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">results</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="n">fetchSuntimes</span><span class="p">(</span><span class="n">latitude</span><span class="p">:</span><span class="w"> </span><span class="n">latitude</span><span class="p">,</span><span class="w"> </span><span class="n">longitude</span><span class="p">:</span><span class="w"> </span><span class="n">longitude</span><span class="p">)</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">results</span><span class="p">.</span><span class="n">sunrise</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">fetchSunset</span><span class="p">(</span><span class="n">latitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">,</span><span class="w"> </span><span class="n">longitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">)</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kr">throws</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">Date</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">results</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="n">fetchSuntimes</span><span class="p">(</span><span class="n">latitude</span><span class="p">:</span><span class="w"> </span><span class="n">latitude</span><span class="p">,</span><span class="w"> </span><span class="n">longitude</span><span class="p">:</span><span class="w"> </span><span class="n">longitude</span><span class="p">)</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">results</span><span class="p">.</span><span class="n">sunset</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="c1">// Helper: Makes the API call to retrieve sunrise and sunset data</span> <span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">fetchSuntimes</span><span class="p">(</span><span class="n">latitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">,</span><span class="w"> </span><span class="n">longitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">)</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kr">throws</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">SuntimesResponse</span><span class="p">.</span><span class="n">Results</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">components</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">URLComponents</span><span class="p">(</span><span class="n">string</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;https://api.sunrise-sunset.org/json&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">components</span><span class="p">?.</span><span class="n">queryItems</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[</span> <span class="w"> </span><span class="n">URLQueryItem</span><span class="p">(</span><span class="n">name</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;lat&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">value</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;</span><span class="si">\(</span><span class="n">latitude</span><span class="si">)</span><span class="s">&quot;</span><span class="p">),</span> <span class="w"> </span><span class="n">URLQueryItem</span><span class="p">(</span><span class="n">name</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;lng&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">value</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;</span><span class="si">\(</span><span class="n">longitude</span><span class="si">)</span><span class="s">&quot;</span><span class="p">),</span> <span class="w"> </span><span class="n">URLQueryItem</span><span class="p">(</span><span class="n">name</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;formatted&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">value</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;0&quot;</span><span class="p">)</span> <span class="w"> </span><span class="p">]</span> <span class="w"> </span><span class="k">guard</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">url</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">components</span><span class="p">?.</span><span class="n">url</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="n">URLError</span><span class="p">(.</span><span class="n">badURL</span><span class="p">)</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="p">(</span><span class="n">data</span><span class="p">,</span><span class="w"> </span><span class="kc">_</span><span class="p">)</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="n">URLSession</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">data</span><span class="p">(</span><span class="n">from</span><span class="p">:</span><span class="w"> </span><span class="n">url</span><span class="p">)</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">decoder</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">JSONDecoder</span><span class="p">()</span> <span class="w"> </span><span class="n">decoder</span><span class="p">.</span><span class="n">dateDecodingStrategy</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">iso8601</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">response</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="n">decoder</span><span class="p">.</span><span class="n">decode</span><span class="p">(</span><span class="n">SuntimesResponse</span><span class="p">.</span><span class="kc">self</span><span class="p">,</span><span class="w"> </span><span class="n">from</span><span class="p">:</span><span class="w"> </span><span class="n">data</span><span class="p">)</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">response</span><span class="p">.</span><span class="n">results</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="c1">// MARK: - Mock Service for Testing</span> <span class="c1">// Provides predictable behavior for testing without actual API calls</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">MockSuntimesService</span><span class="p">:</span><span class="w"> </span><span class="n">SuntimesServiceable</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">fetchSunrise</span><span class="p">(</span><span class="n">latitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">,</span><span class="w"> </span><span class="n">longitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">)</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kr">throws</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">Date</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="p">.</span><span class="n">now</span><span class="w"> </span><span class="c1">// Returns the current time for sunrise</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">fetchSunset</span><span class="p">(</span><span class="n">latitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">,</span><span class="w"> </span><span class="n">longitude</span><span class="p">:</span><span class="w"> </span><span class="nb">Double</span><span class="p">)</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kr">throws</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">Date</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="n">URLError</span><span class="p">(.</span><span class="n">badServerResponse</span><span class="p">)</span><span class="w"> </span><span class="c1">// Simulates an error</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">SuntimesServiceView</span><span class="p">:</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="p">@</span><span class="n">State</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">sunrise</span><span class="p">:</span><span class="w"> </span><span class="n">Date</span><span class="p">?</span> <span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">service</span><span class="p">:</span><span class="w"> </span><span class="n">SuntimesServiceable</span> <span class="w"> </span><span class="c1">// Injects the dependency, defaulting to the live service</span> <span class="w"> </span><span class="kd">init</span><span class="p">(</span><span class="n">service</span><span class="p">:</span><span class="w"> </span><span class="n">SuntimesServiceable</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">SuntimesService</span><span class="p">())</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kc">self</span><span class="p">.</span><span class="n">service</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">service</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">body</span><span class="p">:</span><span class="w"> </span><span class="n">some</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">VStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="n">sunrise</span><span class="p">?.</span><span class="n">formatted</span><span class="p">()</span><span class="w"> </span><span class="p">??</span><span class="w"> </span><span class="s">&quot;&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="s">&quot;Fetch Sunrise&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Task</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="n">fetchSunrise</span><span class="p">()</span><span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="c1">// Fetches sunrise data asynchronously</span> <span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">fetchSunrise</span><span class="p">()</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="k">do</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="c1">// Warning: Swift 6 Error (Sending &#39;self.service&#39; risks causing data races)</span> <span class="w"> </span><span class="c1">// Sending main actor-isolated &#39;self.service&#39; to nonisolated instance method &#39;fetchSunrise(latitude:longitude:)&#39; </span> <span class="w"> </span><span class="c1">// risks causing data races between nonisolated and main actor-isolated uses</span> <span class="w"> </span><span class="n">sunrise</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="n">service</span><span class="p">.</span><span class="n">fetchSunrise</span><span class="p">(</span><span class="n">latitude</span><span class="p">:</span><span class="w"> </span><span class="mf">43.6532</span><span class="p">,</span><span class="w"> </span><span class="n">longitude</span><span class="p">:</span><span class="w"> </span><span class="o">-</span><span class="mf">79.3832</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">catch</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="bp">print</span><span class="p">(</span><span class="n">error</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="c1">// MARK: - Previews</span> <span class="p">#</span><span class="n">Preview</span><span class="p">(</span><span class="s">&quot;Service Pattern (Live)&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">SuntimesServiceView</span><span class="p">(</span><span class="n">service</span><span class="p">:</span><span class="w"> </span><span class="n">SuntimesService</span><span class="p">())</span> <span class="p">}</span> <span class="p">#</span><span class="n">Preview</span><span class="p">(</span><span class="s">&quot;Service Pattern (Preview)&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">SuntimesServiceView</span><span class="p">(</span><span class="n">service</span><span class="p">:</span><span class="w"> </span><span class="n">MockSuntimesService</span><span class="p">())</span> <span class="p">}</span> </code></pre></div> <h4>Keeping It Simple</h4> <p>I'm really excited about the Client Pattern going forward as it:</p> <ul> <li>Removes protocol overhead</li> <li>Makes testing easier with closures </li> <li>Integrates well with SwiftUI</li> <li>And requires less code</li> </ul>Wed, 20 Nov 2024 00:00:00 -0000https://maxhumber.com/clientpatternGrug Brained SwiftUIhttps://maxhumber.com/grugui<p>A SwiftUI tribute to the timeless wisdom of "<a href="https://grugbrain.dev/">The Grug Brained Developer</a>", a guide that shows simplicity is not just a choice but a way of life.</p> <h4>swiftui</h4> <p>one day grug find swiftui</p> <p>grug like swiftui</p> <p>swiftui good. swiftui simple. swiftui flow like river</p> <p>but some dev stack rock in river, make waterfall of sad</p> <p>grug think "why fight river with rock???"</p> <p>"why make simple thing complex???"</p> <p>grug say "stop fight river. let river flow!"</p> <p>but some dev not listen</p> <p>some dev throw rock in river again and again</p> <h4>mvvm</h4> <p>grug see dev who fight river also use mvvm</p> <p>grug not big fan. grug know mvvm not always fit swiftui</p> <p>but if mvvm there, grug stay quiet, for shared goal</p> <p>until grug see <em>nested view model</em></p> <p>grug think "why nest view model like angry babushka doll??!"</p> <p>grug say "view model not nest. view model hold state for one thing, not many things!"</p> <p>nest not just throw rock in river but build dam of despair</p> <h4>llm</h4> <p>grug see dev who fight river also use llm for swiftui</p> <p>grug think "llm fine. good tool, like sharp axe"</p> <p>but some dev swing axe bad</p> <p>use llm code, no check if good</p> <p>like build boat with bad wood</p> <p>boat float, then sink</p> <p>grug say "llm not magic"</p> <p>"tell llm wrong thing, like <em>help uncle jack off the horse</em>"</p> <p>"llm give handjob to horse instead"</p> <p>grug say "llm only good if dev know good llm code from bad"</p> <p>boat sink if not</p> <h4>complexity</h4> <p>grug know all swiftui struggles come from one thing: complexity</p> <p>complexity creep in slow</p> <p>grow like debris in river</p> <p>at first seem fine</p> <p>then river clog </p> <p>water stop</p> <p>code become swamp</p> <p>grug only want swiftui stay like river and not turn to swamp</p>Sat, 16 Nov 2024 00:00:00 -0000https://maxhumber.com/gruguiSwiftUI is not UIKithttps://maxhumber.com/notuikit<p>Programming basically boils down to two things: data flow and naming stuff. Simple in theory. Super easy to complicate in practice. To illustrate the point let's look at some iOS code that updates an emoji on a "parent" view from a "child" view action. </p> <h4>Boilerplate UIKit</h4> <p>In UIKit this relatively simple task becomes a whole sad song and dance:</p> <div class="highlight"><pre><span></span><code><span class="kd">import</span><span class="w"> </span><span class="nc">UIKit</span> <span class="kd">class</span><span class="w"> </span><span class="nc">ParentViewController</span><span class="p">:</span><span class="w"> </span><span class="bp">UIViewController</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">labelText</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;🐶&quot;</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kr">didSet</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">label</span><span class="p">.</span><span class="n">text</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;Label: </span><span class="si">\(</span><span class="n">labelText</span><span class="si">)</span><span class="s">&quot;</span> <span class="w"> </span><span class="n">childViewController</span><span class="p">?.</span><span class="n">updateLabel</span><span class="p">(</span><span class="n">with</span><span class="p">:</span><span class="w"> </span><span class="n">labelText</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">label</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="bp">UILabel</span><span class="p">()</span> <span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">childContainer</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="bp">UIView</span><span class="p">()</span> <span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">childViewController</span><span class="p">:</span><span class="w"> </span><span class="n">ChildViewController</span><span class="p">?</span> <span class="w"> </span><span class="kr">override</span><span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">viewDidLoad</span><span class="p">()</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kc">super</span><span class="p">.</span><span class="n">viewDidLoad</span><span class="p">()</span> <span class="w"> </span><span class="n">setupUI</span><span class="p">()</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">setupUI</span><span class="p">()</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">view</span><span class="p">.</span><span class="n">backgroundColor</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">white</span> <span class="w"> </span><span class="n">view</span><span class="p">.</span><span class="n">layer</span><span class="p">.</span><span class="n">borderColor</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="bp">UIColor</span><span class="p">.</span><span class="n">orange</span><span class="p">.</span><span class="n">cgColor</span> <span class="w"> </span><span class="n">view</span><span class="p">.</span><span class="n">layer</span><span class="p">.</span><span class="n">borderWidth</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">1</span> <span class="w"> </span><span class="n">view</span><span class="p">.</span><span class="n">layoutMargins</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">UIEdgeInsets</span><span class="p">(</span><span class="n">top</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="kr">left</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="n">bottom</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="kr">right</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">)</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">parentStack</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="bp">UIStackView</span><span class="p">(</span><span class="n">arrangedSubviews</span><span class="p">:</span><span class="w"> </span><span class="p">[</span> <span class="w"> </span><span class="n">createParentLabelStack</span><span class="p">(),</span><span class="w"> </span><span class="n">childContainer</span> <span class="w"> </span><span class="p">])</span> <span class="w"> </span><span class="n">parentStack</span><span class="p">.</span><span class="n">axis</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">vertical</span> <span class="w"> </span><span class="n">parentStack</span><span class="p">.</span><span class="n">spacing</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">8</span> <span class="w"> </span><span class="n">parentStack</span><span class="p">.</span><span class="n">translatesAutoresizingMaskIntoConstraints</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">false</span> <span class="w"> </span><span class="n">view</span><span class="p">.</span><span class="n">addSubview</span><span class="p">(</span><span class="n">parentStack</span><span class="p">)</span> <span class="w"> </span><span class="bp">NSLayoutConstraint</span><span class="p">.</span><span class="n">activate</span><span class="p">([</span> <span class="w"> </span><span class="n">parentStack</span><span class="p">.</span><span class="n">centerXAnchor</span><span class="p">.</span><span class="n">constraint</span><span class="p">(</span><span class="n">equalTo</span><span class="p">:</span><span class="w"> </span><span class="n">view</span><span class="p">.</span><span class="n">centerXAnchor</span><span class="p">),</span> <span class="w"> </span><span class="n">parentStack</span><span class="p">.</span><span class="n">centerYAnchor</span><span class="p">.</span><span class="n">constraint</span><span class="p">(</span><span class="n">equalTo</span><span class="p">:</span><span class="w"> </span><span class="n">view</span><span class="p">.</span><span class="n">centerYAnchor</span><span class="p">),</span> <span class="w"> </span><span class="n">childContainer</span><span class="p">.</span><span class="n">heightAnchor</span><span class="p">.</span><span class="n">constraint</span><span class="p">(</span><span class="n">equalToConstant</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="p">),</span> <span class="w"> </span><span class="n">childContainer</span><span class="p">.</span><span class="n">widthAnchor</span><span class="p">.</span><span class="n">constraint</span><span class="p">(</span><span class="n">equalToConstant</span><span class="p">:</span><span class="w"> </span><span class="mi">150</span><span class="p">)</span> <span class="w"> </span><span class="p">])</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">childVC</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">ChildViewController</span><span class="p">()</span> <span class="w"> </span><span class="n">childVC</span><span class="p">.</span><span class="n">labelText</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">labelText</span> <span class="w"> </span><span class="n">childVC</span><span class="p">.</span><span class="n">onUpdateText</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">[</span><span class="kr">weak</span><span class="w"> </span><span class="kc">self</span><span class="p">]</span><span class="w"> </span><span class="n">newText</span><span class="w"> </span><span class="k">in</span> <span class="w"> </span><span class="kc">self</span><span class="p">?.</span><span class="n">labelText</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">newText</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="n">addChild</span><span class="p">(</span><span class="n">childVC</span><span class="p">)</span> <span class="w"> </span><span class="n">childContainer</span><span class="p">.</span><span class="n">addSubview</span><span class="p">(</span><span class="n">childVC</span><span class="p">.</span><span class="n">view</span><span class="p">)</span> <span class="w"> </span><span class="n">childVC</span><span class="p">.</span><span class="n">view</span><span class="p">.</span><span class="n">frame</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">childContainer</span><span class="p">.</span><span class="n">bounds</span> <span class="w"> </span><span class="n">childVC</span><span class="p">.</span><span class="n">didMove</span><span class="p">(</span><span class="n">toParent</span><span class="p">:</span><span class="w"> </span><span class="kc">self</span><span class="p">)</span> <span class="w"> </span><span class="n">childViewController</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">childVC</span> <span class="w"> </span><span class="n">childContainer</span><span class="p">.</span><span class="n">layoutMargins</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">UIEdgeInsets</span><span class="p">(</span> <span class="w"> </span><span class="n">top</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="kr">left</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="n">bottom</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="kr">right</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span> <span class="w"> </span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">createParentLabelStack</span><span class="p">()</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="bp">UIView</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">parentLabel</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="bp">UILabel</span><span class="p">()</span> <span class="w"> </span><span class="n">parentLabel</span><span class="p">.</span><span class="n">text</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;Parent&quot;</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">stack</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="bp">UIStackView</span><span class="p">(</span><span class="n">arrangedSubviews</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="n">parentLabel</span><span class="p">,</span><span class="w"> </span><span class="n">label</span><span class="p">])</span> <span class="w"> </span><span class="n">stack</span><span class="p">.</span><span class="n">axis</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">vertical</span> <span class="w"> </span><span class="n">stack</span><span class="p">.</span><span class="n">alignment</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">center</span> <span class="w"> </span><span class="n">stack</span><span class="p">.</span><span class="n">spacing</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">4</span> <span class="w"> </span><span class="n">stack</span><span class="p">.</span><span class="n">layer</span><span class="p">.</span><span class="n">borderColor</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="bp">UIColor</span><span class="p">.</span><span class="n">red</span><span class="p">.</span><span class="n">cgColor</span> <span class="w"> </span><span class="n">stack</span><span class="p">.</span><span class="n">layer</span><span class="p">.</span><span class="n">borderWidth</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">1</span> <span class="w"> </span><span class="n">stack</span><span class="p">.</span><span class="n">layoutMargins</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">UIEdgeInsets</span><span class="p">(</span><span class="n">top</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="kr">left</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="n">bottom</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="kr">right</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">)</span> <span class="w"> </span><span class="n">stack</span><span class="p">.</span><span class="n">isLayoutMarginsRelativeArrangement</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">true</span> <span class="w"> </span><span class="n">label</span><span class="p">.</span><span class="n">text</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;Label: </span><span class="si">\(</span><span class="n">labelText</span><span class="si">)</span><span class="s">&quot;</span> <span class="w"> </span><span class="n">label</span><span class="p">.</span><span class="n">textAlignment</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">center</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">stack</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="kd">class</span><span class="w"> </span><span class="nc">ChildViewController</span><span class="p">:</span><span class="w"> </span><span class="bp">UIViewController</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">labelText</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;🐶&quot;</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kr">didSet</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">label</span><span class="p">.</span><span class="n">text</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;Label: </span><span class="si">\(</span><span class="n">labelText</span><span class="si">)</span><span class="s">&quot;</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">onUpdateText</span><span class="p">:</span><span class="w"> </span><span class="p">((</span><span class="nb">String</span><span class="p">)</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="nb">Void</span><span class="p">)?</span> <span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">label</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="bp">UILabel</span><span class="p">()</span> <span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">updateButton</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="bp">UIButton</span><span class="p">(</span><span class="n">type</span><span class="p">:</span><span class="w"> </span><span class="p">.</span><span class="n">system</span><span class="p">)</span> <span class="w"> </span><span class="kr">override</span><span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">viewDidLoad</span><span class="p">()</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kc">super</span><span class="p">.</span><span class="n">viewDidLoad</span><span class="p">()</span> <span class="w"> </span><span class="n">setupUI</span><span class="p">()</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">setupUI</span><span class="p">()</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">view</span><span class="p">.</span><span class="n">layer</span><span class="p">.</span><span class="n">borderColor</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="bp">UIColor</span><span class="p">.</span><span class="n">green</span><span class="p">.</span><span class="n">cgColor</span> <span class="w"> </span><span class="n">view</span><span class="p">.</span><span class="n">layer</span><span class="p">.</span><span class="n">borderWidth</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">1</span> <span class="w"> </span><span class="n">view</span><span class="p">.</span><span class="n">layoutMargins</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">UIEdgeInsets</span><span class="p">(</span><span class="n">top</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="kr">left</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="n">bottom</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="kr">right</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">)</span> <span class="w"> </span><span class="n">label</span><span class="p">.</span><span class="n">text</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;Label: </span><span class="si">\(</span><span class="n">labelText</span><span class="si">)</span><span class="s">&quot;</span> <span class="w"> </span><span class="n">label</span><span class="p">.</span><span class="n">textAlignment</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">center</span> <span class="w"> </span><span class="n">updateButton</span><span class="p">.</span><span class="n">setTitle</span><span class="p">(</span><span class="s">&quot;Update&quot;</span><span class="p">,</span><span class="w"> </span><span class="k">for</span><span class="p">:</span><span class="w"> </span><span class="p">.</span><span class="n">normal</span><span class="p">)</span> <span class="w"> </span><span class="n">updateButton</span><span class="p">.</span><span class="n">addTarget</span><span class="p">(</span><span class="kc">self</span><span class="p">,</span><span class="w"> </span><span class="n">action</span><span class="p">:</span><span class="w"> </span><span class="k">#selector</span><span class="p">(</span><span class="n">handleUpdateButtonTap</span><span class="p">),</span><span class="w"> </span><span class="k">for</span><span class="p">:</span><span class="w"> </span><span class="p">.</span><span class="n">touchUpInside</span><span class="p">)</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">childLabel</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="bp">UILabel</span><span class="p">()</span> <span class="w"> </span><span class="n">childLabel</span><span class="p">.</span><span class="n">text</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;Child&quot;</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">stack</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="bp">UIStackView</span><span class="p">(</span><span class="n">arrangedSubviews</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="n">childLabel</span><span class="p">,</span><span class="w"> </span><span class="n">label</span><span class="p">,</span><span class="w"> </span><span class="n">updateButton</span><span class="p">])</span> <span class="w"> </span><span class="n">stack</span><span class="p">.</span><span class="n">axis</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">vertical</span> <span class="w"> </span><span class="n">stack</span><span class="p">.</span><span class="n">alignment</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">.</span><span class="n">center</span> <span class="w"> </span><span class="n">stack</span><span class="p">.</span><span class="n">spacing</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">4</span> <span class="w"> </span><span class="n">stack</span><span class="p">.</span><span class="n">translatesAutoresizingMaskIntoConstraints</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">false</span> <span class="w"> </span><span class="n">stack</span><span class="p">.</span><span class="n">isLayoutMarginsRelativeArrangement</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">true</span> <span class="w"> </span><span class="n">view</span><span class="p">.</span><span class="n">addSubview</span><span class="p">(</span><span class="n">stack</span><span class="p">)</span> <span class="w"> </span><span class="bp">NSLayoutConstraint</span><span class="p">.</span><span class="n">activate</span><span class="p">([</span> <span class="w"> </span><span class="n">stack</span><span class="p">.</span><span class="n">centerXAnchor</span><span class="p">.</span><span class="n">constraint</span><span class="p">(</span><span class="n">equalTo</span><span class="p">:</span><span class="w"> </span><span class="n">view</span><span class="p">.</span><span class="n">centerXAnchor</span><span class="p">),</span> <span class="w"> </span><span class="n">stack</span><span class="p">.</span><span class="n">centerYAnchor</span><span class="p">.</span><span class="n">constraint</span><span class="p">(</span><span class="n">equalTo</span><span class="p">:</span><span class="w"> </span><span class="n">view</span><span class="p">.</span><span class="n">centerYAnchor</span><span class="p">)</span> <span class="w"> </span><span class="p">])</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="kr">@objc</span><span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">handleUpdateButtonTap</span><span class="p">()</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">labelText</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;🐈&quot;</span> <span class="w"> </span><span class="n">onUpdateText</span><span class="p">?(</span><span class="n">labelText</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">updateLabel</span><span class="p">(</span><span class="n">with</span><span class="w"> </span><span class="n">text</span><span class="p">:</span><span class="w"> </span><span class="nb">String</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">labelText</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">text</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="c1">// Only required for Xcode Previews</span> <span class="kd">import</span><span class="w"> </span><span class="nc">SwiftUI</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">ParentViewControllerWrapper</span><span class="p">:</span><span class="w"> </span><span class="n">UIViewControllerRepresentable</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="p">@</span><span class="n">Binding</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">text</span><span class="p">:</span><span class="w"> </span><span class="nb">String</span> <span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">makeUIViewController</span><span class="p">(</span><span class="n">context</span><span class="p">:</span><span class="w"> </span><span class="n">Context</span><span class="p">)</span><span class="w"> </span><span class="p">-&gt;</span><span class="w"> </span><span class="n">ParentViewController</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">parentVC</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">ParentViewController</span><span class="p">()</span> <span class="w"> </span><span class="n">parentVC</span><span class="p">.</span><span class="n">labelText</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">text</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">parentVC</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">updateUIViewController</span><span class="p">(</span><span class="kc">_</span><span class="w"> </span><span class="n">uiViewController</span><span class="p">:</span><span class="w"> </span><span class="n">ParentViewController</span><span class="p">,</span><span class="w"> </span><span class="n">context</span><span class="p">:</span><span class="w"> </span><span class="n">Context</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">uiViewController</span><span class="p">.</span><span class="n">labelText</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">text</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="p">#</span><span class="n">Preview</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="p">@</span><span class="n">Previewable</span><span class="w"> </span><span class="p">@</span><span class="n">State</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">text</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;🐶&quot;</span> <span class="w"> </span><span class="n">ParentViewControllerWrapper</span><span class="p">(</span><span class="n">text</span><span class="p">:</span><span class="w"> </span><span class="err">$</span><span class="n">text</span><span class="p">)</span> <span class="w"> </span><span class="p">.</span><span class="n">frame</span><span class="p">(</span><span class="n">width</span><span class="p">:</span><span class="w"> </span><span class="mi">200</span><span class="p">,</span><span class="w"> </span><span class="n">height</span><span class="p">:</span><span class="w"> </span><span class="mi">220</span><span class="p">)</span> <span class="p">}</span> </code></pre></div> <p>Seriously?! All of that to change an emoji from "🐶" to "🐈"!</p> <h4>"SwiftUIKit"</h4> <p>Many seasoned UIKit developers transitioning to SwiftUI treat the framework as “UIKit with different syntax.” The result? Bloated SwiftUI code that looks like this:</p> <div class="highlight"><pre><span></span><code><span class="kd">import</span><span class="w"> </span><span class="nc">SwiftUI</span> <span class="c1">// Unnecessary coordinator pattern</span> <span class="kd">class</span><span class="w"> </span><span class="nc">ParentCoordinator</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="c1">// Unneccessary nesting</span> <span class="w"> </span><span class="kr">weak</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">parentViewModel</span><span class="p">:</span><span class="w"> </span><span class="n">ParentViewModel</span><span class="p">?</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">childCoordinator</span><span class="p">:</span><span class="w"> </span><span class="n">ChildCoordinator</span><span class="p">?</span> <span class="w"> </span><span class="kd">init</span><span class="p">(</span><span class="n">parentViewModel</span><span class="p">:</span><span class="w"> </span><span class="n">ParentViewModel</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kc">self</span><span class="p">.</span><span class="n">parentViewModel</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">parentViewModel</span> <span class="w"> </span><span class="kc">self</span><span class="p">.</span><span class="n">childCoordinator</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">ChildCoordinator</span><span class="p">(</span><span class="n">childViewModel</span><span class="p">:</span><span class="w"> </span><span class="n">parentViewModel</span><span class="p">.</span><span class="n">childViewModel</span><span class="p">)</span> <span class="w"> </span><span class="kc">self</span><span class="p">.</span><span class="n">childCoordinator</span><span class="p">?.</span><span class="n">delegate</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">self</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="c1">// Unneccessary delegate pattern</span> <span class="kd">extension</span><span class="w"> </span><span class="nc">ParentCoordinator</span><span class="p">:</span><span class="w"> </span><span class="n">ChildCoordinatorDelegate</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">childCoordinatorDidUpdateLabelText</span><span class="p">(</span><span class="kc">_</span><span class="w"> </span><span class="n">newText</span><span class="p">:</span><span class="w"> </span><span class="nb">String</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">parentViewModel</span><span class="p">?.</span><span class="n">labelText</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">newText</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="c1">// Unneccessary protocol</span> <span class="kd">protocol</span><span class="w"> </span><span class="nc">ChildCoordinatorDelegate</span><span class="p">:</span><span class="w"> </span><span class="nb">AnyObject</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">childCoordinatorDidUpdateLabelText</span><span class="p">(</span><span class="kc">_</span><span class="w"> </span><span class="n">newText</span><span class="p">:</span><span class="w"> </span><span class="nb">String</span><span class="p">)</span> <span class="p">}</span> <span class="c1">// Unneccessary child coordinator</span> <span class="kd">class</span><span class="w"> </span><span class="nc">ChildCoordinator</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="c1">// Unneccessary nesting</span> <span class="w"> </span><span class="kr">weak</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">childViewModel</span><span class="p">:</span><span class="w"> </span><span class="n">ChildViewModel</span><span class="p">?</span> <span class="w"> </span><span class="kr">weak</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">delegate</span><span class="p">:</span><span class="w"> </span><span class="n">ChildCoordinatorDelegate</span><span class="p">?</span> <span class="w"> </span><span class="kd">init</span><span class="p">(</span><span class="n">childViewModel</span><span class="p">:</span><span class="w"> </span><span class="n">ChildViewModel</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kc">self</span><span class="p">.</span><span class="n">childViewModel</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">childViewModel</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="c1">// Unnecessary @MainActor</span> <span class="w"> </span><span class="p">@</span><span class="n">MainActor</span><span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">updateText</span><span class="p">()</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">childViewModel</span><span class="p">?.</span><span class="n">updateText</span><span class="p">()</span> <span class="w"> </span><span class="n">delegate</span><span class="p">?.</span><span class="n">childCoordinatorDidUpdateLabelText</span><span class="p">(</span><span class="n">childViewModel</span><span class="p">!.</span><span class="n">labelText</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="c1">// Unneccessary view model</span> <span class="p">@</span><span class="n">Observable</span> <span class="kd">class</span><span class="w"> </span><span class="nc">ParentViewModel</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">labelText</span><span class="p">:</span><span class="w"> </span><span class="nb">String</span> <span class="w"> </span><span class="c1">// Unneccessary nesting</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">childViewModel</span><span class="p">:</span><span class="w"> </span><span class="n">ChildViewModel</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">coordinator</span><span class="p">:</span><span class="w"> </span><span class="n">ParentCoordinator</span><span class="p">?</span> <span class="w"> </span><span class="kd">init</span><span class="p">(</span><span class="n">labelText</span><span class="p">:</span><span class="w"> </span><span class="nb">String</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;🐶&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kc">self</span><span class="p">.</span><span class="n">labelText</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">labelText</span> <span class="w"> </span><span class="kc">self</span><span class="p">.</span><span class="n">childViewModel</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">ChildViewModel</span><span class="p">(</span><span class="n">labelText</span><span class="p">:</span><span class="w"> </span><span class="n">labelText</span><span class="p">)</span> <span class="w"> </span><span class="kc">self</span><span class="p">.</span><span class="n">coordinator</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">ParentCoordinator</span><span class="p">(</span><span class="n">parentViewModel</span><span class="p">:</span><span class="w"> </span><span class="kc">self</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="c1">// Unneccessary view model</span> <span class="p">@</span><span class="n">Observable</span> <span class="kd">class</span><span class="w"> </span><span class="nc">ChildViewModel</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">labelText</span><span class="p">:</span><span class="w"> </span><span class="nb">String</span> <span class="w"> </span><span class="kd">init</span><span class="p">(</span><span class="n">labelText</span><span class="p">:</span><span class="w"> </span><span class="nb">String</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kc">self</span><span class="p">.</span><span class="n">labelText</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">labelText</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="c1">// Unneccessary @MainActor</span> <span class="w"> </span><span class="p">@</span><span class="n">MainActor</span><span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">updateText</span><span class="p">()</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">labelText</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;🐈&quot;</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="c1">// Overly complex view hierarchy</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">ParentView</span><span class="p">:</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="p">@</span><span class="n">Bindable</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">viewModel</span><span class="p">:</span><span class="w"> </span><span class="n">ParentViewModel</span> <span class="w"> </span><span class="kd">init</span><span class="p">()</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nv">viewModel</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">ParentViewModel</span><span class="p">()</span> <span class="w"> </span><span class="kc">self</span><span class="p">.</span><span class="n">viewModel</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">viewModel</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">body</span><span class="p">:</span><span class="w"> </span><span class="n">some</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">VStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">VStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Parent&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Label: </span><span class="si">\(</span><span class="n">viewModel</span><span class="p">.</span><span class="n">labelText</span><span class="si">)</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">.</span><span class="n">padding</span><span class="p">()</span> <span class="w"> </span><span class="p">.</span><span class="n">background</span><span class="p">(</span><span class="n">Rectangle</span><span class="p">().</span><span class="n">stroke</span><span class="p">(</span><span class="n">Color</span><span class="p">.</span><span class="n">red</span><span class="p">))</span> <span class="w"> </span><span class="n">ChildView</span><span class="p">(</span><span class="n">viewModel</span><span class="p">:</span><span class="w"> </span><span class="n">viewModel</span><span class="p">.</span><span class="n">childViewModel</span><span class="p">,</span><span class="w"> </span><span class="n">coordinator</span><span class="p">:</span><span class="w"> </span><span class="n">viewModel</span><span class="p">.</span><span class="n">coordinator</span><span class="p">!.</span><span class="n">childCoordinator</span><span class="p">!)</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">.</span><span class="n">padding</span><span class="p">()</span> <span class="w"> </span><span class="p">.</span><span class="n">background</span><span class="p">(</span><span class="n">Rectangle</span><span class="p">().</span><span class="n">stroke</span><span class="p">(</span><span class="n">Color</span><span class="p">.</span><span class="n">orange</span><span class="p">))</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="c1">// Child view with direct connection to the coordinator</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">ChildView</span><span class="p">:</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="p">@</span><span class="n">Bindable</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">viewModel</span><span class="p">:</span><span class="w"> </span><span class="n">ChildViewModel</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">coordinator</span><span class="p">:</span><span class="w"> </span><span class="n">ChildCoordinator</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">body</span><span class="p">:</span><span class="w"> </span><span class="n">some</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">VStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Child&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Label: </span><span class="si">\(</span><span class="n">viewModel</span><span class="p">.</span><span class="n">labelText</span><span class="si">)</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="p">(</span><span class="n">action</span><span class="p">:</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">coordinator</span><span class="p">.</span><span class="n">updateText</span><span class="p">()</span> <span class="w"> </span><span class="p">})</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Update&quot;</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">.</span><span class="n">padding</span><span class="p">()</span> <span class="w"> </span><span class="p">.</span><span class="n">background</span><span class="p">(</span><span class="n">Rectangle</span><span class="p">().</span><span class="n">stroke</span><span class="p">(</span><span class="n">Color</span><span class="p">.</span><span class="n">green</span><span class="p">))</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="p">#</span><span class="n">Preview</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">ParentView</span><span class="p">()</span> <span class="p">}</span> </code></pre></div> <p>Coordinators, delegates, nested view models... and enough boilerplate to sink a ship!</p> <h4>Vanilla SwiftUI</h4> <p>Here's the exact same problem solved with just a few lines of SwiftUI:</p> <div class="highlight"><pre><span></span><code><span class="kd">import</span><span class="w"> </span><span class="nc">SwiftUI</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">ParentView</span><span class="p">:</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="c1">// Single source of truth using @State</span> <span class="w"> </span><span class="p">@</span><span class="n">State</span><span class="w"> </span><span class="kd">private</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">text</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;🐶&quot;</span> <span class="w"> </span><span class="c1">// Simple, declarative view hierarchy</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">body</span><span class="p">:</span><span class="w"> </span><span class="n">some</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">VStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">VStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Parent&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Label: </span><span class="si">\(</span><span class="n">text</span><span class="si">)</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">.</span><span class="n">padding</span><span class="p">()</span> <span class="w"> </span><span class="p">.</span><span class="n">background</span><span class="p">(</span><span class="n">Rectangle</span><span class="p">().</span><span class="n">stroke</span><span class="p">(.</span><span class="n">red</span><span class="p">))</span> <span class="w"> </span><span class="n">ChildView</span><span class="p">(</span><span class="n">text</span><span class="p">:</span><span class="w"> </span><span class="err">$</span><span class="n">text</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">.</span><span class="n">padding</span><span class="p">()</span> <span class="w"> </span><span class="p">.</span><span class="n">background</span><span class="p">(</span><span class="n">Rectangle</span><span class="p">().</span><span class="n">stroke</span><span class="p">(.</span><span class="n">orange</span><span class="p">))</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">ChildView</span><span class="p">:</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="c1">// Direct connection to parent state using @Binding</span> <span class="w"> </span><span class="p">@</span><span class="n">Binding</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">text</span><span class="p">:</span><span class="w"> </span><span class="nb">String</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">body</span><span class="p">:</span><span class="w"> </span><span class="n">some</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">VStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Child&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Label: </span><span class="si">\(</span><span class="n">text</span><span class="si">)</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">text</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;🐈&quot;</span> <span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="n">label</span><span class="p">:</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Update&quot;</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">.</span><span class="n">padding</span><span class="p">()</span> <span class="w"> </span><span class="p">.</span><span class="n">background</span><span class="p">(</span><span class="n">Rectangle</span><span class="p">().</span><span class="n">stroke</span><span class="p">(.</span><span class="n">green</span><span class="p">))</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="p">#</span><span class="n">Preview</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">ParentView</span><span class="p">()</span> <span class="p">}</span> </code></pre></div> <p>No coordinators. No delegates. No view models. Just <code>@State</code> and <code>@Binding</code> doing what they were designed to do. SwiftUI is able to handle all of the complexity that had to be manually orchestrated in UIKit.</p> <h4>MVVM</h4> <p>Some might complain, but what about MVVM? Well, if you <em>must</em> (a topic for another time) use MVVM with SwiftUI here's what that might look like:</p> <div class="highlight"><pre><span></span><code><span class="kd">import</span><span class="w"> </span><span class="nc">SwiftUI</span> <span class="p">@</span><span class="n">Observable</span> <span class="kd">class</span><span class="w"> </span><span class="nc">ViewModel</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="c1">// Single source of truth at the model layer</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">text</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;🐶&quot;</span> <span class="w"> </span><span class="kd">func</span><span class="w"> </span><span class="nf">update</span><span class="p">()</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">text</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&quot;🐈&quot;</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">ParentView</span><span class="p">:</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="c1">// View owns the model instance</span> <span class="w"> </span><span class="p">@</span><span class="n">State</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">viewModel</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="n">ViewModel</span><span class="p">()</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">body</span><span class="p">:</span><span class="w"> </span><span class="n">some</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">VStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">VStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Parent&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Label: </span><span class="si">\(</span><span class="n">viewModel</span><span class="p">.</span><span class="n">text</span><span class="si">)</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">.</span><span class="n">padding</span><span class="p">()</span> <span class="w"> </span><span class="p">.</span><span class="n">background</span><span class="p">(</span><span class="n">Rectangle</span><span class="p">().</span><span class="n">stroke</span><span class="p">(.</span><span class="n">red</span><span class="p">))</span> <span class="w"> </span><span class="n">ChildView</span><span class="p">()</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">.</span><span class="n">padding</span><span class="p">()</span> <span class="w"> </span><span class="p">.</span><span class="n">background</span><span class="p">(</span><span class="n">Rectangle</span><span class="p">().</span><span class="n">stroke</span><span class="p">(.</span><span class="n">orange</span><span class="p">))</span> <span class="w"> </span><span class="p">.</span><span class="n">environment</span><span class="p">(</span><span class="n">viewModel</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="kd">struct</span><span class="w"> </span><span class="nc">ChildView</span><span class="p">:</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="c1">// Child accesses shared model through environment</span> <span class="w"> </span><span class="p">@</span><span class="n">Environment</span><span class="p">(</span><span class="n">ViewModel</span><span class="p">.</span><span class="kc">self</span><span class="p">)</span><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">viewModel</span><span class="p">:</span><span class="w"> </span><span class="n">ViewModel</span> <span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">body</span><span class="p">:</span><span class="w"> </span><span class="n">some</span><span class="w"> </span><span class="n">View</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">VStack</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Child&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Label: </span><span class="si">\(</span><span class="n">viewModel</span><span class="p">.</span><span class="n">text</span><span class="si">)</span><span class="s">&quot;</span><span class="p">)</span> <span class="w"> </span><span class="n">Button</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">viewModel</span><span class="p">.</span><span class="n">update</span><span class="p">()</span> <span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="n">label</span><span class="p">:</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">Text</span><span class="p">(</span><span class="s">&quot;Update&quot;</span><span class="p">)</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">.</span><span class="n">padding</span><span class="p">()</span> <span class="w"> </span><span class="p">.</span><span class="n">background</span><span class="p">(</span><span class="n">Rectangle</span><span class="p">().</span><span class="n">stroke</span><span class="p">(.</span><span class="n">green</span><span class="p">))</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="p">#</span><span class="n">Preview</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="n">ParentView</span><span class="p">()</span> <span class="p">}</span> </code></pre></div> <p>At least the thing given the ViewModel name this time is actually a view model!</p> <h4>The Bottom Line</h4> <p>SwiftUI isn’t just a new UI framework—it’s a completely different way of thinking. A brand new way of building. UIKit patterns worked well... in UIKit. Forcing them into SwiftUI only creates complexity.</p>Thu, 14 Nov 2024 00:00:00 -0000https://maxhumber.com/notuikit