<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Xcode14 on blog.iankulin.com</title><link>https://blog.iankulin.com/tags/xcode14/</link><description>Recent content in Xcode14 on blog.iankulin.com</description><generator>Hugo</generator><language>en-AU</language><lastBuildDate>Sat, 10 Dec 2022 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.iankulin.com/tags/xcode14/index.xml" rel="self" type="application/rss+xml"/><item><title>Sharing is caring</title><link>https://blog.iankulin.com/sharing-is-caring/</link><pubDate>Sat, 10 Dec 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/sharing-is-caring/</guid><description>&lt;p&gt;Continuing on with the demo project from yesterday, in which we used the ImageRenderer class to turn a view into an image, today we want to let the user share it somehow.&lt;/p&gt;
&lt;p&gt;Typically, apps have a button using the square.and.arrow.up SF Symbol to share something from the current screen. It&amp;rsquo;s probably not an accident that it&amp;rsquo;s literally the first symbol in the app.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-12-05-at-9.23.33-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Pressing it generally opens the &amp;ldquo;share sheet&amp;rdquo; which has options for opening whatever is being shared in another app, printing it, saving it to photos, or whatever.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s our ticket app from a couple of days ago (the TicketView is unchanged). We&amp;rsquo;re still using ImageRenderer() to make the image version of the view in OnAppear(), but this time there&amp;rsquo;s a &amp;ldquo;sharelink&amp;rdquo;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;ContentView&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;private&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; ticketView &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; TicketView&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;State &lt;span style="color:#81a1c1;font-weight:bold"&gt;private&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; image&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Image&lt;span style="color:#eceff4"&gt;?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; VStack &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Spacer&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ticketView
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Spacer&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; image &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; image &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ShareLink&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;item&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; image&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; preview&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; SharePreview&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;View&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;image&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; image&lt;span style="color:#eceff4"&gt;))&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Label&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Share&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; systemImage&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;square.and.arrow.up&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;padding&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;onAppear &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; renderer &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; ImageRenderer&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;content&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; ticketView&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; uiImage &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; renderer&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;uiImage &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Image&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;uiImage&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; uiImage&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If we click on the share link, it looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/simulator-screen-shot-iphone-14-pro-2022-12-05-at-21.10.20.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The little thumbnail and the word &amp;ldquo;View&amp;rdquo; at the top of the share sheet is from the preview parameter in our call.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ShareLink(item: image, preview: SharePreview(&amp;#34;View&amp;#34;,image: image)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Label(&amp;#34;Share&amp;#34;, systemImage: &amp;#34;square.and.arrow.up&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Since this is an image, perhaps you were expecting a &amp;ldquo;Save image&amp;rdquo; option? That&amp;rsquo;s the most likely thing we&amp;rsquo;d want to do with an image, but it&amp;rsquo;s not there. The reason is that saving an image to the users camera roll requires a specific permission. In the app settings, choose &amp;ldquo;Info&amp;rdquo; then right click on the entries and &amp;ldquo;Add Row&amp;rdquo;. Search for / add the key &amp;ldquo;Privacy - Photo Library Additions Usage Description&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-12-07-at-8.33.52-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The text you add in the description is what the app will show to the user when the dialogue pops up asking the user if it&amp;rsquo;s okay to give this app the permission to save photos.&lt;/p&gt;
&lt;p&gt;If we go back and and try again to share the ticket, we&amp;rsquo;ve now got the options to &amp;ldquo;Save Image&amp;rdquo;. If we choose that for the first time, we&amp;rsquo;ll be asked to grant permission to this app.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-12-07-at-8.41.40-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;If the user chooses OK, then it will be saved to photos:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-12-07-at-8.42.04-pm.jpg" alt=""&gt;&lt;/p&gt;</description></item><item><title>Console spam - No wall clock alignment</title><link>https://blog.iankulin.com/console-spam-no-wall-clock-alignment/</link><pubDate>Wed, 16 Nov 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/console-spam-no-wall-clock-alignment/</guid><description>&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-11-12-at-8.02.28-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;When I was working on the Day 60 app, I noticed I kept getting a message in the console &amp;ldquo;&lt;code&gt;No wall clock alignment provided at SwiftUI/ResolvableStringAttribute.swift:86&lt;/code&gt;&amp;rdquo; every time I went into the detail view. Via elimination by commenting bits out, I&amp;rsquo;ve narrowed it down to a date formatting call. Here is the code to reproduce it in Xcode Version 14.0.1, Swift 5.7.0.127.4&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;struct ContentView&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Date: \(Date(), style: .date)&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s to do with the style - if I change it to .time or .relative the message does not appear.&lt;/p&gt;
&lt;p&gt;I assume an Apple programmer has checked for something on line 86 of ResolvableStringAttribute.swift and unexpectedly found it missing. If I search for that file on my system, it doesn&amp;rsquo;t appear to be here, so I guess the SwiftUI source is not part of the XCode installation.&lt;/p&gt;
&lt;p&gt;Googling the error only produces a single result - a &lt;a href="https://www.reddit.com/r/iOSProgramming/comments/vm90cf/no_wall_clock_alignment_provided_at/"&gt;reddit&lt;/a&gt; user asking for help on the message and being told not to worry about the noise coming from Apple frameworks if everything&amp;rsquo;s working okay. The question is from five months ago and has a different line number - so presumably from an earlier version of SwiftUI.&lt;/p&gt;
&lt;p&gt;Since it can be reproduced with such a tiny code snippet, and it occurs in every simulator I tried it on, it may be easy to reproduce, and therefore fix. So without really knowing the exact guidelines, but with encouragement from &lt;a href="https://developer.apple.com/bug-reporting/"&gt;Apple&lt;/a&gt;, I filed my first Radar.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-11-12-at-8.18.29-pm.jpg" alt=""&gt;&lt;/p&gt;</description></item><item><title>FriendFace</title><link>https://blog.iankulin.com/friendface/</link><pubDate>Mon, 14 Nov 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/friendface/</guid><description>&lt;p&gt;The &lt;a href="https://www.hackingwithswift.com/guide/ios-swiftui/5/3/challenge"&gt;Day 60 Milestone&lt;/a&gt; is a demo app that vacuums up some JSON and displays it in a list in a NavigationView that links to a details page. Nothing super strenuous, the steps were something like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Download the JSON and have a look at the structure. Firefox has a simple JSON viewer built in, so it was straightforward to see this is an array of users, which along with some (mostly string) properties contains an array of tag strings, and another array of friends.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-11-12-at-3.23.28-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Build the structs for these.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;import&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;Foundation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;User&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Codable &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; id&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;String&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; isActive&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;Bool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; name&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;String&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; age&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;Int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; company&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;String&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; email&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;String&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; address&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;String&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; about&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;String&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; registered&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Date
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; tags&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;String&lt;/span&gt;&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; friends&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;Friend&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;Friend&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Codable &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; id&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;String&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; name&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;String&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The date you can see in the JSON is in the ISO-8601 format - and @twostraws gives the hint about using the dateDecodingStrategy for it.&lt;/p&gt;
&lt;ol start="3"&gt;
&lt;li&gt;Fetch the data. I made this a .task attached to the list, I didn&amp;rsquo;t notice it loading multiple times, but Paul did caution about this, so the code checks if the array is empty before calling fetching the JSON.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.task {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if users.isEmpty {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; await fetchUsers()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="4"&gt;
&lt;li&gt;Process the data into our structs. Since they are codable, this is a bit of a joy. I&amp;rsquo;d made an error and the JSON wasn&amp;rsquo;t loading, but I could not see what it was. I fixed this by nesting my do/catch blocks. It seems a bit unwieldy - I feel like I should be able to do several risky steps and have the catch catch all the errors, but it didn&amp;rsquo;t seem to be. This code works fine, but I feel it could be improved.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;func&lt;/span&gt; &lt;span style="color:#88c0d0"&gt;fetchUsers&lt;/span&gt;&lt;span style="color:#eceff4"&gt;()&lt;/span&gt; async &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;guard&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; url &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; URL&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;string&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;https://www.hackingwithswift.com/samples/friendface.json&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;else&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Invalid URL&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;do&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; decoder &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; JSONDecoder&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; decoder&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;dateDecodingStrategy &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;iso8601
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;data&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;_&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;try&lt;/span&gt; await URLSession&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;shared&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;data&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;from&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; url&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;do&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; decodedUsers &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;try&lt;/span&gt; decoder&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;decode&lt;span style="color:#eceff4"&gt;([&lt;/span&gt;User&lt;span style="color:#eceff4"&gt;].&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;self&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; from&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; data&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; users &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; decodedUsers
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;catch&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;error&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;catch&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;error&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note the square brackets for [User] - we&amp;rsquo;re decoding an array of users.&lt;/p&gt;
&lt;ol start="5"&gt;
&lt;li&gt;Display it in the list, with an NavigationLink to the UserDetails view.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;ContentView&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;State &lt;span style="color:#81a1c1;font-weight:bold"&gt;private&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; users &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;User&lt;span style="color:#eceff4"&gt;]()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; NavigationView &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; List&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;users&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; id&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#bf616a"&gt;\&lt;/span&gt;&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;id&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; user &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; NavigationLink&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;destination&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; UserDetail&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;user&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; user&lt;span style="color:#eceff4"&gt;))&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; VStack&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;alignment&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;leading&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;user&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;name&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;font&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;headline&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;user&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;isActive &lt;span style="color:#eceff4"&gt;?&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Active&amp;#34;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Not active&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;task &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; users&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;isEmpty&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; await fetchUsers&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;navigationBarTitle&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;FriendFace&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="5"&gt;
&lt;li&gt;Show the user details. This is where I started going off script. The user is passed into the detail view. I used a Form because the layout looks a bit nice, and the Friends is a little list.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The script departure was wanting to have a profile pic. As described yesterday, I used an AsyncImage for this. I would have liked to have cached the image so it doesn&amp;rsquo;t re-fetch when you go out of a user and back in (this would have solved the problem of the random image I described yesterday as well) and fiddled around with trying to save a .snapshot of the AsyncImage view - but then decided I should be moving on instead of cracking that particular procrastination nut, especially because of this parting advice from Paul.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Tip:&lt;/strong&gt; As always, the best way to solve this challenge is to keep it simple – write as little code as you can to solve the challenge, and for you to feel comfortable that it works well.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;UserDetail&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; user&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; User
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Form &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Section &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HStack &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;user&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;name&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;font&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;headline&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;frame&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;maxWidth&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;infinity&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; alignment&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;center&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; AsyncImage&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; url&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; URL&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;string&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;https://randomuser.me/api/portraits/men/&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;nameHash&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;.jpg&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; scale&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; image &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; image
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;resizable&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;scaledToFit&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; placeholder&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ProgressView&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Section &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Age: &lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;user&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;age&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Company: &lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;user&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;company&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;email: &lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;user&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;email&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Address: &lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;user&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;address&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Registered: &lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;user&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;registered&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; style&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;date&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Section&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;header&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Friends&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;))&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; List&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;user&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;friends&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; id&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#bf616a"&gt;\&lt;/span&gt;&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;id&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; friend &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;friend&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;name&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; nameHash&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;Int&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; user&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;name&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;utf8&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;reduce&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;0&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; $0 &lt;span style="color:#81a1c1"&gt;+&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;Int&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;$1&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;%&lt;/span&gt; &lt;span style="color:#b48ead"&gt;100&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>Project 12 Challenges</title><link>https://blog.iankulin.com/project-12-challenges/</link><pubDate>Thu, 10 Nov 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/project-12-challenges/</guid><description>&lt;p&gt;Project 12 was a series of code tutorials around developing CoreData concepts rather than a real app, but the &lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/core-data-wrap-up"&gt;challenges&lt;/a&gt; are based on a very small app that uses a subview to allow dynamic (ie changeable at runtime) filtering of a list of data. The reason this would be tricky is that the @FetchRequest is a property of a view - and therefore mutable. The trick is to have a subview to build that part of the view, and to pass parameters into it which build the fetchrequest using an underscore.&lt;/p&gt;
&lt;p&gt;The data is three recording artists - Taylor Swift, Ed Sheeran and Adel. The ContentView is a list of their names, with some buttons to filter it. Here&amp;rsquo;s the content view:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;ContentView&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;Environment&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#bf616a"&gt;\&lt;/span&gt;&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;managedObjectContext&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; moc
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;State &lt;span style="color:#81a1c1;font-weight:bold"&gt;private&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; lastNameFilter &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;A&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; VStack &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;// list of matching singers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FilteredList&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;filter&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; lastNameFilter&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Add Examples&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; taylor &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Singer&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;context&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; moc&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; taylor&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;firstName &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Taylor&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; taylor&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;lastName &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Swift&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; ed &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Singer&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;context&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; moc&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ed&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;firstName &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Ed&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ed&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;lastName &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Sheeran&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; adele &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Singer&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;context&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; moc&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; adele&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;firstName &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Adele&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; adele&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;lastName &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Adkins&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;try&lt;/span&gt;&lt;span style="color:#eceff4"&gt;?&lt;/span&gt; moc&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;save&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Show A&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lastNameFilter &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;A&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Show S&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lastNameFilter &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;S&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the subview that builds the FetchRequest and the filtered list:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;FilteredList&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;FetchRequest &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; fetchRequest&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; FetchedResults&lt;span style="color:#eceff4"&gt;&amp;lt;&lt;/span&gt;Singer&lt;span style="color:#eceff4"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; List&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;fetchRequest&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; id&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#bf616a"&gt;\&lt;/span&gt;&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;self&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; singer &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;singer&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;wrappedFirstName&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt; &lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;singer&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;wrappedLastName&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;init&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;filter&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;String&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _fetchRequest &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; FetchRequest&lt;span style="color:#eceff4"&gt;&amp;lt;&lt;/span&gt;Singer&lt;span style="color:#eceff4"&gt;&amp;gt;(&lt;/span&gt;sortDescriptors&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[],&lt;/span&gt; predicate&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; NSPredicate&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;format&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;lastName BEGINSWITH %@&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;filter&lt;/span&gt;&lt;span style="color:#eceff4"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="task-1"&gt;Task 1&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Modify the FilteredList View we made to make it accept a string parameter that controls which predicate is applied. You can use Swift’s string interpolation to place this in the predicate.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The changes here are to the init() in the FilteredList. Just need another argument to replace the format parameter in the FetchRequest. I may have misunderstood something, because Paul hinted to use string interpolation, but I can&amp;rsquo;t see that it&amp;rsquo;s needed.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;init(format: String, filter: String) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _fetchRequest = FetchRequest&amp;lt;Singer&amp;gt;(sortDescriptors: [], predicate: NSPredicate(format: format, filter))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then when we call it, just pass in the format string.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;// list of matching singers
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;FilteredList(format: &amp;#34;lastName BEGINSWITH %@&amp;#34;, filter: lastNameFilter)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="task-2"&gt;Task 2&lt;/h4&gt;
&lt;p&gt;I&amp;rsquo;m a fan of this task, because of my wariness of Hard Coded Strings.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Modify the predicate string parameter to be an enum such as &lt;code&gt;.beginsWith&lt;/code&gt;, then make that enum get resolved to a string inside the initializer.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The enum, with attached raw values:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;enum&lt;/span&gt; PredicateType&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#bf616a"&gt;String&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;case&lt;/span&gt; beginsWith &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;BEGINSWITH&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;case&lt;/span&gt; beginsWithIgnoreCase &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;BEGINSWITH[c]&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;case&lt;/span&gt; contains &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;CONTAINS&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;case&lt;/span&gt; equals &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;==&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;case&lt;/span&gt; lessThan &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;&amp;lt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;case&lt;/span&gt; greaterThan &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The ContentView has a @State variable for it. The user clicks on buttons to change it, and it&amp;rsquo;s passed to the subview instead of the previous string:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;ContentView&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;Environment&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#bf616a"&gt;\&lt;/span&gt;&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;managedObjectContext&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; moc
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;State &lt;span style="color:#81a1c1;font-weight:bold"&gt;private&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; lastNameFilter &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;A&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;State &lt;span style="color:#81a1c1;font-weight:bold"&gt;private&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; predicateType &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; PredicateType&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;beginsWith
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; VStack &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;// list of matching singers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FilteredList&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;format&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; predicateType&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;filter&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; lastNameFilter&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Add Examples&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; taylor &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Singer&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;context&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; moc&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; taylor&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;firstName &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Taylor&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; taylor&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;lastName &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Swift&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; ed &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Singer&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;context&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; moc&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ed&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;firstName &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Ed&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ed&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;lastName &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Sheeran&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; adele &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Singer&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;context&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; moc&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; adele&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;firstName &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Adele&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; adele&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;lastName &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Adkins&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;try&lt;/span&gt;&lt;span style="color:#eceff4"&gt;?&lt;/span&gt; moc&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;save&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Begins with A&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lastNameFilter &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;A&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; predicateType &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;beginsWith
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Begins with S&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lastNameFilter &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;S&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; predicateType &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;beginsWith
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Contains n&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lastNameFilter &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;n&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; predicateType &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;contains&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then in the subview, we grab the rawValue and interpolate that into the string. I&amp;rsquo;m just now realising that for Task 1, Paul probably didn&amp;rsquo;t want the whole string passed in, just the &amp;ldquo;BEGINSWITH&amp;rdquo; operator or whatever.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;FilteredList&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;FetchRequest &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; fetchRequest&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; FetchedResults&lt;span style="color:#eceff4"&gt;&amp;lt;&lt;/span&gt;Singer&lt;span style="color:#eceff4"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; List&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;fetchRequest&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; id&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#bf616a"&gt;\&lt;/span&gt;&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;self&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; singer &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;singer&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;wrappedFirstName&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt; &lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;singer&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;wrappedLastName&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;init&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;format&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; PredicateType&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;filter&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;String&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _fetchRequest &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; FetchRequest&lt;span style="color:#eceff4"&gt;&amp;lt;&lt;/span&gt;Singer&lt;span style="color:#eceff4"&gt;&amp;gt;(&lt;/span&gt;sortDescriptors&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[],&lt;/span&gt; predicate&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; NSPredicate&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;format&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;lastName &lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;format&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;rawValue&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt; %@&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;filter&lt;/span&gt;&lt;span style="color:#eceff4"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="task-3"&gt;Task 3&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Make &lt;code&gt;FilteredList&lt;/code&gt; accept an array of &lt;code&gt;SortDescriptor&lt;/code&gt; objects to get used in its fetch request.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yet another change to the init in our subview. It&amp;rsquo;s getting kinda messy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;init(format: PredicateType, filter: String, sortDescriptors: [SortDescriptor&amp;lt;Singer&amp;gt;]) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _fetchRequest = FetchRequest&amp;lt;Singer&amp;gt;(sortDescriptors: sortDescriptors, predicate: NSPredicate(format: &amp;#34;lastName \(format.rawValue) %@&amp;#34;, filter))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then in the subview call:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;// list of matching singers
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;FilteredList(format: predicateType, filter: lastNameFilter, sortDescriptors: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; SortDescriptor&amp;lt;Singer&amp;gt;(\.firstName)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>Bookworm Feedback</title><link>https://blog.iankulin.com/bookworm-feedback/</link><pubDate>Mon, 07 Nov 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/bookworm-feedback/</guid><description>&lt;p&gt;I did so well on this one that it&amp;rsquo;s not going to make a very interesting post. My first two challenge solutions were pretty much character for character the same - so not much to report.&lt;/p&gt;
&lt;p&gt;On the third challenge, there was a minor difference in the display process. I had done this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;let date = book.date ?? Date()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Text(date.formatted(.dateTime.day().month().year()))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .foregroundColor(.secondary)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .opacity(date == book.date ? 1 : 0)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But @twostraws went:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;if let date = book.date {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(date.formatted(date:.abbreviated, time:.omitted))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I agree the &lt;em&gt;if let&lt;/em&gt; is neater. I was heading a warning from earlier in the series about avoiding if statements when building views as they can cause the view to have to be destroyed and recreated if it contains different elements (as opposed to a property change - which I don&amp;rsquo;t fully understand since I also believed those modifier properties where causing the views to be recreated inside other views?), but I don&amp;rsquo;t know if that&amp;rsquo;s an issue at all for list elements - I&amp;rsquo;m guessing not if Paul is doing it this way. I have noticed (when playing with print statements in init methods) that SwiftUI is very good at only recreating the views that need recreated.&lt;/p&gt;
&lt;p&gt;I would stand by my use of the opacity to disappear the text but save the space though - this is a good trick if you want to have a piece of a view not there, but still reserve its space.&lt;/p&gt;
&lt;p&gt;Paul has also done better with his date format. Mine does not take into account the date formats for different locales, where as iOS will manage that with Paul&amp;rsquo;s use of .abbreviated.&lt;/p&gt;</description></item><item><title>Bookworm Challenges</title><link>https://blog.iankulin.com/bookworm-challenges/</link><pubDate>Sun, 06 Nov 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/bookworm-challenges/</guid><description>&lt;p&gt;Another set of challenges for a &lt;a href="https://www.hackingwithswift.com/100/swiftui"&gt;#100DaysofSwiftUI&lt;/a&gt; tutorial app. Project 11 was a book tracking app - the big new thing was using CoreData. Here&amp;rsquo;s the &lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/bookworm-wrap-up"&gt;challenges for it&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Right now it’s possible to select no title, author, or genre for books, which causes a problem for the detail view. Please fix this, either by forcing defaults, validating the form, or showing a default picture for unknown genres – you can choose.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The genre is already forced since it uses a picker, but I added a default (plain grey) image to deal with the situation that there&amp;rsquo;s no data for it in a record. It doesn&amp;rsquo;t make sense to provide defaults for the title or author, but some validation to ensure those fields are not empty is worthwhile.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Section {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button(&amp;#34;Save&amp;#34;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; let newBook = Book(context: moc)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; newBook.id = UUID()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; newBook.title = title
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; newBook.author = author
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; newBook.rating = Int16(rating)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; newBook.genre = genre
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; newBook.review = review
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; newBook.date = Date()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; try? moc.save()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dismiss()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.disabled(title.isEmpty || author.isEmpty || genre.isEmpty)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://github.com/IanKulin/Bookworm/commit/989942b228f96540f1f46e04e91e4816f9072a38"&gt;&lt;img src="https://blog.iankulin.com/images/github-mark-120px-plus.png" width="34" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Modify &lt;code&gt;ContentView&lt;/code&gt; so that books rated as 1 star are highlighted somehow, such as having their name shown in red.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Straightforward, with use of the ternary operator.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-11-04-at-6.17.12-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/IanKulin/Bookworm/commit/cc81d29a799e7bb97e9813c4ad17e66a64361e6a"&gt;&lt;img src="https://blog.iankulin.com/images/github-mark-120px-plus-1.png" width="35" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Add a new “date” attribute to the Book entity, assigning &lt;code&gt;Date.now&lt;/code&gt; to it so it gets the current date and time, then format that nicely somewhere in &lt;code&gt;DetailView&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Add the date field to the datamodel&lt;/li&gt;
&lt;li&gt;Set it to current date in the Save of the AddView&lt;/li&gt;
&lt;li&gt;Showing it in the Detail View was a little more interesting - adding a test to ensure it was in the database - since we&amp;rsquo;ve now got two versions of these records.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Text(book.author ?? &amp;#34;Unknown author&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .font(.title)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .foregroundColor(.secondary)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;let date = book.date ?? Date()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Text(date.formatted(.dateTime.day().month().year()))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .foregroundColor(.secondary)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .opacity(date == book.date ? 1 : 0)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Text(book.review ?? &amp;#34;No review&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .padding()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://github.com/IanKulin/Bookworm/commit/9ec21d4d827b1ae1799c34d65b9d2366e5c4ce36"&gt;&lt;img src="https://blog.iankulin.com/images/github-mark-120px-plus-2.png" width="36" alt=""&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>CoreData and the Preview</title><link>https://blog.iankulin.com/coredata-and-the-preview/</link><pubDate>Fri, 04 Nov 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/coredata-and-the-preview/</guid><description>&lt;p&gt;I&amp;rsquo;ve noticed Paul is inclined to ignore the preview and run his code in the simulator to check its operation. That&amp;rsquo;s valid, but it seems quicker, and reassuring, to see it in the preview as I type.&lt;/p&gt;
&lt;p&gt;This led to a small problem with &lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/how-to-combine-core-data-and-swiftui"&gt;Day 53&lt;/a&gt; that uses CoreData. When I added a student in the preview, it looked like this, and was immediately followed with a crash report.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-30-at-11.32.07-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;It ran fine in the simulator, and in the text for Day 53 there was a comment about adding the managed object context to the preview, but without a hint about how.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-30-at-11.34.07-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I tried a few things - it felt like I should be able to get it from the @Environment somehow, but ended up using &lt;a href="https://www.hackingwithswift.com/forums/100-days-of-swiftui/day-53-question-how-to-set-up-a-managed-object-context-in-xcode-s-swiftui-previews/16686"&gt;this&lt;/a&gt; solution from fellow HWS student &lt;a href="https://www.hackingwithswift.com/users/Fly0strich"&gt;@Fly0strich&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-30-at-11.47.51-am.jpg" alt=""&gt;&lt;/p&gt;</description></item><item><title>Cupcake Corner Feedback</title><link>https://blog.iankulin.com/cupcake-corner-feedback/</link><pubDate>Thu, 03 Nov 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/cupcake-corner-feedback/</guid><description>&lt;p&gt;As usual, here&amp;rsquo;s my thoughts comparing my attempts at the challenges to Paul&amp;rsquo;s. Usually he&amp;rsquo;s better!&lt;/p&gt;
&lt;h4 id="1-whitespace"&gt;1) Whitespace&lt;/h4&gt;
&lt;p&gt;The task was to validate the order address properties, not just by checking they are not empty, but also that they don&amp;rsquo;t just contain spaces. I went the bruteforce route since there was no .isEmptyIncludingWhitespace method.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; hasValidAddress&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Bool &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; let trimmedName &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; name&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;trimmingCharacters&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;whitespacesAndNewlines&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; let trimmedStreetAddress &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; streetAddress&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;trimmingCharacters&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;whitespacesAndNewlines&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; let trimmedCity &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; city&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;trimmingCharacters&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;whitespacesAndNewlines&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; let trimmedZip &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; zip&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;trimmingCharacters&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;whitespacesAndNewlines&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; trimmedName&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;isEmpty &lt;span style="color:#81a1c1"&gt;||&lt;/span&gt; trimmedStreetAddress&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;isEmpty &lt;span style="color:#81a1c1"&gt;||&lt;/span&gt; trimmedCity&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;isEmpty &lt;span style="color:#81a1c1"&gt;||&lt;/span&gt; trimmedZip&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;isEmpty &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As soon as Paul mentioned extending String, I facepalmed - of course, just create the method I want on string. Paul&amp;rsquo;s is a one line extension - neater, and Swiftyier.&lt;/p&gt;
&lt;h4 id="2-alert-for-post-fail"&gt;2) Alert for POST fail&lt;/h4&gt;
&lt;p&gt;Paul&amp;rsquo;s approach exactly the same as mine, with the addition of showing the localizedDescription of &lt;em&gt;error&lt;/em&gt; which is something that must exist in catch blocks.&lt;/p&gt;
&lt;h4 id="3-struct-wrapper"&gt;3) struct Wrapper&lt;/h4&gt;
&lt;p&gt;We had the same approach. I liked Paul&amp;rsquo;s naming better. He named the wrapper class &lt;em&gt;SharedOrder&lt;/em&gt;, then the instance &lt;em&gt;order&lt;/em&gt;. Then the instance of the struct &lt;em&gt;data&lt;/em&gt;. That way the name hierarchys in the code were something like &lt;em&gt;order.data.street&lt;/em&gt; which was better than mine, although they still bug me. Another difference I noticed was that the static enum for the cupcake types he put in the wrapper class whereas I had it in the order struct.&lt;/p&gt;
&lt;p&gt;Paul left the CodingKey enum in - no problem with that, but I can&amp;rsquo;t see that it&amp;rsquo;s needed.&lt;/p&gt;
&lt;h4 id="bombshell"&gt;Bombshell&lt;/h4&gt;
&lt;p&gt;You know how I was complaining that the class.struct.propertyname things were the main downside of the class wrapping a struct approach? Next Paul pulls a rabbit out of his hat with &lt;em&gt;@dynamicMemberLookup&lt;/em&gt; combined with &lt;em&gt;keyPaths&lt;/em&gt;. I&amp;rsquo;m not going to explain how these work, but the effect is that we can eliminate the struct name from our names so &lt;em&gt;order.data.street&lt;/em&gt; is just &lt;em&gt;order.street&lt;/em&gt; but it is still referencing our struct property wrapped in the class.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-29-at-9.16.01-pm.jpg" alt=""&gt;&lt;/p&gt;</description></item><item><title>Cupcake Corner challenges</title><link>https://blog.iankulin.com/cupcake-corner-challenges/</link><pubDate>Wed, 02 Nov 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/cupcake-corner-challenges/</guid><description>&lt;p&gt;&lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/cupcake-corner-wrap-up"&gt;Day 52&lt;/a&gt; of &lt;a href="https://www.hackingwithswift.com/100/swiftui"&gt;#100Days&lt;/a&gt; was the challenges to the Cupcake Corner app - an app that allows you to build a one-row order, encode it as JSON and submit it to an API with a URLSession. To allow the order to be passed around, it&amp;rsquo;s an @ObservedObject which meant that a few extra hoops needed to be jumped through to make it Codable.&lt;/p&gt;
&lt;h4 id="1-whitespace-validation"&gt;1) Whitespace validation&lt;/h4&gt;
&lt;p&gt;The tutorial app validates the order address by checking that each field is not empty, but it can be fooled by just entering some spaces. The first challenge was to fix that.&lt;/p&gt;
&lt;p&gt;The tutorial version of the app accomplished the checking with a computed property in the Order struct - which is a good place for it. Here it is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; hasValidAddress&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Bool &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; name&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;isEmpty &lt;span style="color:#81a1c1"&gt;||&lt;/span&gt; streetAddress&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;isEmpty &lt;span style="color:#81a1c1"&gt;||&lt;/span&gt; city&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;isEmpty &lt;span style="color:#81a1c1"&gt;||&lt;/span&gt; zip&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;isEmpty &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s no .isEmptyIfYouIgnoreWhiteSpace method, so I did this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; hasValidAddress&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Bool &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; let trimmedName &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; name&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;trimmingCharacters&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;whitespacesAndNewlines&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; let trimmedStreetAddress &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; streetAddress&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;trimmingCharacters&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;whitespacesAndNewlines&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; let trimmedCity &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; city&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;trimmingCharacters&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;whitespacesAndNewlines&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; let trimmedZip &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; zip&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;trimmingCharacters&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;whitespacesAndNewlines&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; trimmedName&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;isEmpty &lt;span style="color:#81a1c1"&gt;||&lt;/span&gt; trimmedStreetAddress&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;isEmpty &lt;span style="color:#81a1c1"&gt;||&lt;/span&gt; trimmedCity&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;isEmpty &lt;span style="color:#81a1c1"&gt;||&lt;/span&gt; trimmedZip&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;isEmpty &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://github.com/IanKulin/CupcakeCorner/commit/2bd1247d268c41b8cb83c07af55ca4bb6f291e81#diff-5d943085c2460b6ea685f488eec227df2a6fec0aa2155bc7ffa85d457604c91e"&gt;source&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="2-show-an-alert-if-the-post-fails"&gt;2) Show an alert if the POST fails&lt;/h4&gt;
&lt;p&gt;In the tute version, this is just a print statement. This challenge just involves adding a second alert to the checkout view. &lt;a href="https://github.com/IanKulin/CupcakeCorner/commit/cfb2347c3e48fd68ac47b4f2153cc1faa01149ee"&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="3-change-order-to-struct"&gt;3) Change order to struct&lt;/h4&gt;
&lt;p&gt;This is a bit more complicated. A reason for preferring a struct is that all of the extra work we did to make the class Codable is eliminated. We need the thing being passed around to be an object because we want a reference type that can be mutated inside the view hierarchy, and because we want it to be an @Observed Object. The change proposed here is sort of the best of both worlds - have the object, but it&amp;rsquo;s sole property is the struct.&lt;/p&gt;
&lt;p&gt;I created a wrapper class, with the struct as an @Published var.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;class&lt;/span&gt; Wrapper&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; ObservableObject &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#bf616a"&gt;@&lt;/span&gt;Published &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; order &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; Order&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; deinit &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then I changed the Order class to a struct, removed @Published from all the properties and deleted the encode/decode code. Then in all the views, I had to go through and fix the names. I did not love the less readable names I was creating; for example &lt;code&gt;wrapped.order.streetAddress&lt;/code&gt; instead of just &lt;code&gt;order.streetAddress&lt;/code&gt;. But that all worked.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/IanKulin/CupcakeCorner/commit/c8559232b681f527c72ee9b2d89bfba997894d84#diff-5d943085c2460b6ea685f488eec227df2a6fec0aa2155bc7ffa85d457604c91e"&gt;source&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Day 50 - @State vs @Observed again</title><link>https://blog.iankulin.com/day-50-state-vs-obseved-again/</link><pubDate>Fri, 28 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/day-50-state-vs-obseved-again/</guid><description>&lt;p&gt;Way back when, I was unclear about @StateObject and @ObservedObject (&lt;a href="https://blog.iankulin.com/simple-mvvm/"&gt;here&lt;/a&gt;, and &lt;a href="https://blog.iankulin.com/observedobject-v-stateobject/"&gt;here&lt;/a&gt;). I still am.&lt;/p&gt;
&lt;p&gt;But in &lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/taking-basic-order-details"&gt;today&amp;rsquo;s tutorial&lt;/a&gt; video, Paul clearly says that the @StateObject is for the single place in your app where the object is created, then everywhere else, use @ObservedObject. Trouble is, I just know from the Simple MVVM app I made if you wrap the single instance of the data model with the @ObservedObject, it still works.&lt;/p&gt;
&lt;p&gt;Half way through my #100Days and this mystery is still not solved for me. I imagine now it never will be until I can figure out how the property wrappers differ. When you hold down command, and click over a keyword, you get a list of options. One of these options is &amp;ldquo;Jump to definition&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-26-at-6.28.33-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;So I should be able to do that with @StateObject and @ObservedObject then just diff the code and see right?&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-26-at-6.26.19-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Obviously, I&amp;rsquo;m not comprehending all of this, but it looks like the main difference is this extra Wrapper struct in the ObservedObject:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@dynamicMemberLookup @frozen public struct Wrapper {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; public subscript&amp;lt;Subject&amp;gt;(dynamicMember keyPath: ReferenceWritableKeyPath&amp;lt;ObjectType, Subject&amp;gt;) -&amp;gt; Binding&amp;lt;Subject&amp;gt; { get }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Yeah. I&amp;rsquo;m not going to be able to unpack that bad boy. I&amp;rsquo;ll just file it in &amp;ldquo;probably something to do with reference counting, and come back to it when I&amp;rsquo;m smarter.&lt;/p&gt;</description></item><item><title>Day 47 - Habits App</title><link>https://blog.iankulin.com/day-47-habits-app/</link><pubDate>Thu, 27 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/day-47-habits-app/</guid><description>&lt;p&gt;I&amp;rsquo;ve been mucking around with the Habits app too long - it&amp;rsquo;s started to look like procrastination. It already meets the &lt;a href="https://www.hackingwithswift.com/100/swiftui/47"&gt;specification&lt;/a&gt;, so I&amp;rsquo;m calling it an MVP and moving on.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/IanKulin/Habitual"&gt;&lt;img src="https://blog.iankulin.com/images/github-mark-32px.png" width="32" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/img_3110.png" width="266" alt=""&gt;
&lt;p&gt;This is the first app of mine I&amp;rsquo;ve loaded onto my phone and started using, and there are a couple of things I&amp;rsquo;d like to do with it. It currently just lets you specify how many days between an activity repeating - so if you say you should go to the gym every second day, and you complete that activity on Monday, &amp;ldquo;Gym&amp;rdquo; will make it&amp;rsquo;s way to the top of the list on Wednesday. While it&amp;rsquo;s waiting in the list for Wednesday to come around, it will show the &amp;ldquo;Due&amp;rdquo; time as being exactly 48 hours after you last pressed &amp;ldquo;done&amp;rdquo; on it. But if the habit you want is to go to the gym after work at 6:00pm that&amp;rsquo;s when you want it to be due. I&amp;rsquo;d like that.&lt;/p&gt;
&lt;p&gt;This does overlap a bit with the idea of a &amp;ldquo;ToDo&amp;rdquo; list, so maybe it&amp;rsquo;s a feature creep it shouldn&amp;rsquo;t have. Espeically since the reason I wanted it was because in the process of adding test activities I added &amp;ldquo;Take out the bins&amp;rdquo; and that has to be done on Wednesday nights.&lt;/p&gt;
&lt;p&gt;Paul&amp;rsquo; has moved away from all the data being in an @State in the View to having a data model object in a separate file. It feels like only a few steps until he jumps out from behind a bush and says &amp;ldquo;You&amp;rsquo;ve been writting MVVM already!&amp;rdquo; although currently its a bit more like just MV.&lt;/p&gt;
&lt;p&gt;The list of things I&amp;rsquo;m going to come back to is growing - I also have still not finished writing the custom picker so I can match the design work I paid for on the &lt;a href="https://blog.iankulin.com/design-help/"&gt;times table app&lt;/a&gt;. Nevertheless, I&amp;rsquo;ve so far spent 120 days on the &lt;a href="https://www.hackingwithswift.com/100/swiftui"&gt;#100DaysOfSwiftUI&lt;/a&gt; and I&amp;rsquo;m only up to day 47, so time to get moving.&lt;/p&gt;</description></item><item><title>Why?</title><link>https://blog.iankulin.com/why/</link><pubDate>Wed, 26 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/why/</guid><description>&lt;p&gt;Why do I have to resize this preview window every time I open Xcode?&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/y4imP93Czmc?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;
</description></item><item><title>JSON encode/decode</title><link>https://blog.iankulin.com/json-encode-decode/</link><pubDate>Mon, 24 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/json-encode-decode/</guid><description>&lt;img src="https://blog.iankulin.com/images/img_3110.png" width="150" alt="Screenshop of Habits app"&gt;
&lt;p&gt;As usual, I&amp;rsquo;m spending way more time on the apps written from scratch in the &lt;a href="https://www.hackingwithswift.com/guide/ios-swiftui/4/3/challenge"&gt;100Days series&lt;/a&gt;. The Habit tracking app I&amp;rsquo;m working on has been good practice, especially of the architecture of the simple &lt;a href="https://blog.iankulin.com/list-apps/"&gt;list based app&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;My version has a couple of refinements I quite like. I&amp;rsquo;m using a checkmark in a rectangle as the button to mark that activity as done, and I&amp;rsquo;ve added a nice fade to the checkmark as time goes on to represent the percentage of time from when it is done until it becomes due again.&lt;/p&gt;
&lt;p&gt;Until this app I&amp;rsquo;ve purely been using the preview and simulator for looking and and checking the operation of the app. But now I&amp;rsquo;ve started using my own iPhone for testing and actually trying to use the app from day to day. There&amp;rsquo;s a noticeable difference between thinking up use-cases in your head or &lt;a href="https://en.wikipedia.org/wiki/Eating_your_own_dog_food"&gt;dogfooding&lt;/a&gt; it. This has led to several improvements, but now I&amp;rsquo;m at a crossroads with one to do with the data persistence.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m using the Swift &lt;a href="https://www.ralfebert.com/ios/json-handling-in-swift/"&gt;built in JSON&lt;/a&gt; encode/decode which has been quite painless. It uses the struct property names (so not really in line with the JSON snakecase convention) and just magics everthing up for you. Here&amp;rsquo;s the structs, and the JSON produced after a week of using the app.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;HabitItem&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Identifiable&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; Codable&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;Equatable&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; id &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; UUID&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; name&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;String&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; started &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Date&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; timesDone &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; lastDone&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Date
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; daysBetweenCompletions &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;1.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;class&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;Habits&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; ObservableObject &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;Published &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; items &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;HabitItem&lt;span style="color:#eceff4"&gt;]()&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;didSet&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; encoded &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;try&lt;/span&gt;&lt;span style="color:#eceff4"&gt;?&lt;/span&gt; JSONEncoder&lt;span style="color:#eceff4"&gt;().&lt;/span&gt;encode&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;items&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; UserDefaults&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;standard&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;set&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;encoded&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; forKey&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Habits&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;init&lt;/span&gt;&lt;span style="color:#eceff4"&gt;()&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; savedItems &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; UserDefaults&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;standard&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;data&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;forKey&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Habits&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; decodedItems &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;try&lt;/span&gt;&lt;span style="color:#eceff4"&gt;?&lt;/span&gt; JSONDecoder&lt;span style="color:#eceff4"&gt;().&lt;/span&gt;decode&lt;span style="color:#eceff4"&gt;([&lt;/span&gt;HabitItem&lt;span style="color:#eceff4"&gt;].&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;self&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; from&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; savedItems&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; items &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; decodedItems
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; items &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;5D52B636-030A-4571-8B6E-2117AFD69304&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;timesDone&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;2&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;daysBetweenCompletions&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;1&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Dishes&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;lastDone&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;687916240.08548605&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;started&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;687741044.36479199&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;26D89BA0-D3FA-49CF-BB01-249A19E90871&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;timesDone&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;4&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;daysBetweenCompletions&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;1&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Bed&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;lastDone&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;688060851.81663799&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;started&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;687702031.59694302&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;C73234ED-3FA9-4B91-AC24-42E7391E3BE1&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;timesDone&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;10&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;daysBetweenCompletions&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;0.5&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Eating &amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;lastDone&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;688108866.69676399&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;started&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;687702494.12051105&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;0940450A-2A5E-4B11-B049-9B33F9C01F7D&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;timesDone&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;6&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;daysBetweenCompletions&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;0.5&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Brush teeth&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;lastDone&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;688110048.34211302&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;started&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;687702045.01338601&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;FA6556A8-17A8-4556-8690-9522A5B22856&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;timesDone&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;4&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;daysBetweenCompletions&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;1&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Write code&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;lastDone&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;688108275.33836806&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;started&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;687702594.956689&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;DA2A1F85-1274-4C58-B411-D6FF9B2A2AAF&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;timesDone&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;0&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;daysBetweenCompletions&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;7&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Walk exercise&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;lastDone&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;687702620.82404804&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;started&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;687702620.82411301&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;155D50A7-1403-47C6-AFCA-12DB13270A55&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;timesDone&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;1&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;daysBetweenCompletions&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;7&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Bins out&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;lastDone&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;687879744.60854602&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;started&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;687702107.33206701&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;86FE01B9-4716-4C2C-B768-1AC8E3B03DDB&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;timesDone&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;1&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;daysBetweenCompletions&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;7&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Roomba kitchen&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;lastDone&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;687916244.61723006&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;started&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;687702077.16412401&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;EEA3D2CD-3717-49DD-B5E2-924956F41189&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;timesDone&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;2&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;daysBetweenCompletions&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;7&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Washing&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;lastDone&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;688115581.30661595&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;&amp;#34;started&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#b48ead"&gt;687702096.33233297&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The problem I&amp;rsquo;ve got now I&amp;rsquo;d like to change the struct to add some different functionality. As soon as I change the struct, on the next run the JSON decode will fail and my data model will be empty. This can be caught, and I could use a renamed old version of my struct to load the existing data, and convert it across to the new model. What I&amp;rsquo;d really like is an option in the JSON decoder that if a property is missing, and I&amp;rsquo;ve provided a default in the struct, that it just uses that.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not the first developer to have this wish, and there&amp;rsquo;s a &lt;a href="https://stackoverflow.com/questions/44575293/with-jsondecoder-in-swift-4-can-missing-keys-use-a-default-value-instead-of-hav"&gt;number of solid solutions&lt;/a&gt;, but they all seem to require a bit more work than my simple dream above.&lt;/p&gt;</description></item><item><title>Refreshing SwiftUI Views</title><link>https://blog.iankulin.com/refreshing-swiftui-views/</link><pubDate>Sun, 23 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/refreshing-swiftui-views/</guid><description>&lt;p&gt;SwiftUI does some property wrapper magic to (very efficiently) refresh your views, but what if you want to force a refresh for some reason? Here&amp;rsquo;s the techniques I&amp;rsquo;m currently using to do that.&lt;/p&gt;
&lt;p&gt;The tricks are below, but just so you can see them in context, here&amp;rsquo;s the sample app we&amp;rsquo;re working on. It&amp;rsquo;s a list of cars so you can keep track of how many of each kind you own. Here&amp;rsquo;s our data:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;Car&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Identifiable&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; Codable&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;Equatable&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; id &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; UUID&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; model&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;String&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; number &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;class&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;Cars&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; ObservableObject &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;Published &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; items &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;Car&lt;span style="color:#eceff4"&gt;]()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;init&lt;/span&gt;&lt;span style="color:#eceff4"&gt;()&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; car1 &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Car&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;model&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Station wagon&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; car2 &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Car&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;model&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Sedan&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; car3 &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Car&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;model&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Hatchback&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; items &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;car1&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; car2&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; car3&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;func&lt;/span&gt; &lt;span style="color:#88c0d0"&gt;increment&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;_&lt;/span&gt; car&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Car&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;Bool&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; index &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; items&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;firstIndex&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;of&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; car&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; index &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; index &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; items&lt;span style="color:#eceff4"&gt;[&lt;/span&gt;index&lt;span style="color:#eceff4"&gt;].&lt;/span&gt;number &lt;span style="color:#81a1c1"&gt;+=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;else&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;deinit&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The app is a list inside a navigation stack. The view for each car is split out into a subview:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;ContentView&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;StateObject &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; cars &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Cars&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; NavigationView &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; List &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ForEach&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;cars&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;items&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; car &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CarView&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;car&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; car&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; cars&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; cars&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;navigationTitle&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Cars&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;CarView&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; car&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Car
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; cars&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Cars
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HStack &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;Int&lt;/span&gt;&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;random&lt;span style="color:#a3be8c"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;10.&lt;/span&gt;&lt;span style="color:#eceff4"&gt;..&lt;/span&gt;&lt;span style="color:#b48ead"&gt;99&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;))&lt;/span&gt;&lt;span style="color:#a3be8c"&gt; &amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; VStack&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;alignment&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;leading&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;car&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;model&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;font&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;headline&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Spacer&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34; &lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;car&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;number&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt; &amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;!&lt;/span&gt;cars&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;increment&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;car&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Unexpected error - car not found:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;car&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;model&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; label&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Image&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;systemName&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;plus&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The random Int is just so we can get a visual indication that our view has refreshed.&lt;/p&gt;
&lt;h4 id="trick-1---change-a-value"&gt;Trick 1 - change a value&lt;/h4&gt;
&lt;p&gt;All the other tricks rely on this trick. SwiftUI reacts to any @State property changing, so to force a change, there just needs to be a @State property we change. I add a:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#bf616a"&gt;@&lt;/span&gt;State &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; refresh &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;to my ContentView. Whenever this changes, ie refresh.toggle() the view will be redrawn. Of course we don&amp;rsquo;t want just the ContentView redrawn, but the subview as well, so it needs to be passed into the subview. We don&amp;rsquo;t do anything with it there, just pass it in.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;ContentView&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;StateObject &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; cars &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Cars&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;State &lt;span style="color:#81a1c1;font-weight:bold"&gt;private&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; refresh &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; NavigationView &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; List &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ForEach&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;cars&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;items&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; intItem &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CarView&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;car&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; intItem&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; cars&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; cars&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; refresh&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; refresh&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;navigationTitle&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Cars&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;CarView&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; car&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Car
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; cars&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Cars
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; refresh&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;Bool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HStack &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;Int&lt;/span&gt;&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;random&lt;span style="color:#a3be8c"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;10.&lt;/span&gt;&lt;span style="color:#eceff4"&gt;..&lt;/span&gt;&lt;span style="color:#b48ead"&gt;99&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;))&lt;/span&gt;&lt;span style="color:#a3be8c"&gt; &amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; VStack&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;alignment&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;leading&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;car&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;model&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;font&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;headline&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Spacer&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34; &lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;car&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;number&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt; &amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;!&lt;/span&gt;cars&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;increment&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;car&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Unexpected error - car not found:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;car&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;model&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; label&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Image&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;systemName&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;plus&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="trick-2---refreshable"&gt;Trick 2 - .refreshable()&lt;/h4&gt;
&lt;p&gt;That&amp;rsquo;s the infrastructure in place, so now we can use it. We could add a button that the user could click to refresh, but 2022 users have been trained to pull down on views to make them refresh, so let&amp;rsquo;s do that. Just add a &lt;code&gt;.refreshable()&lt;/code&gt; modifier to our list, then toggle refresh in the closure.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;ContentView&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;StateObject &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; cars &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Cars&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;State &lt;span style="color:#81a1c1;font-weight:bold"&gt;private&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; refresh &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; NavigationView &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; List &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ForEach&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;cars&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;items&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; intItem &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CarView&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;car&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; intItem&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; cars&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; cars&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; refresh&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; refresh&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;navigationTitle&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Cars&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;refreshable &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; refresh&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;toggle&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now when the user pulls the list down, the familiar wait wheel appears, the view, and the car subviews, all redraw.&lt;/p&gt;
&lt;h4 id="trick-3---onchangeof-scene"&gt;Trick 3 - OnChange(of: scene)&lt;/h4&gt;
&lt;p&gt;In my Habit app, the activities are going to appear with a temporal description of when they should be done, like &amp;ldquo;next week&amp;rdquo;, &amp;ldquo;in a few minutes&amp;rdquo;, or &amp;ldquo;tomorrow&amp;rdquo;. Those will be created from the date/time the activity was last done, how often the activity should be done, and the current date/time. So if our app has been in the background and we open it up, we want fresh calculations. We can do that by detecting a &lt;code&gt;scenePhase&lt;/code&gt; change to &lt;code&gt;.active&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We have to declare a variable for it, and then add the .onChange to the view:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;ContentView&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;StateObject &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; cars &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Cars&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;State &lt;span style="color:#81a1c1;font-weight:bold"&gt;private&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; refresh &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;Environment&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#bf616a"&gt;\&lt;/span&gt;&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;scenePhase&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; scenePhase
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; NavigationView &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; List &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ForEach&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;cars&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;items&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; intItem &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CarView&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;car&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; intItem&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; cars&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; cars&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; refresh&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; refresh&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;navigationTitle&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Cars&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;onChange&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;of&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; scenePhase&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; newPhase &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; newPhase &lt;span style="color:#eceff4"&gt;==&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;active &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; refresh&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;toggle&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="trick-4---a-timer"&gt;Trick 4 - a timer&lt;/h4&gt;
&lt;p&gt;Even if our user leaves the app open and foregrounded, eventually the descriptions will be out of date, so another thing we could do is use a timer. We have to add a timer property, and add an .onReceive() to the view. The timer interval in seconds is set when the timer is created. The example below is going to trigger every second.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;ContentView&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;StateObject &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; cars &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Cars&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;State &lt;span style="color:#81a1c1;font-weight:bold"&gt;private&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; refresh &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; timer &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Timer&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;publish&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;every&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;1&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; on&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;main&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;common&lt;span style="color:#eceff4"&gt;).&lt;/span&gt;autoconnect&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; NavigationView &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; List &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ForEach&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;cars&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;items&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; intItem &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CarView&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;car&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; intItem&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; cars&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; cars&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; refresh&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; refresh&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;navigationTitle&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Cars&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;onReceive&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;timer&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; perform&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;_&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; refresh&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;toggle&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>You need to enjoy puzzles</title><link>https://blog.iankulin.com/you-need-to-enjoy-puzzles/</link><pubDate>Sat, 22 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/you-need-to-enjoy-puzzles/</guid><description>&lt;p&gt;I&amp;rsquo;m writing the Habits &lt;a href="https://blog.iankulin.com/list-apps/"&gt;list based app&lt;/a&gt; from #100Days and had a working MVP, then for some reason, decided to refactor by changing the subview I&amp;rsquo;d written as a function, into a struct. Some time later, I discovered that my list items were not updating correctly, so detective time.&lt;/p&gt;
&lt;p&gt;I talked a little bit about the architecture yesterday - the item is a struct, and there&amp;rsquo;s a class containing an array of the items. Something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;Car&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Identifiable&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; Codable&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;Equatable&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; id &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; UUID&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; model&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;String&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; number &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;class&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;Cars&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; ObservableObject &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;Published &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; items &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;Car&lt;span style="color:#eceff4"&gt;]()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;init&lt;/span&gt;&lt;span style="color:#eceff4"&gt;()&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; car1 &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Car&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;model&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Station wagon&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; car2 &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Car&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;model&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Sedan&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; car3 &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Car&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;model&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Hatchback&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; items &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;car1&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; car2&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; car3&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;func&lt;/span&gt; &lt;span style="color:#88c0d0"&gt;increment&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;_&lt;/span&gt; car&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Car&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;Bool&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; index &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; items&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;firstIndex&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;of&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; car&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; index &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; index &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; items&lt;span style="color:#eceff4"&gt;[&lt;/span&gt;index&lt;span style="color:#eceff4"&gt;].&lt;/span&gt;number &lt;span style="color:#81a1c1"&gt;+=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;else&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;deinit&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then the ContentView is a NavigationView with a List. To build the list, I ForEach on the array inside the object, calling a subview on each one:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;ContentView&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;StateObject &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; cars &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Cars&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; NavigationView &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; List &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ForEach&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;cars&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;items&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; intItem &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CarView&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;car&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; intItem&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; cars&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; cars&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;navigationTitle&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Cars&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;CarView&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; car&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Car
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; cars&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Cars
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HStack &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; VStack&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;alignment&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;leading&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;car&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;model&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;font&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;headline&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Spacer&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34; &lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;car&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;number&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt; &amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;!&lt;/span&gt;cars&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;increment&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;car&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Unexpected error - car not found:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;car&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;model&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; label&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Image&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;systemName&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;plus&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When the user clicks on the button in a list item, the underlying data is amended somehow - in this pared down example the number of that type of car is incremented. Since my &amp;ldquo;car&amp;rdquo; is a value type there&amp;rsquo;s no point incrementing its number, so instead I call the .increment method on the cars object that is holding my array.&lt;/p&gt;
&lt;p&gt;Then, in that method, I have to search for the car the user intends to change, then change it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;func&lt;/span&gt; increment&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;_ car&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Car&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;-&amp;gt;&lt;/span&gt; Bool &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; let index &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; items&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;firstIndex&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;of&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; car&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; let index &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; index &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; items&lt;span style="color:#eceff4"&gt;[&lt;/span&gt;index&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;number &lt;span style="color:#81a1c1"&gt;+=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;else&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The situation of not being able to find the car should never occur, but out of longstanding habit I check for it and log an error to the console. This then happens repeatedly&amp;hellip;&lt;/p&gt;
&lt;p&gt;After some print statement debugging, I got it into my head that I&amp;rsquo;d caused this by changing the subview from a function (which had worked) to a struct (which was not). I decided it was a value type / reference type problem cause between the difference between these two subview approaches. Since that seems like an interesting blog post topic, I spent a long time distilling the code down to a simple looking example of this phenomena.&lt;/p&gt;
&lt;p&gt;Then tried it, it worked correctly.&lt;/p&gt;
&lt;p&gt;Then changed the function to the struct, it worked correctly.&lt;/p&gt;
&lt;p&gt;Went back to the original app, changed the subview function to a struct, it worked correctly.&lt;/p&gt;
&lt;p&gt;So I don&amp;rsquo;t know what caused my original problem, or what changed to fix it. In theory I could go back through the commits (they have helpful names like &amp;ldquo;general progress&amp;rdquo; and &amp;ldquo;progress save&amp;rdquo;) and find it.&lt;/p&gt;
&lt;p&gt;But I&amp;rsquo;ve already spent way longer on this app than I should have, and all I have to show for it is this post about what a doofus I am.&lt;/p&gt;</description></item><item><title>Purple warning - "Publishing changes"</title><link>https://blog.iankulin.com/purple-warning-publishing-changes/</link><pubDate>Fri, 21 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/purple-warning-publishing-changes/</guid><description>&lt;p&gt;It&amp;rsquo;s a pretty safe bet that if Xcode is saying there&amp;rsquo;s an error in my code, that it&amp;rsquo;s correct, and I am in error - not Xcode. Today I came across a situation where that might not be true.&lt;/p&gt;
&lt;p&gt;I think the purple warnings are problems detected at runtime - I&amp;rsquo;ve heard of thread problems causing purple warnings. The error I was getting was &amp;ldquo;&lt;code&gt;Publishing changes from within view updates is not allowed, this will cause undefined behavior.&lt;/code&gt;&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The error was on two lines in my model where I&amp;rsquo;d called a method to update the model from a button press in a sub-view function.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-17-at-5.16.18-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;After poking around to try and work it out, I found &lt;a href="https://www.donnywals.com/xcode-14-publishing-changes-from-within-view-updates-is-not-allowed-this-will-cause-undefined-behavior/"&gt;this clear blog post from Donny Wals&lt;/a&gt; and it turns out it&amp;rsquo;s possibly a bug in XCode 14. My situation fitted the description - calling the method from a button in a list, and the temporary workaround of adding a modifier to the button eliminated the warning.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m on Xcode 14.0, and I gather from Donny&amp;rsquo;s post that the problem might have been fixed in newer versions.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the calling code - a button in a list, with the &amp;ldquo;fix&amp;rdquo; on line 25.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;func&lt;/span&gt; &lt;span style="color:#88c0d0"&gt;habitView&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;habitItem&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; HabitItem&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; habitsCollection&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Habits&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;-&amp;gt;&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HStack &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; VStack&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;alignment&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;leading&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;habitItem&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;name&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;font&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;headline&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;habitItem&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;lastDone&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;formatted&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;date&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;abbreviated&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; time&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;omitted&lt;span style="color:#eceff4"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34; (&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;habitItem&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;timesDone&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;)&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;font&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;largeTitle&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Spacer&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;!&lt;/span&gt;habitsCollection&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;markAsDone&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;habit&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; habitItem&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Unexpected error - habit not found in collection:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;habitItem&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;name&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; label&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; habitItem&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;due &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Image&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;systemName&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;rectangle&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;font&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;system&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;size&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;40&lt;/span&gt;&lt;span style="color:#eceff4"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;else&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Image&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;systemName&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;checkmark.rectangle&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;font&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;system&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;size&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;40&lt;/span&gt;&lt;span style="color:#eceff4"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;buttonStyle&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;bordered&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>Drawing Feedback</title><link>https://blog.iankulin.com/drawing-feedback/</link><pubDate>Wed, 19 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/drawing-feedback/</guid><description>&lt;p&gt;Here&amp;rsquo;s the summary of my learning from comparing &lt;a href="https://blog.iankulin.com/project-9-drawing/"&gt;my efforts&lt;/a&gt; with Paul&amp;rsquo;s solutions to the Project Nine challenges from &lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/drawing-wrap-up"&gt;Day 46&lt;/a&gt; of his &lt;a href="https://www.hackingwithswift.com/100/swiftui"&gt;100 Days of SwiftUI course&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Create an &lt;code&gt;Arrow&lt;/code&gt; shape – having it point straight up is fine. This could be a rectangle/triangle-style arrow, or perhaps three lines, or maybe something else depending on what kind of arrow you want to draw.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Very similar solutions - Shape returning a Path, expect he finished off his with path.closeSubPath() rather than adding in the last line. That&amp;rsquo;s a bit neater, so that point to Paul.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Make the line thickness of your &lt;code&gt;Arrow&lt;/code&gt; shape animatable.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I did say in &lt;a href="https://blog.iankulin.com/project-9-drawing/"&gt;my solution&lt;/a&gt; that it seemed too simple, and I was expecting to have to use AnimatableData, and that was correct. Paul does not mean animating the &lt;em&gt;line&lt;/em&gt; &lt;em&gt;thickness&lt;/em&gt;, he means the &lt;em&gt;shaft width&lt;/em&gt; of the arrow - though I can see how they&amp;rsquo;re sort of the same thing. I wish he had his website code on GitHub and I would have fixed it for him, along with an error I&amp;rsquo;d spotted in one of the earlier lessons.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/e3GAMZAVqus?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;p&gt;In any case, Paul&amp;rsquo;s fix was as per the earlier lesson.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Create a &lt;code&gt;ColorCyclingRectangle&lt;/code&gt; shape that is the rectangular cousin of &lt;code&gt;ColorCyclingCircle&lt;/code&gt;, allowing us to control the position of the gradient using one or more properties.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This was just a matter of a couple of edits to the ColorCyclingCircle() struct Paul had created in one of the lessons to change the shape and to pass in values so the gradient could be manipulated. Although our solutions where similar, Paul&amp;rsquo;s was a bit more thorough - controlling the start and end points of the gradient.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m interested that Paul uses the US spelling for colour. I&amp;rsquo;ve wondered about that in my code - of course Apple spell it that way in all the Swift source, so it does look odd if you stick to the UK spelling, and I guess the majority of people who learn English as a second language probably learn American spellings.&lt;/p&gt;</description></item><item><title>Project 9 - Drawing</title><link>https://blog.iankulin.com/project-9-drawing/</link><pubDate>Tue, 18 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/project-9-drawing/</guid><description>&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-16-at-12.17.46-pm.jpg" alt="Screenshot of Xcode and the preview showing some fancy graphics"&gt;&lt;/p&gt;
&lt;p&gt;These few days of &lt;a href="https://www.hackingwithswift.com/100/swiftui/43"&gt;#100DaysOfSwiftUI&lt;/a&gt; we made some pretty shapes by playing around with some of the SwiftUI systems for drawing on the screen, including paths, shapes, transformations, ImagePaint, drawingGroup() to use Metal rendering, blurs, blend modes and using animatableData for animating - which I think is the solution to an animation problem in my TimesTable app I hadn&amp;rsquo;t been able to solve yet.&lt;/p&gt;
&lt;p&gt;The challenges were:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;Create an &lt;code&gt;Arrow&lt;/code&gt; shape – having it point straight up is fine. This could be a rectangle/triangle-style arrow, or perhaps three lines, or maybe something else depending on what kind of arrow you want to draw.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Make the line thickness of your &lt;code&gt;Arrow&lt;/code&gt; shape animatable.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Create a &lt;code&gt;ColorCyclingRectangle&lt;/code&gt; shape that is the rectangular cousin of &lt;code&gt;ColorCyclingCircle&lt;/code&gt;, allowing us to control the position of the gradient using one or more properties.&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h4 id="arrow-shape"&gt;Arrow shape&lt;/h4&gt;
&lt;p&gt;Nothing too tricky here - define the Arrow as a Shape, then build the part for it. The rect in path has yZero at the top.&lt;/p&gt;
&lt;h4 id="animate-line-thickness"&gt;Animate line thickness&lt;/h4&gt;
&lt;p&gt;I was expecting to have to use animatableData in the challenges, so perhaps I&amp;rsquo;ve misunderstood the brief here&lt;/p&gt;
&lt;h4 id="colorcycling-rectangle"&gt;ColorCycling Rectangle&lt;/h4&gt;
&lt;p&gt;I locked in the start point for the gradient, then let the user manipulate the end point with two sliders. I wanted a vertical slider, but it wasn&amp;rsquo;t as simple as rotating a slider so I abandoned that as not worth the time investment for the present.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;ContentView&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;State &lt;span style="color:#81a1c1;font-weight:bold"&gt;private&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; lineWidth &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;10.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;State &lt;span style="color:#81a1c1;font-weight:bold"&gt;private&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; xAmount &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;0.5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;@&lt;/span&gt;State &lt;span style="color:#81a1c1;font-weight:bold"&gt;private&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; yAmount &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;0.5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; VStack &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; endPoint &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; UnitPoint&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;x&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; xAmount&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; y&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; yAmount&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ColorCyclingRectangle&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;startPoint&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;top&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; endPoint&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; endPoint&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;padding&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Slider&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;value&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#bf616a"&gt;$&lt;/span&gt;xAmount&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;padding&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;horizontal&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Slider&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;value&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#bf616a"&gt;$&lt;/span&gt;yAmount&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;padding&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;horizontal&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Divider&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Arrow&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;stroke&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;red&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; style&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; StrokeStyle&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;lineWidth&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; lineWidth&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; lineCap&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;round&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; lineJoin&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;round&lt;span style="color:#eceff4"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;frame&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;width&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;200&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; height&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;400&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;animation&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;linear&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;duration&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;1&lt;/span&gt;&lt;span style="color:#eceff4"&gt;),&lt;/span&gt; value&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; lineWidth&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;padding&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;vertical&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HStack&lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Line width: &amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;5&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;lineWidth &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;5&lt;/span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;10&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;lineWidth &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;10&lt;/span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;25&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;lineWidth &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;25&lt;/span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;50&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;lineWidth &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;50&lt;/span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;padding&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;vertical&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;ColorCyclingRectangle&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; startPoint&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; UnitPoint
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; endPoint&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; UnitPoint
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; body&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ZStack &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ForEach&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;0.&lt;/span&gt;&lt;span style="color:#eceff4"&gt;.&amp;lt;&lt;/span&gt;&lt;span style="color:#b48ead"&gt;100&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; value &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Rectangle&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;inset&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;by&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;Double&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;value&lt;span style="color:#eceff4"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;strokeBorder&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; LinearGradient&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; gradient&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Gradient&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;colors&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; color&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;for&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; value&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; brightness&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;1&lt;/span&gt;&lt;span style="color:#eceff4"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; color&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;for&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; value&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; brightness&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;0.5&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;]),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; startPoint&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; startPoint&lt;span style="color:#eceff4"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; endPoint&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; endPoint
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lineWidth&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;drawingGroup&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;func&lt;/span&gt; &lt;span style="color:#88c0d0"&gt;color&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;for&lt;/span&gt; value&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;Int&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; brightness&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;Double&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;-&amp;gt;&lt;/span&gt; Color &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; targetHue &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;Double&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;value&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;/&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;Double&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;100&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;+&lt;/span&gt; &lt;span style="color:#b48ead"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; targetHue &lt;span style="color:#81a1c1"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#b48ead"&gt;1&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; targetHue &lt;span style="color:#81a1c1"&gt;-=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; Color&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;hue&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; targetHue&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; saturation&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;1&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; brightness&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; brightness&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;struct&lt;/span&gt; &lt;span style="color:#8fbcbb"&gt;Arrow&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Shape &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;func&lt;/span&gt; &lt;span style="color:#88c0d0"&gt;path&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; rect&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; CGRect&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;-&amp;gt;&lt;/span&gt; Path &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; path &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; Path&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;// follow the points from the tip around clockwise&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; path&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;move&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;to&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; CGPoint&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;x&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; rect&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;midX&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; y&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; rect&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;minY&lt;span style="color:#eceff4"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; path&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;addLine&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;to&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; CGPoint&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;x&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; rect&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;maxX&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; y&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; rect&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;maxY&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;&lt;span style="color:#b48ead"&gt;3&lt;/span&gt;&lt;span style="color:#eceff4"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; path&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;addLine&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;to&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; CGPoint&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;x&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; rect&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;maxX&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;&lt;span style="color:#b48ead"&gt;3&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;*&lt;/span&gt;&lt;span style="color:#b48ead"&gt;2&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; y&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; rect&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;maxY&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;&lt;span style="color:#b48ead"&gt;3&lt;/span&gt;&lt;span style="color:#eceff4"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; path&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;addLine&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;to&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; CGPoint&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;x&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; rect&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;maxX&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;&lt;span style="color:#b48ead"&gt;3&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;*&lt;/span&gt;&lt;span style="color:#b48ead"&gt;2&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; y&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; rect&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;maxY&lt;span style="color:#eceff4"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; path&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;addLine&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;to&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; CGPoint&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;x&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; rect&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;maxX&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;&lt;span style="color:#b48ead"&gt;3&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; y&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; rect&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;maxY&lt;span style="color:#eceff4"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; path&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;addLine&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;to&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; CGPoint&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;x&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; rect&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;maxX&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;&lt;span style="color:#b48ead"&gt;3&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; y&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; rect&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;maxY&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;&lt;span style="color:#b48ead"&gt;3&lt;/span&gt;&lt;span style="color:#eceff4"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; path&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;addLine&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;to&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; CGPoint&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;x&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; rect&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;minX&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; y&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; rect&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;maxY&lt;span style="color:#81a1c1"&gt;/&lt;/span&gt;&lt;span style="color:#b48ead"&gt;3&lt;/span&gt;&lt;span style="color:#eceff4"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; path&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;addLine&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;to&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; CGPoint&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;x&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; rect&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;midX&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; y&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; rect&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;minY&lt;span style="color:#eceff4"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; path
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As this wasn&amp;rsquo;t a real project - just scratch code of various techniques - I hadn&amp;rsquo;t worried about a git repo for it, but twice that came back to bite me when I wanted to go abandon an approach and pick up at an earlier point. That&amp;rsquo;s a lesson for me.&lt;/p&gt;</description></item><item><title>When it Works</title><link>https://blog.iankulin.com/when-it-works/</link><pubDate>Mon, 17 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/when-it-works/</guid><description>&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-15-at-10.43.18-am.jpg" alt="Screenshot of swiftui code and the iphone simulator with a roughly drawn face"&gt;&lt;/p&gt;
&lt;p&gt;The little joy of something working. It&amp;rsquo;s one of the things that makes coding enjoyable. Like a good video game you have an overarching goal, but on the way you need to solve a large number of problems of variable complexity, and you get a little bit of dopamine for each one.&lt;/p&gt;
&lt;p&gt;I think in every language I&amp;rsquo;ve ever learned, as soon as I know how to draw something on the screen, I start to get the urge to create a simple drawing application. When I was starting on Visual C++ and the MFC (Microsoft Foundation Classes) the &lt;a href="https://www.amazon.com/Beginning-Visual-C-Ivor-Horton/dp/1861000081"&gt;book&lt;/a&gt; I used to get started built a drawing application as an example. It hooks into the benefit of being able to quickly see the evidence you&amp;rsquo;ve achieved something.&lt;/p&gt;</description></item><item><title>Moonshot Feedback</title><link>https://blog.iankulin.com/moonshot-feedback/</link><pubDate>Sat, 15 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/moonshot-feedback/</guid><description>&lt;p&gt;I&amp;rsquo;ve watched Paul&amp;rsquo;s solution to the &lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/moonshot-wrap-up"&gt;Moonshot challenges&lt;/a&gt; (the solutions are one of the perks of being a Hacking With Swift subscriber). When I&amp;rsquo;m solo learning like this its one of the few ways I can get any feedback on my coding, so I highly value it, and usually write one of these posts as a way to ensure I reflect on it.&lt;/p&gt;
&lt;p&gt;The second challenge was to pull out a couple of sub-views, Paul had used a struct as I had, but put them in their own files. I think that&amp;rsquo;s good practice if those sections are going to be used from other views, otherwise I like them in the the file with the view they&amp;rsquo;re a part of. I guess if you make it a habit pull them out into files, you&amp;rsquo;d look there for them so it would not be a drama, but XCode is pretty handy for finding what you want in a reasonable size file, so that&amp;rsquo;s not a big consideration.&lt;/p&gt;
&lt;p&gt;The challenge I was really interested to see was his use of a List for the &amp;ldquo;list&amp;rdquo; version of the main view. Although his list looked a bit nicer when he&amp;rsquo;d finished, that&amp;rsquo;s more of a comment on the effort I put into the design (minimal - I just made a list version of the grid style). The List does detect that it&amp;rsquo;s inside a NavigationView and has those &amp;gt; signs at the end of each row - so there&amp;rsquo;s a visual prompt to the user that these are tappable for more. In my version, I&amp;rsquo;d just kept the existing scroll view, deleted the grid and used HStacks to build out the view for each mission.&lt;/p&gt;
&lt;p&gt;Lists do have a heap of features - checkboxes, multiple selection, pull down for refresh, left slide for delete and all sorts of other goodies. However, none of those are needed for this app, so in that respect my solution is fine.&lt;/p&gt;
&lt;p&gt;There is however, a generally important reason for using the standard iOS controls in the way they are intended - accessibility. There&amp;rsquo;s a good example of that a few minutes later when Paul adds his toolbar to the NavigationView. Here&amp;rsquo;s mine - succinct, nice use of the ternary operator:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;toolbar &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#bf616a"&gt;Image&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;systemName&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; showingList &lt;span style="color:#bf616a"&gt;?&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;square.grid.2x2&amp;#34;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;list.bullet&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;onTapGesture &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; showingList&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;toggle&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here&amp;rsquo;s Paul&amp;rsquo;s:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt; toolbar &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#bf616a"&gt;Button&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; showingGrid&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;toggle&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt; label&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; showingGrid
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#bf616a"&gt;Label&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Show as table&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; systemImage&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;list.dash&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;else&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#bf616a"&gt;Label&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Show as grid&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; systemImage&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;square.grid.2x2&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Essentially the same, except he&amp;rsquo;s used a Button rather than an Image with an .onTapGesture. You may ask why that matters - the answer is that if you were blind and using the iOS screen reader it would matter a lot. With Paul&amp;rsquo;s version the reader would know it was a button - something for collecting user input, AND it would read out the purpose contained in the label.&lt;/p&gt;
&lt;p&gt;Apple deserve credit for the effort and thought put into their accessibility features, and as developers we get a lot of that functionality for free, or at least very cheaply - but only if we do things the Apple way with standard controls.&lt;/p&gt;</description></item><item><title>Moonshot Challenges</title><link>https://blog.iankulin.com/moonshot-challenges/</link><pubDate>Fri, 14 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/moonshot-challenges/</guid><description>&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2022-10-09-at-2.00.26-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-09-at-2.00.26-pm.png" width="269" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Another few coding challenges at the end of a tutorial app in the &lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/moonshot-wrap-up"&gt;100 Days of SwiftUI&lt;/a&gt; course. The app is a sort of information app - composed of navigation views going down into more detail about the Apollo space missions. The most exciting revelation for me was how straightforward it is to pull JSON into your apps data structures.&lt;/p&gt;
&lt;h4 id="challenge-1"&gt;Challenge 1&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Add the launch date to &lt;code&gt;MissionView&lt;/code&gt;, below the mission badge. You might choose to format this differently given that more space is available, but it’s down to you.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Adding a text view, but also another computed property to the Mission type to retrieve a longer version of the date string.&lt;/p&gt;
&lt;h4 id="challenge-2"&gt;Challenge 2&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Extract one or two pieces of view code into their own new SwiftUI views – the horizontal scroll view in &lt;code&gt;MissionView&lt;/code&gt; is a great candidate, but if you followed my styling then you could also move the &lt;code&gt;Rectangle&lt;/code&gt; dividers out too.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the &lt;a href="https://blog.iankulin.com/tags/swiftlint/"&gt;SwiftLint&lt;/a&gt; rules is about the length of views, and I recall Paul Hegarty saying something in the first couple of CS193p lectures about views needing to be kept short. I never know the pros and cons of extracting parts of them as a function or as another struct. And if I do extract them as a struct, it always seems odd to me to just be able to &amp;ldquo;call&amp;rdquo; a struct exactly as a function - although since this is declarative programing and we&amp;rsquo;re just describing a view I guess it&amp;rsquo;s fine.&lt;/p&gt;
&lt;p&gt;For this change, I pulled it out the crew view and a custom divider as structs. I had to move a baby data struct out of the MissionView namespace to make it available to the new view. An alternative have been keeping the new struct also in the main view, but I don&amp;rsquo;t love doing that too much. The trade off here was the potential advantages of reusing the new sub-view somewhere else, ease of testing and shorter views, vs polluting the namespace and putting things in the global app scope that do not need to be there. Deciding factor for me in this case was just ease of understanding the code.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/IanKulin/MoonShot/commit/1e5c84ccc4692df06026425684831fab60300215"&gt;source&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="challenge-3"&gt;Challenge 3&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;For a tough challenge, add a toolbar item to &lt;code&gt;ContentView&lt;/code&gt; that toggles between showing missions as a grid and as a list.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&amp;rsquo;s some combination of unsettling and pleasing if Paul thinks something should be hard and I knock it out quickly. I never know if I&amp;rsquo;ve missed some crucial point or I&amp;rsquo;m just getting the hang of things.&lt;/p&gt;
&lt;p&gt;For this one I pulled out all the code that showed the grid inside a ScrollView(), duplicated it and edited the new one into a &amp;ldquo;list&amp;rdquo; by deleting the enclosing GridView(), changing a couple of VStacks to HStacks and tweaking some sizes. I added a @State variable for showing the grid, and a SF Symbol to the toolbar to swap between the views.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ve previously been advised to avoid an if statement to chose between two views as a state changes since it causes the new view to be created (rather than a ternary operator on a modifier or similar), but there&amp;rsquo;s no real way of avoiding it here - the user is literally asking us for a new view - so that goes into the main view where I extracted the other code from.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/IanKulin/MoonShot/commit/9faa8c5fe99628264fef184112685365330e60fd"&gt;source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When I read down into the hints after completing this, Paul talks about some gotchas with List() - which I hadn&amp;rsquo;t used, so I&amp;rsquo;ll be interested to see his wrap up to see if a List would have been better than my solution.&lt;/p&gt;</description></item><item><title>iExpense Feedback</title><link>https://blog.iankulin.com/iexpense-feedback/</link><pubDate>Wed, 12 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/iexpense-feedback/</guid><description>&lt;p&gt;I finally got around to looking at Paul&amp;rsquo;s solutions for the &lt;a href="https://blog.iankulin.com/iexpense-challenges/"&gt;iExpense challenges&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Use the user’s preferred currency, rather than always using US dollars.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Same approach as me,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.currency(code: Locale.current.currency?.identifier ?? &amp;quot;USD&amp;quot;)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;except that he does the work in a local variable which is a bit neater. Since it appears in two places - the display view and the view for adding an expense, this would mean duplicating it, making it global (not rare for user default type settings) or passing it down through the view hierarchy. I feel either of the first two options are fine for this project, but Paul is thorough and extends the FormatStyle protocol, only for currency, to have a new computed property .localcurrency - which is a great solution.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Modify the expense amounts in &lt;code&gt;ContentView&lt;/code&gt; to contain some styling depending on their value – expenses under $10 should have one style, expenses under $100 another, and expenses over $100 a third style. What those styles are depend on you.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&amp;rsquo;d done this with ternary operator modifiers:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(item.amount, format: .currency(code: Locale.current.currency?.identifier ?? &amp;#34;USD&amp;#34;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }.foregroundColor((item.amount &amp;lt; 10) ? .purple : (item.amount &amp;lt; 100) ? .green : .blue)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But Paul, again, goes one better with another extension, this time of View, to create a style(for: Item) - this is a better approach, especially if (as would be likely in a real app) it was going to need to be reused.&lt;/p&gt;
&lt;p&gt;I also note that Paul added new files for both these extensions. I think that&amp;rsquo;s wise for the currency one, but perhaps this one could probably have lived wherever the ExpenceItem was defined.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;For a bigger challenge, try splitting the expenses list into two sections: one for personal expenses, and one for business expenses. This is tricky for a few reasons, not least because it means being careful about how items are deleted!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is where things get weird. I split the types up by running to ForEach in their own groups. Paul had warned that deleting would be tricky because the offset into the array would not necessarily match the offset being passed to the remove method - so the wrong one might be deleted. I implemented my solution without worrying about that and it seemed to work, so I assumed I&amp;rsquo;d just come up with a better solution and moved on. But it does bear thinking about. Say (starting from no records) I create two business expenses names &lt;em&gt;Bus1&lt;/em&gt; &amp;amp; &lt;em&gt;Bus 2&lt;/em&gt;, and then two personal expenses names &lt;em&gt;Per 1&lt;/em&gt; and &lt;em&gt;Per 2&lt;/em&gt;. My array of expense objects is going to look a bit like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Bus 1 [0]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Bus 2 [1]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Per 1 [2]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Per 2 [3]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But my code is going to list the personal expenses first, then the business ones:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;List &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Section&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;header&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Personal&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;))&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ForEach&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;expenses&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;items&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; item &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Group &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; item&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;type &lt;span style="color:#eceff4"&gt;==&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Personal&amp;#34;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; itemView&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;item&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; item&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;onDelete&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;perform&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; removeItems&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Section&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;header&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Business&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;))&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ForEach&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;expenses&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;items&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; item &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Group &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; item&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;type &lt;span style="color:#eceff4"&gt;==&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Business&amp;#34;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; itemView&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;item&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; item&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;onDelete&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;perform&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; removeItems&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2022-10-08-at-3.06.30-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-08-at-3.06.30-pm.png" width="283" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The numbers in the brackets are what I think their array indexes should be. If what I understand Paul to be saying, if I now try to delete &lt;em&gt;Per 2,&lt;/em&gt; its offset in the list would be 1 (the second item in the list), but the remove call to the array is going to delete the expense at index 1, which is actually &lt;em&gt;Bus 2&lt;/em&gt;. But actually, it just deletes the correct one:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2022-10-08-at-3.14.51-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-08-at-3.14.51-pm.png" width="736" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2022-10-08-at-3.14.56-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-08-at-3.14.56-pm.png" width="734" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Perplexed, now that I&amp;rsquo;m thinking carefully about it. I did some print statement debugging:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;func&lt;/span&gt; &lt;span style="color:#88c0d0"&gt;removeItems&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;at offsets&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; IndexSet&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;for&lt;/span&gt; i &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; offsets &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Offset:&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;\(&lt;/span&gt;i&lt;span style="color:#a3be8c"&gt;)&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Before remove&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;for&lt;/span&gt; item &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; expenses&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;items &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;item&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;name&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; expenses&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;items&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;remove&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;atOffsets&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; offsets&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;After remove&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;for&lt;/span&gt; item &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; expenses&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;items &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;item&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;name&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Which outputs this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Offset:3
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Before remove
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Bus 1 [0]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Bus 2 [1]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Per 1 [2]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Per 2 [3]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;After remove
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Bus 1 [0]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Bus 2 [1]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Per 1 [2]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So actually, the IndexSet that SwiftUI passes to .onDelete() has already correctly identified the correct array index (shown as &amp;ldquo;Offset: 3&amp;rdquo; above). I assume as it&amp;rsquo;s built the List for me. That&amp;rsquo;s lovely, but I was perplexed about what Paul is talking about then.&lt;/p&gt;
&lt;p&gt;In Paul&amp;rsquo;s solution, he adds computed properties to the Expenses class to return arrays of either personal or business expenses. he does this with the arrays filter() method. This is a cool method (which I didn&amp;rsquo;t know about) but it returns a new array with elements meeting the filter condition. So it makes sense that when you create the index set from this, it&amp;rsquo;s indexed into the new smaller (filtered) array, so of course you need to account for this.&lt;/p&gt;
&lt;p&gt;In that case, this might be the first occasion when I think my solution is better than Paul&amp;rsquo;s!&lt;/p&gt;</description></item><item><title>SwiftLint</title><link>https://blog.iankulin.com/swiftlint/</link><pubDate>Mon, 10 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/swiftlint/</guid><description>&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screenshot-2022-10-04-at-08-30-59-code-complete-mcconnell-steve-amazon.com_.au-books.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I was watching a &lt;a href="https://www.techwithtim.net/"&gt;Tim Ruscica&lt;/a&gt; &lt;a href="https://www.youtube.com/watch?v=wJNikDr-aNM"&gt;video&lt;/a&gt; about the things that highly effective developers do, and it called to mind a book I read years ago called &lt;a href="https://www.amazon.com.au/Code-Complete-Steve-McConnell/dp/0735619670"&gt;Code Complete&lt;/a&gt;. It is the only book I ever owned that I immediately purchased the new edition when it came out. It was about the meta stuff around programming that is the difference between coding and developing. In particular, it got me invested in source control and testing.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve been reading along, you&amp;rsquo;ll know I am keen to leverage the great tools available to support quality software development, and one I haven&amp;rsquo;t tackled till this week is a linter.&lt;/p&gt;
&lt;p&gt;Linters are tools to enforce (or at least suggest) rules to improve your code that are not strictly necessary (the compiler hasn&amp;rsquo;t enforced them) but they make your code better, or at least prettier, in other ways. I gather the most popular linter for Swift is &lt;a href="https://github.com/realm/SwiftLint"&gt;SwiftLint&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I installed it by downloading and running the &lt;a href="https://github.com/realm/SwiftLint/releases/download/0.49.1/SwiftLint.pkg"&gt;.pkg&lt;/a&gt;. Then in any Xcode projects you want to use it in, you go into &lt;em&gt;Build Phases&lt;/em&gt; for the current &lt;em&gt;Scheme&lt;/em&gt; in your project and add a new script that runs SwiftLint on the files in the project&amp;rsquo;s folder. There&amp;rsquo;s a good step by step &lt;a href="https://medium.com/developerinsider/how-to-use-swiftlint-with-xcode-to-enforce-swift-style-and-conventions-368e49e910"&gt;here&lt;/a&gt; by Vineet Choudhary. You should end up with something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-04-at-8.39.34-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Now when you Command-B to build your project, the linter will run and (especially the first time) add some extra warnings and errors.&lt;/p&gt;
&lt;p&gt;SwiftLint has many rules. Some are default rules (these are the most widely accepted ones) and some are optional. Turning rules off or on, or altering their parameters is done using a &lt;code&gt;swiftlint.yml&lt;/code&gt; config file in the top level folder of your project.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example from &lt;a href="https://github.com/IanKulin/dotfiles/blob/main/.swiftlint.yml"&gt;mine&lt;/a&gt;, where I want to alter a rule:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;vertical_whitespace:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; max_empty_lines: 2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s a rule called vertical_whitespace. It throws a warning if there is more than one consecutive empty line in a file. I use two line gaps all over my code to signify a separate method or function, so that does not work for me. The config change above changes it to allow two empty lines. This same config file can also be used to disable any of the default rules, or enable any of the optional rules.&lt;/p&gt;
&lt;p&gt;Rules can also be disabled and enabled in your code with special comments. For example, in my tests, I have enormous multi-line strings which don&amp;rsquo;t follow proper indentation for a deliberate reason, so at the top of this file I have:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;// swiftlint:disable line_length
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;// swiftlint:disable indentation_width
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can turn rules back on as well. For example in this code snippet I have code that the linter wants me to write as a trailing closure, but I&amp;rsquo;m not sure how to do that in this situation yet - so I turn the rule off before it, then back on.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;func&lt;/span&gt; &lt;span style="color:#88c0d0"&gt;stripSpaces&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;_&lt;/span&gt; codeLines&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;String&lt;/span&gt; &lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;String&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;// break into lines&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; lines &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; codeLines&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;components&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;separatedBy&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#ebcb8b"&gt;\n&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; minCount &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;Int&lt;/span&gt;&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;max&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;// step though the lines and count how many spaces, save the minimum amount&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;for&lt;/span&gt; line &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; lines &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;// swiftlint:disable trailing_closure&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; leadingSpaces &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; line&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;prefix&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;while&lt;/span&gt;&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; $0 &lt;span style="color:#eceff4"&gt;==&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34; &amp;#34;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}).&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;count&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;// swiftlint:enable trailing_closure&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; minCount &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; leadingSpaces &lt;span style="color:#81a1c1"&gt;&amp;lt;&lt;/span&gt; minCount &lt;span style="color:#eceff4"&gt;?&lt;/span&gt; leadingSpaces &lt;span style="color:#eceff4"&gt;:&lt;/span&gt; minCount
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;// step though the lines again, and trim the min amount from each line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;for&lt;/span&gt; index &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; &lt;span style="color:#b48ead"&gt;0.&lt;/span&gt;&lt;span style="color:#eceff4"&gt;.&amp;lt;&lt;/span&gt;lines&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;count&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lines&lt;span style="color:#eceff4"&gt;[&lt;/span&gt;index&lt;span style="color:#eceff4"&gt;]&lt;/span&gt; &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;String&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;lines&lt;span style="color:#eceff4"&gt;[&lt;/span&gt;index&lt;span style="color:#eceff4"&gt;].&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;dropFirst&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;minCount&lt;span style="color:#eceff4"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#616e87;font-style:italic"&gt;// stitch it back up&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; lines&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;joined&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;separator&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#ebcb8b"&gt;\n&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;My strategy with the linter was to turn all the optional rules on, then run it and go through to consider each of the optional rules I&amp;rsquo;ve transgressed to decide if it makes more sense to me to change the code or eliminate the rule.&lt;/p&gt;</description></item><item><title>Testing, testing</title><link>https://blog.iankulin.com/testing-testing/</link><pubDate>Sun, 09 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/testing-testing/</guid><description>&lt;p&gt;I have unit testing in my goals, and if I&amp;rsquo;m going to throw this &lt;a href="https://blog.iankulin.com/codetrimmer-first-macos-app/"&gt;space trimming&lt;/a&gt; macOS utility up on the web, now might be a good time to figure out how to add unit tests to a project, how to write them, and how to run them. XCode is well set up for this, so it&amp;rsquo;s really no drama to do.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-03-at-9.09.32-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Although I haven&amp;rsquo;t worried with any unit testing up to this point in my iOS/Swift learning, in my previous programming work I did a lot of work with the large calculations involved in translating GPS coordinates and robotic positioning models where errors would be bad - so I&amp;rsquo;ve written a lot of tests over the years. I&amp;rsquo;ve also definitely felt the confidence you can dramatically refactor code with when you know the code has a robust test suite. I&amp;rsquo;m a big fan.&lt;/p&gt;
&lt;h4 id="adding-tests-to-existing-project"&gt;Adding Tests to Existing Project&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2022-10-03-at-9.28.39-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-03-at-9.28.39-pm.png" width="239" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In Xcode, with your project open, you need to add the &lt;em&gt;testing target&lt;/em&gt;. &lt;code&gt;File | New | Target...&lt;/code&gt; then scroll down to find the &lt;em&gt;Unit Testing Bundle&lt;/em&gt;. When you add that, you&amp;rsquo;ll see a new folder in the Project Navigator, and a new source file - both with the name &lt;code&gt;&amp;lt;app name&amp;gt;Tests&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you click into the &lt;code&gt;&amp;lt;app name&amp;gt;Test.swift&lt;/code&gt; file, you&amp;rsquo;ll see a couple of methods for setting up the testing environment and cleaning up after it. They are called before and after each test - you&amp;rsquo;d use them if you needed to set something up like some files in a directory or similar and wanted to ensure it was not affected by the tests run before the current test. Don&amp;rsquo;t worry about them for the time being.&lt;/p&gt;
&lt;h4 id="writing-tests"&gt;Writing Tests&lt;/h4&gt;
&lt;p&gt;Before we can write any tests, we need to import any modules with the code we want to test. In my case, I want to test the function stripSpaces() which is in the ContentView of my app called CodeTrimmer. So in the top of the CodeTrimmerTests.swift file, under the other import, I add the import:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;import XCTest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@testable import CodeTrimmer
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then we can write our tests. The test functions must start with the magic prefix &lt;em&gt;test&lt;/em&gt; so that Xcode treats them as such. The usual approach in the test is to call a function with some specific input, then test the output is what is expected with a special test version of assert():&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a couple. My function stripSpaces() deletes a number of spaces from each line of a block of code such that the block becomes left aligned. if the XCTAsserts() pass, the test passes and I get a green tick. To run the tests I just click in the breakpoint gutter next to the function declaration.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;func&lt;/span&gt; &lt;span style="color:#88c0d0"&gt;testStripSpaces01&lt;/span&gt;&lt;span style="color:#eceff4"&gt;()&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;throws&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; testString &lt;span style="color:#eceff4"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a3be8c"&gt; single line string no spaces
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a3be8c"&gt; &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; resultString &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; stripSpaces&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;testString&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; XCTAssertTrue&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;resultString &lt;span style="color:#eceff4"&gt;==&lt;/span&gt; testString&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; testString&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;func&lt;/span&gt; &lt;span style="color:#88c0d0"&gt;testStripSpaces02&lt;/span&gt;&lt;span style="color:#eceff4"&gt;()&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;throws&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; testString &lt;span style="color:#eceff4"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a3be8c"&gt; single line 4 spaces
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a3be8c"&gt; &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;let&lt;/span&gt; resultString &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; stripSpaces&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;testString&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; XCTAssertFalse&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;resultString &lt;span style="color:#eceff4"&gt;==&lt;/span&gt; testString&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; testString&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; XCTAssertTrue&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;testString&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;dropFirst&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;4&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;==&lt;/span&gt; resultString&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; testString&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>Where's My App?</title><link>https://blog.iankulin.com/wheres-my-app/</link><pubDate>Sat, 08 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/wheres-my-app/</guid><description>&lt;p&gt;The iOS apps I&amp;rsquo;ve been making, can only run in the simulator or on my tethered device (which I haven&amp;rsquo;t actually tried yet), but the MacOS app I made today, in theory could be zipped up and distributed to the world from my website. At the very least, I wanted to drop it into my Applications folder so I could use it, so I needed to find the .app &amp;ldquo;file&amp;rdquo;, and realised I had no idea where it was. If that&amp;rsquo;s your situation, then here&amp;rsquo;s the steps you need.&lt;/p&gt;
&lt;p&gt;First of all, you are currently building debug versions of your app. We need to create a new &lt;em&gt;Scheme&lt;/em&gt; for the release build. In Xcode use the menus to go to Product | Scheme | New Scheme&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-03-at-1.51.03-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Give it a sensible name - maybe &lt;code&gt;&amp;lt;app name&amp;gt; Release&lt;/code&gt; or similar. Then Product | Scheme | Edit Scheme and on the Info tab, change it to a &lt;em&gt;Release&lt;/em&gt; build.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-03-at-2.40.14-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Build the app (Product | Build ), then back in the Product menu, there&amp;rsquo;s an item &amp;ldquo;Show Build Folder in Finder&amp;rdquo;. Click on that, and there&amp;rsquo;s your different builds. In the Release folder will be the &lt;em&gt;&lt;app name&gt;.app&lt;/em&gt; file that can be copied into your applications folder.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not actually sure if this is a file, more likely a folder that MacOS is pretending is a file. If you right click on it and select &amp;ldquo;Show Package Contents&amp;rdquo; you can see the actual files inside it.&lt;/p&gt;</description></item><item><title>Customizing the default About dialog for MacOS apps</title><link>https://blog.iankulin.com/customizing-the-default-about-dialog-for-macos-apps/</link><pubDate>Fri, 07 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/customizing-the-default-about-dialog-for-macos-apps/</guid><description>&lt;p&gt;The default Xcode MacOS targeted app has a built in &amp;ldquo;About&amp;rdquo; dialog called up from the &amp;ldquo;About &lt;app name&gt;&amp;rdquo; menu item in the Mac menu bar. It wasn&amp;rsquo;t immediately clear to me how to customise this, but after digging through some MacOS apps on GitHub, here&amp;rsquo;s the answer.&lt;/p&gt;
&lt;p&gt;When you app is being built, it looks for the file &amp;ldquo;Credits.rtf&amp;rdquo; in the app bundle. If that is found (&lt;a href="https://developer.apple.com/documentation/appkit/nsapplication/aboutpaneloptionkey/2869609-credits"&gt;or &amp;ldquo;Credits.html&amp;rdquo; or &amp;ldquo;Credits.rtfd&amp;rdquo;&lt;/a&gt;) it&amp;rsquo;s used to build out the About dialog along with your app icon.&lt;/p&gt;
&lt;p&gt;After you&amp;rsquo;ve created the &amp;ldquo;Credits.rtf&amp;rdquo; file, you need to drop it into the folder for your project where the source files go. Then in Xcode, add it to your project in that inner folder:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-03-at-1.15.45-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Then when you rebuild the app, it will show in your about dialog.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-03-at-1.16.33-pm.jpg" alt=""&gt;&lt;/p&gt;</description></item><item><title>CodeTrimmer - First MacOS App</title><link>https://blog.iankulin.com/codetrimmer-first-macos-app/</link><pubDate>Thu, 06 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/codetrimmer-first-macos-app/</guid><description>&lt;p&gt;I was listening to the StackTrace app this morning (&lt;a href="https://stacktracepodcast.fm/episodes/169/"&gt;episode 169 - &amp;ldquo;Choosing What Bugs to Ship&amp;rdquo;&lt;/a&gt;) and one of the ideas discussed was taking the time to automate some of your development processes, partially to save time, but also because if you make a process simple and quick, you&amp;rsquo;ll be more likely to do it multiple times to improve quality.&lt;/p&gt;
&lt;p&gt;Coincidentally, I&amp;rsquo;d been thinking about how often I paste some code from Xcode in order to display it in one of these blog posts. If it&amp;rsquo;s from the middle of a method, it will generally be indented a long way in, and there&amp;rsquo;s no point in displaying it like that (especially for a mobile reader) so I usually manually delete a heap of spaces from each line to left align it whilst keeping the needed indentation.&lt;/p&gt;
&lt;p&gt;Sounds like a job for a tiny MacOS app - my first! Here it is in action. You copy your code from Xcode, and paste it into the app:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-03-at-11.43.38-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Then press the &amp;ldquo;Strip Spaces&amp;rdquo; button to get:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-03-at-11.43.43-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve got a few little niceties to add/tidy up, but it was a breeze converting my SwiftUI iOS development skills to MacOS - literally ticking a box.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/IanKulin/CodeTrimmer/blob/f0fc616bc1d334f7d5268f5231630940cd14d57b/CodeTrimmer/ContentView.swift"&gt;Source&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Design Challenge</title><link>https://blog.iankulin.com/design-challenge/</link><pubDate>Wed, 05 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/design-challenge/</guid><description>&lt;p&gt;So, I&amp;rsquo;ve been working on translating the &lt;a href="https://blog.iankulin.com/design-help/"&gt;UI design&lt;/a&gt; created by the external designer into SwiftUI, and have done all of the easy bits:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-03-at-8.19.43-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The rounded rectangles for things like the question display/number input are just ZStacks of roundedrects filled, then stroked:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ZStack &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RoundedRectangle&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;cornerRadius&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;10&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;fill&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;white&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;padding&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;horizontal&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RoundedRectangle&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;cornerRadius&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;10&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;stroke&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;black&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; lineWidth&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;2&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;padding&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;horizontal&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HStack &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;questionText&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;font&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;title&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;fontWeight&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;heavy&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;calculatorDisplay&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;font&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;title&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;fontWeight&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;heavy&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;foregroundColor&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;blue&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;frame&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;maxWidth&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;350&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;offset&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;y&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;15&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Something I have learned in the process is the .offset modifer. This is what&amp;rsquo;s used to move a view from where SwiftUI would have placed it, and is what I&amp;rsquo;ve done to create that overlapped style where the question display/number input is sitting halfway over the bottom of the blue rounded rectangle. This is in the last line of the code above: &lt;code&gt;.offset(y: 15)&lt;/code&gt; This is moving the whole ZStack down by 15. A trick to watch with this is that since you&amp;rsquo;ve messed with SwiftUI&amp;rsquo;s arrangement, it doesn&amp;rsquo;t then shuffle everything else around this - you need to manually deal with making some space below it.&lt;/p&gt;
&lt;p&gt;The jobs still to do on this view is the customisation of the picker, which I think is going to have to be writing a picker from scratch (or finding the source for the library picker and altering it), and the even more custom combined picker/progress indicator.&lt;/p&gt;</description></item><item><title>iExpense Challenges</title><link>https://blog.iankulin.com/iexpense-challenges/</link><pubDate>Sun, 02 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/iexpense-challenges/</guid><description>&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2022-09-29-at-6.41.29-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-29-at-6.41.29-am.png" width="308" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.hackingwithswift.com/100/swiftui/38"&gt;Day 38&lt;/a&gt; is three challenges on the iExpense app - a simple expense tracking app that uses UseDefaults for storing it&amp;rsquo;s data.&lt;/p&gt;
&lt;h3 id="locale"&gt;Locale&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Use the user’s preferred currency, rather than always using US dollars.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the joys of modern programming (as opposed to mid-1990&amp;rsquo;s programming) is the ability of the internet to give you answers. I knew the answer to this would be lurking in the locale environment variable, but instead of &lt;a href="https://developer.apple.com/documentation/swiftui/environmentvalues/locale"&gt;looking it up&lt;/a&gt;, just googled, and found a viable looking solution on &lt;a href="https://www.reddit.com/r/SwiftUI/comments/t7g7ds/localising_currency/"&gt;Reddit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Text(amount, format: .currency(code: Locale.current.currencyCode ?? &amp;quot;USD&amp;quot;))&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;When I paste it into Xcode, another modern miracle occurs - Xcode warns me know &lt;em&gt;currencyCode&lt;/em&gt; is out of date:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;'currencyCode' was deprecated in iOS 16: renamed to 'currency.identifier'&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;and offers to fix it for me:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Use 'currency.identifier' instead&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;So I&amp;rsquo;m like &amp;ldquo;sure, fix that for me&amp;rdquo;. Then there&amp;rsquo;s an error because the chaining is needs touched up now:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Value of optional type 'Locale.Currency?' must be unwrapped to refer to member 'identifier' of wrapped base type 'Locale.Currency'&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;and again offers to fix the chaining for me, so okay it, and end up with this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; fileprivate &lt;span style="color:#81a1c1;font-weight:bold"&gt;func&lt;/span&gt; &lt;span style="color:#88c0d0"&gt;itemView&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;item&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; ExpenseItem&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;-&amp;gt;&lt;/span&gt; some View &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HStack &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; VStack&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;alignment&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;leading&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;item&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;name&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;font&lt;span style="color:#eceff4"&gt;(.&lt;/span&gt;headline&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;item&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;type&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Spacer&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;item&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;amount&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; format&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;currency&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;code&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Locale&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;current&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;currency&lt;span style="color:#eceff4"&gt;?.&lt;/span&gt;identifier &lt;span style="color:#eceff4"&gt;??&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;USD&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}.&lt;/span&gt;foregroundColor&lt;span style="color:#eceff4"&gt;((&lt;/span&gt;item&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;amount &lt;span style="color:#81a1c1"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#b48ead"&gt;10&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;?&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;purple &lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;item&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;amount &lt;span style="color:#81a1c1"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#b48ead"&gt;100&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;?&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;green &lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;blue&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="conditional-formatting"&gt;Conditional formatting&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Modify the expense amounts in &lt;code&gt;ContentView&lt;/code&gt; to contain some styling depending on their value – expenses under $10 should have one style, expenses under $100 another, and expenses over $100 a third style. What those styles are depend on you.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Easy - ternary operator on HStack - see above&lt;/p&gt;
&lt;h3 id="conditional-list-building"&gt;Conditional list building&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;For a bigger challenge, try splitting the expenses list into two sections: one for personal expenses, and one for business expenses. This is tricky for a few reasons, not least because it means being careful about how items are deleted!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This was tricky, but I didn&amp;rsquo;t run into deletion problems. I thought I&amp;rsquo;d just run the ForEach twice and use an if inside it to build two different list sections. The compiler was very upset with this, saying something about not being able to determine the type in the view, but not being able to identify the view.&lt;/p&gt;
&lt;p&gt;The solution for this turned out to be to put them inside a Group{}. I guess this means it&amp;rsquo;s something related to the ways Views are built and the magic inside the @ViewBuilder property wrapper.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; List &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Section&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;header&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Personal&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;))&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ForEach&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;expenses&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;items&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; item &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Group &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; item&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;type &lt;span style="color:#eceff4"&gt;==&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Personal&amp;#34;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; itemView&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;item&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; item&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;onDelete&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;perform&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; removeItems&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Section&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;header&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Business&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;))&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ForEach&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;expenses&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;items&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; item &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Group &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; item&lt;span style="color:#eceff4"&gt;.&lt;/span&gt;type &lt;span style="color:#eceff4"&gt;==&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Business&amp;#34;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; itemView&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;item&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; item&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;onDelete&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;perform&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; removeItems&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;navigationTitle&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;iExpense&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;.&lt;/span&gt;toolbar&lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; showingAddExpense &lt;span style="color:#eceff4"&gt;=&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; label&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Image&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;systemName&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;plus&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>Times Tables -Day 35 Challenge</title><link>https://blog.iankulin.com/times-tables-day-35-challenge/</link><pubDate>Sat, 01 Oct 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/times-tables-day-35-challenge/</guid><description>&lt;p&gt;The challenge for &lt;a href="https://www.hackingwithswift.com/guide/ios-swiftui/3/3/challenge"&gt;Day 35&lt;/a&gt; of &lt;a href="https://www.hackingwithswift.com/100/swiftui"&gt;100 Days of Swift&lt;/a&gt; UI was to create a simple times tables drilling app. I&amp;rsquo;ve met all the requirements, so I&amp;rsquo;ll move on, but I am struck by how ugly it is. Making better looking apps needs to be added to my goals. Especially since this app is intended to appeal to children, and is at the end of a few lessons on animation, this is definitely a weakness of mine at the moment.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/ui-copy.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I enjoyed this challenge, it had a few interesting aspects. One was the number keypad for the user to enter, and the other one was creating the dialogue shown to the user at the end of a round with the statistics. I really don&amp;rsquo;t love the iOS modal dialogue boxes, so I ZStacked a rounded rectangle with some views on top of it, and controlled the visibility with .opacity. I wasn&amp;rsquo;t sure what would happen to the user OnTap events - would they go through? The answer is that if you make it very see through they go through to the elements underneath, but if it&amp;rsquo;s reasonably solid, they stay there.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/lQFDcBNAr-s?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/IanKulin/TimesTables/blob/719229d3caf80b12ddfff65032e0bee29036e1c9/TimesTables/ContentView.swift"&gt;Source&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Gitting Xcode to Push</title><link>https://blog.iankulin.com/gitting-xcode-to-push/</link><pubDate>Fri, 30 Sep 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/gitting-xcode-to-push/</guid><description>&lt;p&gt;I&amp;rsquo;m very comfortable with doing all the routine git stuff from the command line, but it was bugging me that I hadn&amp;rsquo;t for the Xcode integration working. I was able to commit locally with no problem from Xcode, but could not push up to Github. It works fine from the command line, so the error about the change to a stronger SSH authentication didn&amp;rsquo;t really make sense to me.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-26-at-6.57.35-am.png" alt=""&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;ERROR: You&amp;rsquo;re using an RSA key with SHA-1, which is no longer allowed. Please use a newer client or a different key type&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://developer.apple.com/forums/thread/702389"&gt;This post&lt;/a&gt; from &lt;a href="https://developer.apple.com/forums/profile/pasllani"&gt;pasllani&lt;/a&gt; on the Apple Developer forums was super helpful, the only thing they missed was that you need to restart Xcode before it will work. The steps were:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Generate a new ECDSA SSH key with &lt;code&gt;ssh-keygen -t ecdsa -C &amp;quot;IanKulin@kulin.com.au&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Copy the key to the keyboard with &lt;code&gt;pbcopy &amp;lt; ~/.ssh/id_ecdsa.pub&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;On Github, add the new SSH key, and while you&amp;rsquo;re there, generate a new Token in Developer Tools&lt;/li&gt;
&lt;li&gt;In XCode, delete your github account, then recreate it specifying SSH and choose the ECDSA key. It will need the access token at this stage.&lt;/li&gt;
&lt;li&gt;Restart XCode&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="other-gitgithub-posts"&gt;Other git/Github posts&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Intro to git - &lt;a href="https://blog.iankulin.com/gitting-started/"&gt;Gitting Started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Common git commands - &lt;a href="https://blog.iankulin.com/gitting-the-hang-of-it/"&gt;Gitting the Hang of it&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.iankulin.com/gitting-up-to-date/"&gt;Merge vs rebase&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.iankulin.com/create-an-empty-folder-on-github/"&gt;Create an Empty Folder on Github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.iankulin.com/download-a-directory-from-a-github-repo/"&gt;Download a Directory from a Github repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.iankulin.com/how-to-download-a-file-from-github/"&gt;Download a File from Github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>User Defaults &amp; Horizontal Pickers</title><link>https://blog.iankulin.com/user-defaults-horizontal-pickers/</link><pubDate>Wed, 28 Sep 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/user-defaults-horizontal-pickers/</guid><description>&lt;p&gt;I&amp;rsquo;m on the challenges for &lt;a href="https://www.hackingwithswift.com/guide/ios-swiftui/3/3/challenge"&gt;Day 35&lt;/a&gt; of &lt;a href="https://www.hackingwithswift.com/100/swiftui"&gt;100 Days of SwiftUI&lt;/a&gt;, and despite Paul&amp;rsquo;s very clear warning:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Important:&lt;/strong&gt; It’s really easy to get sucked into these challenges and spend hours&lt;/em&gt;&amp;hellip;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I have spent ages fiddling around, but of course still learning. My issue is not so much getting stuck on bugs, rather I keep wanting to do things I don&amp;rsquo;t know how to do.&lt;/p&gt;
&lt;p&gt;One issue was solved for my by the wonderful &lt;a href="https://firesideswift.fireside.fm/"&gt;Fireside Swift&lt;/a&gt; podcast. I&amp;rsquo;m working through the old (Steve &amp;amp; Zac) episodes, and they did one on the UserDefaults just when I wanted to be able to persist the multiplication table selection the user had made (this challenge app is a multiplication tables drill app for kids).&lt;/p&gt;
&lt;p&gt;First, because I hate hard coded strings, and they seem to be a thing in SwiftUI, and there&amp;rsquo;s no #const system, I&amp;rsquo;ve got an enum:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;enum&lt;/span&gt; HCS&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#bf616a"&gt;String&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;case&lt;/span&gt; operand &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Operand&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;case&lt;/span&gt; markerFeltWide &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;Marker Felt Wide&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then in the OnChange for the picker where I want to save/set the value into the default store:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.onChange(of: tablesSelection) { _ in 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; UserDefaults.standard.set(self.tablesSelection, 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; forKey: HCS.operand.rawValue)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; generateTable()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So basically, there&amp;rsquo;s a UserDefaults class that we can call the set method of, passing it the value we want to store, and a string value for the key.&lt;/p&gt;
&lt;p&gt;Getting it back is no harder:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#bf616a"&gt;@&lt;/span&gt;State private &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; tablesSelection &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; UserDefaults&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;standard&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;integer&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;forKey&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; HCS&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;operand&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;rawValue&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Super simple!&lt;/p&gt;
&lt;p&gt;If the key doesn&amp;rsquo;t exist, &lt;a href="https://developer.apple.com/documentation/foundation/userdefaults/1407405-integer"&gt;it returns a zero&lt;/a&gt;, so the slightly more complicated production version is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#bf616a"&gt;@&lt;/span&gt;State private &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; tablesSelection &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;UserDefaults&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;standard&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;integer&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;forKey&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; HCS&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;operand&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;rawValue&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;!=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;0&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#bf616a"&gt;?&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; UserDefaults&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;standard&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;integer&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;forKey&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; HCS&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;operand&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;rawValue&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#b48ead"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now to my horizontal wheel picker that the user manipulates to chose which times-table they want to practice:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-25-at-12.39.45-pm-1.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I love this little hack stolen from &lt;a href="https://stackoverflow.com/users/8279887/james-castrejon"&gt;James&lt;/a&gt; on &lt;a href="https://stackoverflow.com/questions/61965315/how-to-get-a-horizontal-picker-for-swift-ui"&gt;Stack Overflow&lt;/a&gt;. The first trick is that a .rotationEffect is applied to the picker, and the opposite .rotationEffect is applied to the selections. The second is to chop it off using the frame to make it a bit more compact.&lt;/p&gt;</description></item><item><title>Animating Guess The Flag</title><link>https://blog.iankulin.com/animating-guess-the-flag/</link><pubDate>Sat, 24 Sep 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/animating-guess-the-flag/</guid><description>&lt;p&gt;The challenges for &lt;a href="https://www.hackingwithswift.com/100/swiftui/34"&gt;Project 6&lt;/a&gt; of 100 Days of SwiftUI was to add some animations to the &lt;a href="https://blog.iankulin.com/project-2-guess-the-flag/"&gt;Guess the Flag&lt;/a&gt; app from a little while ago. The animations themselves were not particularly tricky, my main issue was that I was creating the views for the three flags in a ForEach, so the animations were applied to all three flags, but we wanted different animations for the flag the user had clicked versus those they had not.&lt;/p&gt;
&lt;p&gt;I got around this by having the magnitude of the changes stored separately for each of the flag views - so they were all being animated, but some by a zero amount.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://videopress.com/v/QjnVovjB?resizeToParent=true&amp;amp;cover=true&amp;amp;playsinline=true&amp;amp;preloadContent=metadata&amp;amp;useAverageColor=true"&gt;https://videopress.com/v/QjnVovjB?resizeToParent=true&amp;amp;cover=true&amp;amp;playsinline=true&amp;amp;preloadContent=metadata&amp;amp;useAverageColor=true&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s three animations going on here, so three @State properties in the view:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-18-at-3.06.16-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Then attached to the code to create the buttons are all the modifiers for the animation.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-18-at-3.07.18-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;In the FlagTapped() method you can see attached to the button there, we just change the ones we need:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-18-at-3.10.43-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Then, to be a little bit fancy, I re-animated the flagScales after the user closes the alert. I really do not love that alert - it was part of the tutorial project, but it is not a good UI - and it covers up some of the lovely animation.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/IanKulin/GuessTheFlag/compare/b373e1d..cfd2fd4"&gt;Source&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Animations in Views</title><link>https://blog.iankulin.com/animations-in-views/</link><pubDate>Fri, 23 Sep 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/animations-in-views/</guid><description>&lt;p&gt;It&amp;rsquo;s a very Apple-thinking thing to be learning about making beautiful and intuitive user experiences this early in a programing tutorial as I am with the &lt;a href="https://www.hackingwithswift.com/100/swiftui/32"&gt;100 Days of Swift UI&lt;/a&gt; series. Here&amp;rsquo;s a quick look at three different ways of doing animation in SwiftUI Views.&lt;/p&gt;
&lt;h4 id="implicit-animation"&gt;Implicit animation&lt;/h4&gt;
&lt;p&gt;An &lt;em&gt;implicit&lt;/em&gt; animation in SwiftUI is when you add a .&lt;em&gt;animation&lt;/em&gt;() modifier to a view. It needs to be bound to the value that&amp;rsquo;s changing so the framework knows to animate when that value changes, and the nature of the change.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2022-09-18-at-10.21.30-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-18-at-10.21.30-am.png" width="956" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://videopress.com/v/ItGiKMzz?resizeToParent=true&amp;amp;cover=true&amp;amp;preloadContent=metadata&amp;amp;useAverageColor=true"&gt;https://videopress.com/v/ItGiKMzz?resizeToParent=true&amp;amp;cover=true&amp;amp;preloadContent=metadata&amp;amp;useAverageColor=true&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In the example above, the value that&amp;rsquo;s changing is &lt;code&gt;rounded&lt;/code&gt;. We declare this as an @State variable, then in the .animation() modifier, we tell the framework to watch it. When it does change (because the user presses the bottom) SwiftUI considers the difference between the view rendered in rounded state, and in the non-rounded state, then generates and outputs the frames between them.&lt;/p&gt;
&lt;h4 id="binding-animation"&gt;Binding animation&lt;/h4&gt;
&lt;p&gt;The .animation() modifier can be attached when a variable is bound to a control.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-18-at-11.13.05-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve slightly complicated things here by adding the .timingCurve() inside the .animation() - otherwise the animation wasn&amp;rsquo;t obvious visually - the effect of this is just to slow down the animation so it keeps happening for a bit after you&amp;rsquo;ve adjusted the slider.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://videopress.com/v/3caKJJkZ?resizeToParent=true&amp;amp;cover=true&amp;amp;autoPlay=true&amp;amp;controls=false&amp;amp;loop=true&amp;amp;preloadContent=metadata&amp;amp;useAverageColor=true"&gt;https://videopress.com/v/3caKJJkZ?resizeToParent=true&amp;amp;cover=true&amp;amp;autoPlay=true&amp;amp;controls=false&amp;amp;loop=true&amp;amp;preloadContent=metadata&amp;amp;useAverageColor=true&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="explicit-animation"&gt;Explicit animation&lt;/h4&gt;
&lt;p&gt;Explicit animation is used when we want to control when the animation occurs, a common reason for this would be when we want to combine a couple of changes to take place simultaneously) .&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-18-at-11.38.02-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://videopress.com/v/6dE3XCSP?resizeToParent=true&amp;amp;cover=true&amp;amp;autoPlay=true&amp;amp;controls=false&amp;amp;loop=true&amp;amp;preloadContent=metadata&amp;amp;useAverageColor=true"&gt;https://videopress.com/v/6dE3XCSP?resizeToParent=true&amp;amp;cover=true&amp;amp;autoPlay=true&amp;amp;controls=false&amp;amp;loop=true&amp;amp;preloadContent=metadata&amp;amp;useAverageColor=true&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Don't Use Stupid Project Names</title><link>https://blog.iankulin.com/dont-use-stupid-project-names/</link><pubDate>Thu, 22 Sep 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/dont-use-stupid-project-names/</guid><description>&lt;p&gt;I&amp;rsquo;m up to &lt;a href="https://www.hackingwithswift.com/100/swiftui/32"&gt;Day 32&lt;/a&gt; of 100 Days of Swift UI, and although the tutorial is named &amp;ldquo;Project 6&amp;rdquo; it&amp;rsquo;s not really a project that becomes a simple app, it&amp;rsquo;s really just a series of tutorials on animation that I assume the techniques, but none of the code, will be used later in apps.&lt;/p&gt;
&lt;p&gt;I do find there&amp;rsquo;s some value in typing in the code (rather than cutting and pasting, or just passively watching) so I opened up Xcode to follow along. There not being an app name offered, I used &amp;ldquo;Project 6 - Animation&amp;rdquo;. XCode seemed happy enough with that, and created the directory and placeholder, but then refused to build it saying there was seven errors involving the __PACKAGE_NAME macro and a missing }&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-18-at-8.45.57-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I did a do-over with a project name of &amp;ldquo;Animation&amp;rdquo; and it works just fine. When I updated to XCode 14 I didn&amp;rsquo;t keep the previous version, so I&amp;rsquo;ve no idea if letting the user do this is an introduced bug or not. The culprit is the &amp;lsquo;-&amp;rsquo;. Projects with just a space in the name work fine.&lt;/p&gt;
&lt;p&gt;Since a google of the first error does not pull up any hits, I&amp;rsquo;ll paste it in here for future travelers giving their projects names with dashes.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;'___PACKAGENAME' is annotated with @main and must provide a main static function of type () -&amp;gt; Void, () throws -&amp;gt; Void, () async -&amp;gt; Void, or () async throws -&amp;gt; Void&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;As far as I can make out, that is part of the boilerplate creation process and should have been replaced. Likely the dash and the spaces are all replaced with underscores to make a valid app name and the triple underscore has a magic meaning of some kind.&lt;/p&gt;</description></item><item><title>Recording the Simulator</title><link>https://blog.iankulin.com/recording-the-simulator/</link><pubDate>Wed, 21 Sep 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/recording-the-simulator/</guid><description>&lt;p&gt;I&amp;rsquo;ve been using Quicktime to screen record the preview or simulator to document my work here and in Github ReadMe&amp;rsquo;s, but thanks to this &lt;a href="https://sarunw.com/posts/record-ios-simulator-video-and-gif-with-xcode/"&gt;excellent short post&lt;/a&gt; by &lt;a href="https://twitter.com/sarunw"&gt;Sarun W&lt;/a&gt;, I now know it&amp;rsquo;s possible to do that directly from the simulator, including capturing gestures, and exporting to animated gifs!&lt;/p&gt;</description></item><item><title>Changing Xcode Font Size</title><link>https://blog.iankulin.com/changing-xcode-font-size/</link><pubDate>Tue, 20 Sep 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/changing-xcode-font-size/</guid><description>&lt;p&gt;I&amp;rsquo;ve been changing the Xcode font size by going into the &lt;em&gt;preferences&lt;/em&gt;, &lt;em&gt;themes&lt;/em&gt;, then selecting the different content types and changing the font size.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-18-at-7.04.52-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I guess I could speed that process up by saving some themes with different text sizes, but I&amp;rsquo;ve noticed a few times in tutorial videos that there must have been an easier way. It turns out that Ctrl + &amp;ldquo;+&amp;rdquo; and Ctrl + &amp;ldquo;-&amp;rdquo; increases and decreases the code font size.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not sure why I need to change the font size so often, but it was bugging me, so I&amp;rsquo;m glad to find there&amp;rsquo;s sensible shortcuts for it.&lt;/p&gt;</description></item><item><title>Project 5 - Word Scramble</title><link>https://blog.iankulin.com/project-5-word-scramble/</link><pubDate>Mon, 19 Sep 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/project-5-word-scramble/</guid><description>&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-17-at-4.30.14-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Another &lt;a href="https://www.hackingwithswift.com/100/swiftui"&gt;100 Days of Swift UI&lt;/a&gt; project wrapped up - this time a scrabble like word game. New techniques included saving a large text file in the app bundle and loading it (via a string) into an array on launch of the view. Also a short adventure into UIKit to use a UITextChecker.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/IanKulin/WordScramble/compare/c64c21d..ada15e2"&gt;Source&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Word Scramble Feedback</title><link>https://blog.iankulin.com/word-scramble-feedback/</link><pubDate>Mon, 19 Sep 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/word-scramble-feedback/</guid><description>&lt;p&gt;As is my practice now, after completing the &lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/word-scramble-wrap-up"&gt;challenges for Project 5&lt;/a&gt;, I reviewed Paul&amp;rsquo;s solution (which is only available to subscribers) to see what he&amp;rsquo;d done better so I could learn from it.&lt;/p&gt;
&lt;p&gt;Most of the differences where not of much significance, but there was a couple of things I picked up:&lt;/p&gt;
&lt;p&gt;When the user had pressed the reset button, to empty the array of word guesses from the previous turn, I had&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;usedWords = [String]()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Whereas Paul had:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-17-at-6.19.35-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I have no idea if that&amp;rsquo;s better performing or safer, but to me, it&amp;rsquo;s a lot clearer, so I prefer Paul&amp;rsquo;s solution.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2022-09-17-at-4.30.14-pm-1.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-17-at-4.30.14-pm-1.png" width="266" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The second difference is a user experience one. I had chosen to put the score and reset button both in a bottom toolbar. It&amp;rsquo;s a good solution in the sense that it keeps the score and the reset buttons visible regardless of the size of the (scrollable) list. My code for this was appended to the bottom of the list:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;toolbar &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ToolbarItem&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;placement&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;bottomBar&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HStack &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Score: \(score)&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Spacer&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#bf616a"&gt;Button&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;New Word&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; startGame&lt;span style="color:#eceff4"&gt;()&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Paul used a &lt;em&gt;SafeAreaInset&lt;/em&gt; appended to the bottom of the list, and as he was entering it I was thinking it was overly complex:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;•safeAreaInset(edge:.bottom) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(&amp;#34;Score: \(score)&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .frame (maxWidth: .infinitv)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .padding()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .background( .blue)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .foregroundColor(.white)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .font(.title)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;until I saw the result and loved it.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2022-09-17-at-6.22.34-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-17-at-6.22.34-pm.png" width="490" alt=""&gt;&lt;/a&gt;&lt;/p&gt;</description></item></channel></rss>