<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>100daysofswiftui on blog.iankulin.com</title><link>https://blog.iankulin.com/tags/100daysofswiftui/</link><description>Recent content in 100daysofswiftui on blog.iankulin.com</description><generator>Hugo</generator><language>en-AU</language><lastBuildDate>Sat, 26 Nov 2022 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.iankulin.com/tags/100daysofswiftui/index.xml" rel="self" type="application/rss+xml"/><item><title>FriendFace 61 Feedback</title><link>https://blog.iankulin.com/friendface-61-feedback/</link><pubDate>Sat, 26 Nov 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/friendface-61-feedback/</guid><description>&lt;p&gt;As usual after a challenge, I compare my efforts to Paul&amp;rsquo;s model solution. Just to quickly recap the app, it sucks up some data (Users who have multiple friends) and displays it. The change in this challenge was to convert it to add that data to a Core Data store so that if a future network error prevented accessing new data, it could still display the old.&lt;/p&gt;
&lt;h4 id="merge-policy"&gt;Merge Policy&lt;/h4&gt;
&lt;p&gt;The first difference is that Paul adds a merge policy. A Merge policy tells Core how to deal with any constraints defined in the data model. In this app, I&amp;rsquo;d defined the CachedUser.id as a constraint. The purpose of this is that under normal conditions the app would be picking up mostly the same data each time it started up. We don&amp;rsquo;t want scabs of duplicate data, so constraining users based on their unique id is smart.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-11-23-at-4.07.02-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://developer.apple.com/documentation/coredata/nsmergepolicy/merge_policies"&gt;merge policy&lt;/a&gt; tells Core Data how to deal with these conflicts. The policy Paul uses is mergeByPropertyObjectTrump - basically the memory version of an object (in this case the one just provided by the JSON) writes over a previously stored version.&lt;/p&gt;
&lt;p&gt;For testing, I had commented out my &lt;code&gt;try? moc.save()&lt;/code&gt; otherwise I might have been reminded to do this one. First round to Paul, again.&lt;/p&gt;
&lt;h4 id="data"&gt;Data&lt;/h4&gt;
&lt;p&gt;The data model and additions to the managed object source files were all pretty similar to mine, except that Paul left his id&amp;rsquo;s as UUID and it seemed to be happy with these being written to, so I need to look into that to understand what I was doing wrong,&lt;/p&gt;
&lt;p&gt;As far as copying the data over, Paul did it all in a function in the ContentView - I preferred my concept of having these as methods in CachedUser and CachedFriend. He mentions this, and it sounds like he agrees with me, but it&amp;rsquo;s a tiny project so no matter.&lt;/p&gt;
&lt;h4 id="mainactor"&gt;MainActor&lt;/h4&gt;
&lt;p&gt;When outlining the challenge, Paul went to a bit of trouble to explain the way to avoid a clash between updating the view (based on the data) and updating the data using &lt;code&gt;await MainActor.run&lt;/code&gt;. Without really knowing what I was doing, I put this call after the fetch for the JSON. In Paul&amp;rsquo;s version he has this inside the fetch when it&amp;rsquo;s complete. This actually makes more sense - with my version I might be building the Core Data copy before (or even while) it&amp;rsquo;s being fetched.&lt;/p&gt;</description></item><item><title>61 Done</title><link>https://blog.iankulin.com/61-done/</link><pubDate>Fri, 25 Nov 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/61-done/</guid><description>&lt;img src="https://blog.iankulin.com/images/green_tick.svg_.png" width="164" alt=""&gt;
&lt;p&gt;I think I&amp;rsquo;ve finally completed the minimum work for Day 61 of &lt;a href="https://www.hackingwithswift.com/100/swiftui/61"&gt;#100DaysOfSwiftUI&lt;/a&gt;. The task was to suck up some data in JSON, decode it. back it up into a Core Data graph then display the data from the Core Data.&lt;/p&gt;
&lt;p&gt;I got stuck on dealing with the one:many relationship and had to revisit that from a different source to get my head around it, after that it was straightforward. Other small problem I ran into was that I created the id in the CachedUser as a UUID from (newly formed) habit. Then when I went to copy it in from the JSON version, it wouldn&amp;rsquo;t let me. When I realised my mistake and changed it in the data model, I still could not figure out why it wasn&amp;rsquo;t working - but of course I hadn&amp;rsquo;t regenerated the code for the ManagedObject. I just had to change the property type in the already generated code from UUID to string and I was back in business.&lt;/p&gt;
&lt;p&gt;There are five ominous looking, indecipherable messages in the console log, which I assume are related to Core Data, and I think may have begun after I changed the data model and didn&amp;rsquo;t regenerate the NSManagedObjects. The app seems to work perfectly, but as usualy, this leaves me with an uneasy feeling of not understanding everything going on that I couldn&amp;rsquo;t tolerate in a production app.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m looking forwards to seeing Pauls solution for this one - I was out of my depth for a while, but think I&amp;rsquo;m on to it. I might even pause on the course and build a better add/delete/edit app with a one:many in Core Data just to ensure I&amp;rsquo;ve really got the basics of this topic under my belt.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/IanKulin/FriendFace/tree/main/FriendFace"&gt;Source&lt;/a&gt;&lt;/p&gt;</description></item><item><title>FriendFace Feedback</title><link>https://blog.iankulin.com/friendface-feedback/</link><pubDate>Tue, 15 Nov 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/friendface-feedback/</guid><description>&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-11-12-at-4.38.24-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;After each app, I use my HackingWithSwift+ membership to view Paul&amp;rsquo;s version of the app as a way to judge my performance. Yesterday&amp;rsquo;s app was &amp;ldquo;&lt;a href="https://www.hackingwithswift.com/guide/ios-swiftui/5/3/challenge"&gt;FriendFace&lt;/a&gt;&amp;rdquo; - download some JSON of a number of people (including their friends) and display it.&lt;/p&gt;
&lt;h4 id="uuid"&gt;UUID&lt;/h4&gt;
&lt;p&gt;In my struct, I&amp;rsquo;d just specified the User.id as a string, Paul uses UUID - this makes no difference to the app as it stands, but is much better if we ever needed to add users.&lt;/p&gt;
&lt;h4 id="example"&gt;Example&lt;/h4&gt;
&lt;p&gt;For my details preview, I created a fake user in the preview. Paul does something slightly nicer - with a static let example = User(&amp;hellip;) as a property of the User struct. Again - not a biggy for this app, but handy if you need to use it for other previews.&lt;/p&gt;
&lt;h4 id="fetchusers"&gt;fetchUsers()&lt;/h4&gt;
&lt;p&gt;I had a test in the .task() to prevent the double loading of data, Paul puts his here inside a guard. I think mine is simpler to read and understand, but Paul&amp;rsquo;s would be better if there was a chance we might call fetchUsers() from other places in the app. If that&amp;rsquo;s likely, this would be the third time out of three differences where Paul is anticipating a bigger app - it might just be a reflex for such an accomplished developer. However, I&amp;rsquo;m going to award this one to me.&lt;/p&gt;
&lt;h4 id="docatch"&gt;do/catch&lt;/h4&gt;
&lt;p&gt;Paul&amp;rsquo;s JSON fetching and decoding is in a single do/catch block - as mine was in my first drafting, but then it didn&amp;rsquo;t work correctly so I changed it. I think we can assume Paul&amp;rsquo;s works - he&amp;rsquo;s a quality coder, and this code has had a lot of eyes on it by now. So this is something I need to understand better.&lt;/p&gt;
&lt;p&gt;Apart from the two items above, the rest of fetchUsers was essentially the same.&lt;/p&gt;
&lt;h4 id="list"&gt;List&lt;/h4&gt;
&lt;p&gt;Paul used little red or green circles to indicate the active status of users in his list - much nicer than my version of having the text. Apart from that, it was a NavigationView iwht a List with a NavigationLink to the detail view, so same same.&lt;/p&gt;
&lt;h4 id="userdetails"&gt;UserDetails&lt;/h4&gt;
&lt;p&gt;Instead of a form, Paul uses a List with a .listStyle of .grouped which probably follows the HIG better.&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/BmPPhnIxoHM?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>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 Feedback</title><link>https://blog.iankulin.com/project-12-feedback/</link><pubDate>Fri, 11 Nov 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/project-12-feedback/</guid><description>&lt;p&gt;As usual, I watch Paul&amp;rsquo;s solution video, and compare it to mine.&lt;/p&gt;
&lt;h4 id="task-1"&gt;Task 1&lt;/h4&gt;
&lt;p&gt;This was passing in the predicate as a String. I passed the whole thing, but as I figured out along the way, Paul meant just the operator word. He also added some buttons to test it better, which I didn&amp;rsquo;t think of till Task 3 - it would have saved me some simulator runs.&lt;/p&gt;
&lt;h4 id="task-2"&gt;Task 2&lt;/h4&gt;
&lt;p&gt;This was changing to enums. As I mentioned, I am a fan. Meanwhile, my solution was exactly the same as Paul&amp;rsquo;s, down to where he put the enum.&lt;/p&gt;
&lt;h4 id="task-3"&gt;Task 3&lt;/h4&gt;
&lt;p&gt;Another FilteredList argument - this time the sort descriptors. I did get a bit bogged down in this. I followed the build error and was trying to use NSSortDescriptors and tying myself up in knots to get it to work. When I reread the task, I saw Paul&amp;rsquo;s hint to use SortDescriptor&lt;Singer&gt; then it all came together - and we had the same solution.&lt;/p&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>You Can Take Big Steps When You Feel Safe</title><link>https://blog.iankulin.com/you-can-take-big-steps-when-you-feel-safe/</link><pubDate>Wed, 09 Nov 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/you-can-take-big-steps-when-you-feel-safe/</guid><description>&lt;p&gt;&lt;a href="https://www.deviantart.com/jhonair/art/Forest-of-giantess-604262747"&gt;&lt;img src="https://blog.iankulin.com/images/forest-of-giantess-jhonair.png" alt="" title="Forest-of-giantess By JhonAir"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.hackingwithswift.com/100/swiftui/58"&gt;Day 58&lt;/a&gt; of &lt;a href="https://www.hackingwithswift.com/100/swiftui"&gt;#100Days&lt;/a&gt; feels like complex topics are being dropped in pretty fast. We tackle one:many data relationships and how to set them up in CoreData, using CoreData constraints and setting a merge policy to manage conflicts, and even the underscore to access the actual property inside a wrapped property struct (needed for dynamic filtering in a view).&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve &lt;a href="https://blog.iankulin.com/top-four-reasons-why-twostraws-is-a-good-teacher/"&gt;mentioned before&lt;/a&gt; that I think Paul Hudson is an excellent teacher, and an example of this is that even though this was a day with a lot of challenging material, I&amp;rsquo;m not worried. I followed the discussion and tried the code, and more importantly I&amp;rsquo;m anticipating these new skills will be practiced in the next app, and probably shortly after I&amp;rsquo;ll be writing an app using them.&lt;/p&gt;
&lt;p&gt;When learners feel safe and supported, they are comfortable taking bigger risks. This has the effect of growing their Zone of Proximal Development and allows faster learning.&lt;/p&gt;
&lt;p&gt;Some of the complexity around CoreData relates to it&amp;rsquo;s pre-SwiftUI age - it has a lot of power, and does a lot for the developer but is full of non-intuitive bits. The rest of the complexity is really just related to it&amp;rsquo;s job - any object graph persistence that&amp;rsquo;s going to allow us to think of, and work with, our data as native objects is going to have to expose some of the complexity of what&amp;rsquo;s happening underneath in order to provide the flexibility needed. What&amp;rsquo;s not so evident in this implementation is Swifts progressive disclosure of complexity. It&amp;rsquo;s easy to imagine a modern rewrite of a more Swift-like object persistence framework being less scary.&lt;/p&gt;
&lt;p&gt;Since CoreData is using SQLite underneath, an interesting question is what the same code would look like if you pulled in an SQLite library and handled things manually - to approach the same functionality - ie not refetching when a view is recreated if the data hasn&amp;rsquo;t changed, lazy list building etc. My guess is: a lot more complex.&lt;/p&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>Top Four Reasons why @TwoStraws is a Good Teacher</title><link>https://blog.iankulin.com/top-four-reasons-why-twostraws-is-a-good-teacher/</link><pubDate>Tue, 01 Nov 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/top-four-reasons-why-twostraws-is-a-good-teacher/</guid><description>&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2022-10-29-at-1.28.59-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-10-29-at-1.28.59-pm.png" width="241" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="good-questions"&gt;Good Questions&lt;/h4&gt;
&lt;p&gt;At various points in the &lt;a href="https://www.hackingwithswift.com/100/swiftui"&gt;100 Days of SwiftUI&lt;/a&gt; course, you get asked sets of questions to check you&amp;rsquo;ve understood the preceding material. They&amp;rsquo;re usually presented as two different statements, one of which is true, and the other false. It&amp;rsquo;s actually a really good technique - the student feels like they&amp;rsquo;ve got a couple of opportunities to figure it out, plus they are forced to read both statements and think about them. Paul does a similar thing in the Unwrapped app - there, the questions are often presented as &amp;ldquo;Is this valid Swift code&amp;rdquo; and the user needs to scan through it all looking for mistakes. It&amp;rsquo;s checking your understanding, and making you a thoughtful debugger!&lt;/p&gt;
&lt;h4 id="zone-of-proximal-development"&gt;Zone of Proximal Development&lt;/h4&gt;
&lt;p&gt;You know how if something is too easy, it&amp;rsquo;s not engaging? It&amp;rsquo;s why you don&amp;rsquo;t choose Snap when you sit down to play cards. It&amp;rsquo;s so far below your skill level your brain is not interested in it. There&amp;rsquo;s a similar problem at the other end - if I ask you do try something that&amp;rsquo;s so hard for you that you&amp;rsquo;ll never be able to achieve it, you want want to do it again. For good learning to take place, it&amp;rsquo;s important to pitch the difficulty of activities just ever so slightly in advance of what the student can comfortably do. This is the zone where the most learning takes place in the shortest amount of time.&lt;/p&gt;
&lt;h4 id="learning-in-context"&gt;Learning in Context&lt;/h4&gt;
&lt;p&gt;Logically, you could teach iOS development with a semester of pure Swift teaching before you got to your first app. Probably there are courses that do that, but if you want to engage your learners do it with as much real life context and hands-on activity as possible. #100Days is all about this.&lt;/p&gt;
&lt;h4 id="beginner-mind"&gt;Beginner Mind&lt;/h4&gt;
&lt;p&gt;In the videos/lessons, Paul often anticipates how the learner might expect something to work, or how they might tackle a problem, before explaining the problem with that thinking or showing a better way of doing things. This is a great trait of a teacher. Often it&amp;rsquo;s difficult for experts (which Paul undoubtedly is) to recall how things looked to them as they were learning. Anticipating the state of mind of the learner, and moving them from that point is both comforting for the learner, and avoids confusion.&lt;/p&gt;
&lt;h4 id="currency"&gt;Currency&lt;/h4&gt;
&lt;p&gt;As soon as you start googling problems and reading blog posts or StackOverflow answers, it becomes apparent that the rapid development of Swift and SwiftUI has a downside - a lot of the helpful information put out there is out of date. Like everyone, I&amp;rsquo;m amazed at the work Paul puts in to producing his massive amount of content, and then keeping it up to date. If there&amp;rsquo;s a Hacking With Swift result in a search you&amp;rsquo;ve made, that&amp;rsquo;s the one to click on.&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>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>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>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>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>Ranges</title><link>https://blog.iankulin.com/ranges/</link><pubDate>Sun, 25 Sep 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/ranges/</guid><description>&lt;p&gt;I wondered aloud, in a &lt;a href="https://blog.iankulin.com/project-4-challenges/"&gt;previous post&lt;/a&gt;, about the differences in writing a range as&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; ForEach(1..&amp;lt;21) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(String($0))
&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;versus&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; ForEach(1...20) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(String($0))
&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;And that&amp;rsquo;s been answered in in one of the Day 34 articles. It sounds like older versions of Swift might not have allowed the second version.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.hackingwithswift.com/guide/ios-swiftui/3/2/key-points"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-18-at-6.51.32-pm.jpg" alt=""&gt;&lt;/a&gt;&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>Animation Feedback</title><link>https://blog.iankulin.com/animation-feedback/</link><pubDate>Sat, 24 Sep 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/animation-feedback/</guid><description>&lt;p&gt;I had a look at Paul&amp;rsquo;s version of the challenge to animate the Guess The Flags app, and like me he&amp;rsquo;d hit on altering the &lt;em&gt;amount&lt;/em&gt; of each animation depending on if it was the clicked on flag, but he&amp;rsquo;d done it with a lot less code - using the ternary operator in each of the modifiers - versus my approach of filling in an Int array for each of the effects and altering them depending on the user selection.&lt;/p&gt;
&lt;p&gt;Another round to Paul!&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><item><title>Project 4 Challenges</title><link>https://blog.iankulin.com/project-4-challenges/</link><pubDate>Fri, 16 Sep 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/project-4-challenges/</guid><description>&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2022-09-13-at-7.22.43-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-13-at-7.22.43-pm.png" width="197" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve completed the Project 4 challenges (source) of the 100 Days of SwiftUI, no biggie - the increase in difficulty between each step of Paul&amp;rsquo;s bootcamp is small enough that it&amp;rsquo;s never too stressful, but large enough you feel like you&amp;rsquo;re progressing all the time.&lt;/p&gt;
&lt;p&gt;Since I&amp;rsquo;ve paid to be a member of Hacking with Swift, one of the perks is to see Paul&amp;rsquo;s video solutions. I&amp;rsquo;ve not worries about it before, but I should - looking at them and comparing to my efforts is probably good feedback. So here&amp;rsquo;s the differences in our answers to the challenges.&lt;/p&gt;
&lt;h4 id="sections"&gt;Sections&lt;/h4&gt;
&lt;p&gt;When I changed the VStacks to Sections, I put the section title text at the top:&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(header: Text(&amp;#34;When do you want to wake up?&amp;#34;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HStack {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; DatePicker(&amp;#34;Please enter a time&amp;#34;, selection: $wakeUp, 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; displayedComponents: .hourAndMinute).labelsHidden()
&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;p&gt;Paul had his in a trailing closure at the bottom:&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; DatePicker(&amp;#34;Please enter a time&amp;#34;, selection: $wakeUp, 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; displayedComponents: .hourAndMinute).labelsHidden()
&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;header: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(&amp;#34;When do you want to wake up?&amp;#34;)
&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 thought mine was nicer, but then the very next thing he shows in the video 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-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Section(&amp;#34;When do you want to wake up?&amp;#34;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; DatePicker(&amp;#34;Please enter a time&amp;#34;, selection: $wakeUp, 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; displayedComponents: .hourAndMinute).labelsHidden()
&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 there - I have learned something for an expert&amp;hellip;&lt;/p&gt;
&lt;p&gt;The next job was to change the stepper to a picker for the number of cups of coffee. I had 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-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Section(header: Text(&amp;#34;Daily coffee intake?&amp;#34;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Picker(coffeeAmount == 1 ? &amp;#34;1 cup&amp;#34; : &amp;#34;\(coffeeAmount) cups&amp;#34;, 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; selection: $coffeeAmount) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ForEach(1...20, id: \.self) { i in
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if i == 1 {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(&amp;#34;1 cup&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } else {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(&amp;#34;\(i) cups&amp;#34;)
&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&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;And Paul:&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(&amp;#34;Daily coffee intake?&amp;#34;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Picker(&amp;#34;Number of cups&amp;#34;, selection: $coffeeAmount) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ForEach(1..&amp;lt;21) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(String($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; }
&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 yeah, then when I looked at my output properly (see simulator image above) I noticed mine didn&amp;rsquo;t make sense anyway. My bad - that&amp;rsquo;s a horrid careless error.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not sure about the difference in the ranges 1&amp;hellip;20 and 1..&amp;lt;21 the ..&amp;lt; is a great habit for avoiding off by one errors with collections, perhaps that&amp;rsquo;s the reason for Paul&amp;rsquo;s choice there.&lt;/p&gt;
&lt;p&gt;The third thing to do was to get rid of the alert, and show the result live as changes were made. I did mine by by adding a text field whose contents were a call to a slightly modified calculateBedtime() function. Paul moved that code up to a new computed property. I&amp;rsquo;m not sure I see any difference there except style.&lt;/p&gt;
&lt;p&gt;So, that&amp;rsquo;s a worthwhile thing to do - to look at Paul&amp;rsquo;s solution and compare it to mine, so I&amp;rsquo;ll go on and do that in future.&lt;/p&gt;</description></item><item><title>Machine Learning</title><link>https://blog.iankulin.com/machine-learning/</link><pubDate>Thu, 15 Sep 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/machine-learning/</guid><description>&lt;p&gt;A few years ago when I still used a Tom-Tom for car navigation, I was a little freaked out when it started offering suggestions on where to go to when I started the car - guessing, usually correctly, where I wanted to go. Like - how did it know I was leaving school for band practice two towns over?&lt;/p&gt;
&lt;p&gt;Clearly, is must have been collecting data on times/days and departure locations to learn some of my habits. It felt quite invasive, but I thought it must have been on-device since I had the wifi turned off in the unit.&lt;/p&gt;
&lt;p&gt;In &lt;a href="https://www.hackingwithswift.com/100/swiftui/27"&gt;Day 27&lt;/a&gt; of 100 Days of Swift UI we CoreML and use a dataset to train a model, then incorporate it in an App. The most shocking thing about it was how straightforward it was. In the example, the model was trained in CreateML from XCode and only used on the device, rather than trained on it, but current iPhones have the power to do that.&lt;/p&gt;
&lt;p&gt;Obviously there&amp;rsquo;s some amazing things that can be done with machine learning, but I&amp;rsquo;m actually more excited about the small things - perhaps just offering better defaults for user inputs.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a great intro to machine learning for Apple developers &lt;a href="https://developer.apple.com/machine-learning/"&gt;here&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Rock, Paper Scissors (2)</title><link>https://blog.iankulin.com/rock-paper-scissors-2/</link><pubDate>Tue, 13 Sep 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/rock-paper-scissors-2/</guid><description>&lt;p&gt;When I was forced by a deadline into delivering this project, I noted in its &lt;a href="https://blog.iankulin.com/rock-paper-scissors-1/"&gt;post&lt;/a&gt; that there was a number of improvements to make:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;The rock paper scissors could be some better data structure than an array and some ints.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;I don’t love the try to win, try to lose aspect, but the client specified it&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Having the didUserWin and didComputerWin funcs is a cop out – that should probably be a single function returning a win/lose/draw type&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;I also am unhappy with nesting them in the view namespace to use my #consts&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;duplicating the last part of the view but making the elements .hidden() to keep the same spacing seems like a kludge&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;when I added the link to the Hacking With SwiftUI page with the app brief just now, I noticed I haven’t done the scoring the way it was asked for&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here&amp;rsquo;s the progress&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The rock paper scissors could be some better data structure than an array and some ints&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Done. Made a sweet swifty enum. Read about it here. That also solved #4&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I don’t love the try to win, try to lose aspect, but the client specified it&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We can&amp;rsquo;t always help what a client wants. Deal with it.&lt;/p&gt;
&lt;p&gt;_Having the didUserWin and didComputerWin funcs is a cop out – that should probably be a single function returning a win/lose/dra_w&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; GameResult &lt;span style="color:#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; win
&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; loss
&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; draw
&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; gameResult&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;user&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; FingerShape&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; computer&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; FingerShape&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;-&amp;gt;&lt;/span&gt; GameResult &lt;span style="color:#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;switch&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 style="color:#81a1c1;font-weight:bold"&gt;case&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;rock&lt;span style="color:#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;switch&lt;/span&gt; computer &lt;span style="color:#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; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;rock&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;draw
&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; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;paper&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;loss
&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; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;scissors&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;win
&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;case&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;paper&lt;span style="color:#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;switch&lt;/span&gt; computer &lt;span style="color:#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; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;rock&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;win
&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; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;paper&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;draw
&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; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;scissors&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;loss
&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;case&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;scissors&lt;span style="color:#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;switch&lt;/span&gt; computer &lt;span style="color:#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; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;rock&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;loss
&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; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;paper&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;win
&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; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;scissors&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;draw
&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;&lt;em&gt;duplicating the last part of the view but making the elements .hidden() to keep the same spacing seems like a kludge&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This was an issue - not just because of the mess of code, but because (according to Paul) it&amp;rsquo;s a workload issue - the view managing part of SwiftUI has to keep creating and destroying parts of our view instead of recycling them. In an earlier lesson, he encouraged the use of the ternary operator in modifiers to avoid this - but .hidden() doesn&amp;rsquo;t have any arguments. Here&amp;rsquo;s the sort of code I&amp;rsquo;m talking about - I&amp;rsquo;ve got this sort of thing several places.&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 revealResult {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(computerSelection.rawValue)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .font(.system(size: 200))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(winText).font(.title)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Spacer()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button(&amp;#34;Play again&amp;#34;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; goalIsWinThisTurn = Bool.random()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; revealResult = false
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if showScores {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; score = 0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; numberOfPlays = 0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; showScores.toggle()
&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; .buttonStyle(CustomButtonStyle())
&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;else {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(computerSelection.rawValue)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .font(.system(size: 200))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .hidden()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(winText)
&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; .hidden()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Spacer()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button(&amp;#34;Play again&amp;#34;) {}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .buttonStyle(CustomButtonStyle())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .hidden()
&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 was surprised when googling something like &amp;ldquo;swiftui view visibility not hidden()&amp;rdquo; I found, instead of a stack overflow question, a &lt;a href="https://developer.apple.com/tutorials/swiftui-concepts/choosing-the-right-way-to-hide-a-view?changes=_3"&gt;nice Apple tutorial&lt;/a&gt; at the top of the links.&lt;/p&gt;
&lt;p&gt;Within was my answer - most views have an .opacity() modifier. With that, the code above becomes:&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;Group {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(computerSelection.rawValue)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .font(.system(size: 200))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(winText).font(.title)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Spacer()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Button(&amp;#34;Play again&amp;#34;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; goalIsWinThisTurn = Bool.random()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; revealResult = false
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if showScores {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; score = 0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; numberOfPlays = 0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; showScores.toggle()
&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; .buttonStyle(CustomButtonStyle())
&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;.opacity(revealResult ? 1 : 0)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Noice. I then when on a ternary insertion spree that dropped my view body down from 74 lines of code to 52 and improved readability substantially.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/IanKulin/RockPaper/blob/fe8e2eea247e9c1ab13daa3e39929100991f69c5/RockPaper/ContentView.swift"&gt;Current source&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Rock, Paper, Scissors (1)</title><link>https://blog.iankulin.com/rock-paper-scissors-1/</link><pubDate>Sat, 10 Sep 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/rock-paper-scissors-1/</guid><description>&lt;p&gt;As I mentioned yesterday, I needed to make some progress to blog about, and I had a half working version of a Rock, Paper, Scissors for &lt;a href="https://www.hackingwithswift.com/100/swiftui/25"&gt;Day 25&lt;/a&gt; so I pushed myself to get that working.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s lots in the code below I don&amp;rsquo;t love.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The rock paper scissors could be some better data structure than an array and some ints.&lt;/li&gt;
&lt;li&gt;I don&amp;rsquo;t love the try to win, try to lose aspect, but the client specified it&lt;/li&gt;
&lt;li&gt;Having the didUserWin and didComputerWin funcs is a cop out - that should probably be a single function returning a win/lose/draw type&lt;/li&gt;
&lt;li&gt;I also am unhappy with nesting them in the view namespace to use my #consts&lt;/li&gt;
&lt;li&gt;duplicating the last part of the view but making the elements .hidden() to keep the same spacing seems like a kludge&lt;/li&gt;
&lt;li&gt;when I added the link to the Hacking With SwiftUI page with the app brief just now, I noticed I haven&amp;rsquo;t done the scoring the way it was asked for&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://github.com/IanKulin/RockPaper/commit/b1497cccf2dc8af953b946458af797dc5ad12dc9?diff=unified#diff-223dd39ecc4f631b084c99b065a71ea40dc2deba8e36e7f5f939802e60c80186"&gt;source on github&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;struct ContentView: View {
@State var score = 0
@State var goalIsWinThisTurn = Bool.random()
@State var userSelection = 0
@State var computerSelection = 0
@State var winText = &amp;quot;&amp;quot;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// game has two modes - revealResult - buttons don't work, and we can see the computer result
// or not readyToPlay - user can chose their play
@State var revealResult = false

let rock = 0
let paper = 1
let scissors = 2
let rpsEmoji = \[&amp;quot;🪨&amp;quot;, &amp;quot;📃&amp;quot;, &amp;quot;✂️&amp;quot;\]

 
var body: some View {
 ZStack{
 LinearGradient(colors: \[.white, .gray\], startPoint: .top, endPoint: .bottom)
 VStack{
 Button(&amp;quot;Score: \\(score)&amp;quot;){
 score = 0
 }
 .font(.title)
 .foregroundColor(.primary)
 
 Spacer()
 if goalIsWinThisTurn {
 Text(&amp;quot;Try to win&amp;quot;)
 .font(.title)
 }
 else {
 Text(&amp;quot;Try to lose&amp;quot;)
 .font(.title)
 }
 
 Spacer()
 HStack{
 Spacer()
 Button(rpsEmoji\[rock\]) {
 processButton(selection: rock)
 }
 Spacer()
 Button(rpsEmoji\[paper\]) {
 processButton(selection: paper)
 }
 Spacer()
 Button(rpsEmoji\[scissors\]){
 processButton(selection: scissors)
 }
 Spacer()
 }.font(.system(size: 60))
 
 Spacer()
 if revealResult {
 Text(rpsEmoji\[computerSelection\])
 .font(.system(size: 200))
 Text(winText).font(.title)
 Spacer()
 Button(&amp;quot;Play again&amp;quot;) {
 goalIsWinThisTurn = Bool.random()
 revealResult = false
 }
 .buttonStyle(CustomButtonStyle())
 }
 else {
 Text(rpsEmoji\[computerSelection\])
 .font(.system(size: 200))
 .hidden()
 Text(winText)
 .font(.title)
 .hidden()
 Spacer()
 Button(&amp;quot;Play again&amp;quot;) {
 goalIsWinThisTurn = Bool.random()
 revealResult = false
 }
 .buttonStyle(CustomButtonStyle())
 .hidden()
 }
 Spacer()
 }
 }
}

func didUserWin(user: Int, computer: Int) -&amp;gt; Bool {
 switch(user) {
 case rock:
 switch(computer) {
 case scissors: return true
 default: return false
 }
 case paper:
 switch(computer) {
 case rock: return true
 default: return false
 }
 case scissors:
 switch(computer) {
 case paper: return true
 default: return false
 }
 default:
 assert(false)
 return false
 }
}

func didComputerWin(user: Int, computer: Int) -&amp;gt; Bool {
 switch(computer) {
 case rock:
 switch(user) {
 case scissors: return true
 default: return false
 }
 case paper:
 switch(user) {
 case rock: return true
 default: return false
 }
 case scissors:
 switch(user) {
 case paper: return true
 default: return false
 }
 default:
 assert(false)
 return false
 }
}

func processButton(selection: Int) {
 if !revealResult {
 computerSelection = Int.random(in: 0...2)
 userSelection = selection
 if didUserWin(user: userSelection, computer: computerSelection) {
 winText = &amp;quot;You win&amp;quot;
 if goalIsWinThisTurn {
 score += 1
 winText = &amp;quot;You win&amp;quot;
 } else {
 score -= 1
 winText = &amp;quot;You win, sorry&amp;quot;
 }
 } else if didComputerWin(user: userSelection, computer: computerSelection) {
 if goalIsWinThisTurn {
 score -= 1
 winText = &amp;quot;You lose, sorry&amp;quot;
 } else {
 score += 1
 winText = &amp;quot;You lose!&amp;quot;
 }
 } else {
 winText = &amp;quot;Draw&amp;quot;
 }
 revealResult = true
 }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;struct CustomButtonStyle : ButtonStyle {
func makeBody(configuration: Configuration) -&amp;gt; some View {
configuration.label
.font(.title)
.padding(10)
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(5)
}
}&lt;/p&gt;</description></item><item><title>.self in ForEach</title><link>https://blog.iankulin.com/self-in-foreach/</link><pubDate>Wed, 07 Sep 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/self-in-foreach/</guid><description>&lt;p&gt;I&amp;rsquo;m on Day 25 of Hacking With SwiftUI, and &lt;a href="https://www.hackingwithswift.com/guide/ios-swiftui/2/2/key-points"&gt;Paul is making a point&lt;/a&gt; about how SwiftUI can loop over an array to build a view. He starts 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-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;let agents = [&amp;#34;Cyril&amp;#34;, &amp;#34;Lana&amp;#34;, &amp;#34;Pam&amp;#34;, &amp;#34;Sterling&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;VStack {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ForEach(0..&amp;lt;agents.count) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text(agents[$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;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But then proposes an alternative:&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 agents = [&amp;#34;Cyril&amp;#34;, &amp;#34;Lana&amp;#34;, &amp;#34;Pam&amp;#34;, &amp;#34;Sterling&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;VStack {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ForEach(agents, id: \.self) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text($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;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;He explains the use of \.self here by saying&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;So, we come back to how Swift can identify values in our array. When we were using a range such as &lt;code&gt;0..&amp;lt;5&lt;/code&gt; or &lt;code&gt;0..&amp;lt;agents.count&lt;/code&gt;, Swift knew for sure that each item was unique because it would use the numbers from the range – each number was used only once in the loop, so it would definitely be unique.&lt;/p&gt;
&lt;p&gt;In our array of strings that’s no longer possible, but we can clearly see that each value is unique: the values in &lt;code&gt;[&amp;quot;Cyril&amp;quot;, &amp;quot;Lana&amp;quot;, &amp;quot;Pam&amp;quot;, &amp;quot;Sterling&amp;quot;]&lt;/code&gt; don’t repeat. So, what we can do is tell SwiftUI that the strings themselves – “Cyril”, “Lana”, etc – are what can be used to identify each view in the loop uniquely.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&amp;rsquo;m having a couple of problems with this.&lt;/p&gt;
&lt;h4 id="grumble-one"&gt;Grumble One&lt;/h4&gt;
&lt;p&gt;The first is that Swift can&amp;rsquo;t really use the &amp;ldquo;strings themselves&amp;rdquo;. So this doesn&amp;rsquo;t work:&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;ForEach(agents, String($0)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text($0)
&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;It complains that there&amp;rsquo;s no initialiser to take those arguments. So \.self must be something fancier. This is something I can presumably investigate by looking at the initialisers for ForEach.&lt;/p&gt;
&lt;h4 id="grumble-two"&gt;Grumble Two&lt;/h4&gt;
&lt;p&gt;And the second grumble is that the first formulation &lt;code&gt;ForEach(0..&amp;lt;agents.count)&lt;/code&gt; throws a compiler warning &amp;ldquo;&lt;code&gt;Non-constant range: argument must be an integer literal&lt;/code&gt;&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-03-at-11.44.26-am.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Which is true, if I swap it for the number &amp;lsquo;4&amp;rsquo; it stops complaining. I guess in the initialiser for ForEach there&amp;rsquo;s something specifying this although it&amp;rsquo;s not clear to me why. Again it&amp;rsquo;s an initialiser mystery because I can make the warning go away by adding the id reference to self.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-03-at-2.06.48-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Googling for the warning finds a &lt;a href="https://www.hackingwithswift.com/forums/swiftui/compiler-warning-non-constant-range-argument-must-be-an-integer-literal/14878"&gt;page on the Hacking with Swift forums&lt;/a&gt; that throws a little bit of light on the issue by looking at the inits.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-03-at-2.13.44-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The difference between these two that we&amp;rsquo;re interested in is that in the first, the type is &lt;code&gt;Range&lt;/code&gt;, and the second &lt;code&gt;Data&lt;/code&gt;. &lt;a href="https://www.hackingwithswift.com/users/roosterboy"&gt;@RoosterBoy&lt;/a&gt;&amp;rsquo;s explanation for why we should use literal ints for the range is that it&amp;rsquo;s to be safe from the array changing size during the loop - which is slightly unsatisfying because that could still happen with the literal int range.&lt;/p&gt;
&lt;h4 id="grumble-one-again"&gt;Grumble One again&lt;/h4&gt;
&lt;p&gt;These initialisers also shed some light on my first problem, the references used by SwiftUI should be KeyPaths. &lt;a href="https://sarunw.com/posts/what-is-keypath-in-swift/"&gt;Sarun&amp;rsquo;s explanation&lt;/a&gt; of them makes them sound like actual references - ie memory addresses for the properties. That would make sense since SwiftUi wants them to keep track of things.&lt;/p&gt;
&lt;p&gt;So I tried 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;ForEach(agents, id: \String.$0) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text($0)
&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;but the compiler is still unhappy - so clearly I am not knowledgeable enough yet to solve this one.&lt;/p&gt;</description></item><item><title>Project 3</title><link>https://blog.iankulin.com/project-3/</link><pubDate>Mon, 05 Sep 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/project-3/</guid><description>&lt;p&gt;This one&amp;rsquo;s not really a project, just a couple of little updates to earlier work, and a code snippet.&lt;/p&gt;
&lt;h4 id="challenge-1"&gt;Challenge 1&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Go back to project 1 and use a conditional modifier to change the total amount text view to red if the user selects a 0% tip.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The first one is pretty simple - a ternary condition to make the total red if the tip is set to zero.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-09-02-at-5.10.56-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Text(grandTotal, format: currencyCode)
.foregroundColor(tipPercentage == 0 ? .red : .primary)&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;ternary operator&lt;/em&gt; is like a little inline if then else statement. It has the format:&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;(w ? t : f)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;where w is the condition, t is the code if the condition is true and f is the code if the condition is false. So the code above checks if the tipPercentage is zero, if it is, the grandTotal text is red, otherwise it&amp;rsquo;s coloured &lt;em&gt;.primary&lt;/em&gt; - one of the semantic colors. The semantic colours are colours set by the system and referred to by their purpose. In this case it will be black (the &amp;ldquo;primary&amp;rdquo; text colour), unless I change the theme to dark mode, in which case it will be white.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/IanKulin/WeSplit/compare/v1.0...v1.1"&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="challenge-2"&gt;Challenge 2&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Go back to project 2 and replace the&lt;/em&gt; &lt;code&gt;Image&lt;/code&gt; &lt;em&gt;view used for flags with a new&lt;/em&gt; &lt;code&gt;FlagImage&lt;/code&gt;&lt;em&gt;&lt;code&gt;()&lt;/code&gt; view that renders one flag image using the specific set of modifiers we had.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Again, pretty straightforward. I just made a new view struct in the main view.&lt;/p&gt;
&lt;p&gt;struct FlagView: View {
var flagOf: String&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; var body: some View {
 Image(flagOf)
 .renderingMode(.original)
 .shadow(radius: 5)
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;Then called it with our flag name.&lt;/p&gt;
&lt;p&gt;ForEach(0..&amp;lt;3) { number in
Button {
// flag was tapped
flagTapped(number)
} label: {
FlagView(flagOf: countries[number])
}
}&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/IanKulin/GuessTheFlag/compare/v1.0...v1.1"&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;Create a custom&lt;/em&gt; &lt;code&gt;ViewModifier&lt;/code&gt; &lt;em&gt;(and accompanying&lt;/em&gt; &lt;code&gt;View&lt;/code&gt; &lt;em&gt;extension) that makes a view have a large, blue font suitable for prominent titles in a view.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;struct ContentView: View {
var body: some View {
VStack{
Text(&amp;ldquo;Hello World&amp;rdquo;)
.titleStyle()
Spacer()
}
}
}&lt;/p&gt;
&lt;p&gt;struct ProminentTitle: ViewModifier {
func body(content: Content) -&amp;gt; some View {
content
.font(.largeTitle)
.foregroundColor(.blue)
.padding()
}
}&lt;/p&gt;
&lt;p&gt;extension View {
func titleStyle() -&amp;gt; some View {
modifier(ProminentTitle())
}
}&lt;/p&gt;</description></item><item><title>Day 23 - Views and Modifiers - Part 4</title><link>https://blog.iankulin.com/day-23-views-and-modifiers-part-4/</link><pubDate>Sat, 03 Sep 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/day-23-views-and-modifiers-part-4/</guid><description>&lt;img src="https://blog.iankulin.com/images/psm_v10_d562_the_hindoo_earth-3.jpg" width="417" alt="This image has an empty alt attribute; its file name is psm\_v10\_d562\_the\_hindoo\_earth-3.jpg"&gt;
&lt;p&gt;Then the last trick for for decomposing the views, is to remember we can pass values when we init a struct. So something like this:&lt;/p&gt;
&lt;p&gt;struct ContentView: View {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var body: some View {
 VStack{
 GreenPaddedText(text: &amp;quot;Hello&amp;quot;)
 GreenPaddedText(text: &amp;quot;world&amp;quot;)
 }
}


struct GreenPaddedText: View {
 var text: String

 var body: some View {
 Text(text)
 .foregroundColor(.green)
 .padding()
 }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;This is probably my favourite - because although in this example I&amp;rsquo;ve created the mini-view struct in the body, if it&amp;rsquo;s a building block I can use elsewhere in a different view, it&amp;rsquo;s super portable.&lt;/p&gt;</description></item><item><title>Day 23 - Views and Modifiers - Part 3</title><link>https://blog.iankulin.com/day-23-views-and-modifiers-part-3/</link><pubDate>Fri, 02 Sep 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/day-23-views-and-modifiers-part-3/</guid><description>&lt;img src="https://blog.iankulin.com/images/psm_v10_d562_the_hindoo_earth-3.jpg" width="472" alt=""&gt;
&lt;p&gt;The next part of day 23 started to make my brain hurt a bit. It&amp;rsquo;s easy to imagine that when presenting a complex screen - perhaps some data from a source as a mixture of images and text loaded from a database into a scroll-able view, that the view may start to get complex. Then it becomes good practice to decompose the views to make the code clearer, less error prone, and to avoid any unnecessary repetition.&lt;/p&gt;
&lt;p&gt;Paul&amp;rsquo;s first suggestion is to pull some parts of the view as &lt;em&gt;properties&lt;/em&gt; of the same view.&lt;/p&gt;
&lt;p&gt;struct ContentView: View {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let greenText = Text(&amp;quot;Hello&amp;quot;).foregroundColor(.green)

var body: some View {
 VStack{
 greenText
 greenText
 }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;This works fine, and exactly how you expect, except that if you don&amp;rsquo;t enclose it in the VStack, you just get one Text, but two ContentPreviews. I do not understand why yet, but its probably something to do with the @ViewBuilder property wrapper.&lt;/p&gt;
&lt;p&gt;But&amp;hellip; a property can&amp;rsquo;t refer to another property, so this isn&amp;rsquo;t compilable Swift:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-08-31-at-8.24.46-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;To get around this, we can use a computed property. So this works:&lt;/p&gt;
&lt;p&gt;struct ContentView: View {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@State private var greeting = &amp;quot;Hello&amp;quot;

var greenText: some View {
 Text(greeting).foregroundColor(.green)
}

var body: some View {
 VStack{
 greenText
 greenText
 }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;In fact, this is what I&amp;rsquo;ve been doing so far to decompose views, although I&amp;rsquo;ve been dropping the segments underneath the main body to make things subjectively neater.&lt;/p&gt;
&lt;p&gt;Paul cautions about returning multiple views in this computed property manner. So this does not work:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-08-31-at-8.33.20-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;That makes sense - we were relying on the implied return. Putting them in a VStack would work - because we&amp;rsquo;re just returning a single view (the VStack) which happens to contain multiple views.&lt;/p&gt;
&lt;p&gt;struct ContentView: View {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@State private var greeting = &amp;quot;Hello&amp;quot;

var body: some View {
 VStack{
 greenText
 greenText
 }
}

var greenText: some View {
 VStack{
 Text(greeting).foregroundColor(.green)
 Text(greeting).foregroundColor(.green)
 }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s another view container type called Group which is like a stack, but just contains, rather than arranging, a collection of views, that can be used in the same way.&lt;/p&gt;
&lt;p&gt;Alternatively, and I assume this is related to the problem I had above, we can just wrap the property with the @ViewBuilder attribute.&lt;/p&gt;
&lt;p&gt;struct ContentView: View {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@State private var greeting = &amp;quot;Hello&amp;quot;

var body: some View {
 VStack{
 greenText
 greenText
 }
}

@ViewBuilder var greenText: some View {
 Text(greeting).foregroundColor(.green)
 Text(greeting).foregroundColor(.green)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;</description></item><item><title>Day 23 - Views and Modifiers - Part 2</title><link>https://blog.iankulin.com/day-23-views-and-modifiers-part-2/</link><pubDate>Thu, 01 Sep 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/day-23-views-and-modifiers-part-2/</guid><description>&lt;img src="https://blog.iankulin.com/images/psm_v10_d562_the_hindoo_earth-2.jpg" width="484" alt=""&gt;
&lt;p&gt;Although &amp;ldquo;immutable&amp;rdquo; the view structs can contain some control statements such as if/then and for loops. So this is quite legal, and useful.&lt;/p&gt;
&lt;p&gt;struct ContentView: View {
@State private var likesGreen = true&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var body: some View {
 if likesGreen {
 Text(&amp;quot;Hello World&amp;quot;)
 .background(.green)
 }
 else
 {
 Text(&amp;quot;Hello World&amp;quot;)
 .background(.blue)
 }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;But Paul cautions against this, saying:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://www.hackingwithswift.com/books/ios-swiftui/conditional-modifiers"&gt;You can often use regular if conditions to return different views based on some state, but this actually creates more work for SwiftUI – rather than seeing one Button being used with different colors, it now sees two different Button views, and when we flip the Boolean condition it will destroy one to create the other rather than just recolor what it has.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Instead, he encourages the use of the ternary operator in modifiers for these situations. The ternary operator is like an if/then/else statement packaged up neatly. So the code above becomes:&lt;/p&gt;
&lt;p&gt;struct ContentView: View {
@State private var likesGreen = true&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var body: some View {
 Text(&amp;quot;Hello World&amp;quot;)
 .background(likesGreen ? .green : .blue)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;He&amp;rsquo;s not arguing that it&amp;rsquo;s neater, but also that it&amp;rsquo;s more efficient - that the first version has to destroy and create a Text when the value of the flag changes, whereas the second one just changes the colour. I assume he&amp;rsquo;s correct, but it&amp;rsquo;s not obvious why that would be so. It must be in the magic of how SwiftUI optimises how and when it renders each part of a view.&lt;/p&gt;</description></item><item><title>Day 23 - Views and Modifiers - Part 1</title><link>https://blog.iankulin.com/day-23-views-and-modifiers-part-1/</link><pubDate>Wed, 31 Aug 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/day-23-views-and-modifiers-part-1/</guid><description>&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/psm_v10_d562_the_hindoo_earth-1.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I found this one of the trickier days, so I&amp;rsquo;ll write it out to clear up my thinking.&lt;/p&gt;
&lt;p&gt;To draw to to screen in SwiftUI, we don&amp;rsquo;t call a command to draw on a canvas or window. Rather, a &lt;em&gt;view&lt;/em&gt; is defined as an immutable struct of type some View. Here&amp;rsquo;s the simple one from the default Xcode project.&lt;/p&gt;
&lt;p&gt;struct ContentView: View {
var body: some View {
Text(&amp;ldquo;Hello, world!&amp;rdquo;)
.padding()
}
}&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;body&lt;/em&gt; var must be present, and can contain up to ten views. This simple example only contains one view - a text box. There are container view types that can, well, contain other views - for example a &lt;em&gt;HStack&lt;/em&gt; which arranges its content horizontally.&lt;/p&gt;
&lt;p&gt;struct ContentView: View {
var body: some View {
HStack {
Text(&amp;ldquo;Hello, world!&amp;rdquo;)
.padding()
Rectangle()
.frame(width: 100, height: 100, alignment: .center)
}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;produces:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2022-08-31-at-7.20.54-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-08-31-at-7.20.54-pm.png" width="164" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Having defined our view struct, we don&amp;rsquo;t call for it to be rendered on the screen, SwiftUI is just going to do that for us whenever it thinks it needs to. This seems bizarre at first, but you get used to it. It will happen when it needs to - usually because the information that makes up the view has changed. We help that to happen by binding the views to their data in various ways. More on that another day.&lt;/p&gt;
&lt;p&gt;If you look back at the code above, you&amp;rsquo;ll see that views often have &lt;em&gt;modifiers&lt;/em&gt; attached to them. In our example the &lt;em&gt;padding()&lt;/em&gt; on the text and the &lt;em&gt;.frame()&lt;/em&gt; on the rectangle. There&amp;rsquo;s many different modifiers for all of the different view primitives. Many of them are common to different primitives, some are different. It&amp;rsquo;s possible to attach them to container views - in which case they are applied to all the views in the container. For example, if we move the .frame() to the HStack, like this:&lt;/p&gt;
&lt;p&gt;struct ContentView: View {
var body: some View {
HStack {
Text(&amp;ldquo;Hello, world!&amp;rdquo;)
.padding()
Rectangle()
}
.frame(width: 100, height: 100, alignment: .center)
}
}&lt;/p&gt;
&lt;p&gt;We get&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2022-08-31-at-7.33.07-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-08-31-at-7.33.07-pm.png" width="188" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The order of the modifiers is important. You can think of each modifier that&amp;rsquo;s added as wrapping around the view and any previous modifiers. In this example, we&amp;rsquo;ve got the same text field with the &lt;em&gt;.padding()&lt;/em&gt; and &lt;em&gt;.background(.blue)&lt;/em&gt; modifiers. The left one has the padding first, then is wrapped in the blue background. The right one has the blue background applied first, then the padding.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2022-08-31-at-7.39.53-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-08-31-at-7.39.53-pm.png" width="192" alt=""&gt;&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Project 2 - Guess the Flag</title><link>https://blog.iankulin.com/project-2-guess-the-flag/</link><pubDate>Wed, 31 Aug 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/project-2-guess-the-flag/</guid><description>&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-08-23-at-8.26.59-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Another 100 Days project - the second tutorial one. This was once again a &amp;ldquo;V&amp;rdquo; design pattern (put everything in the view) and as I kept growing it, especially in the challenges, I had a growing sense of unease.&lt;/p&gt;
&lt;p&gt;New things for me was how image assets work - identifying them with strings is convenient, but I&amp;rsquo;m hoping there&amp;rsquo;s safer system later using enums or something to avoid runtime surprises. Also the alert dialog box system. I was wondering how this was going to work in a declarative framework. I do not really approve of modal dialogs in mobile UI&amp;rsquo;s but I guess they have their place. I appreciated the gradients and frosted glass effects -super simple to implement, and if done thoughtfully, pretty.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/IanKulin/GuessTheFlag"&gt;Source/Github&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Challenge 1</title><link>https://blog.iankulin.com/challenge-1/</link><pubDate>Mon, 29 Aug 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/challenge-1/</guid><description>&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-08-22-at-8.54.26-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m up to Challenge 1 of 100 Days of SwiftUI (&lt;a href="https://www.hackingwithswift.com/100/swiftui/19"&gt;Day 19&lt;/a&gt;) which is to make your own simple (no MVVM) version of the app built in the previous three days. It&amp;rsquo;s about as simple as can be whilst still feeling like a real app. Something I hadn&amp;rsquo;t done before was limiting the keyboard to numbers or adding a toolbar to close it, so that was nice.&lt;/p&gt;
&lt;p&gt;Something that&amp;rsquo;s not nice, is that when you touch into the text field to change the number, it&amp;rsquo;s not selected ready to type over (the way they always are in browser url fields) so you need to backspace over the previous entry. That&amp;rsquo;s the sort of anoying behaviour I don&amp;rsquo;t like. It seems (after some googling) there&amp;rsquo;s no straightforward way of addressing this in SwiftUI, with the best solution involving importing a package. I will come back to that because it is bugging me.&lt;/p&gt;
&lt;p&gt;struct ContentView: View {
@State private var fromDistance = 0.0
@State private var fromUnits = &amp;ldquo;meters&amp;rdquo;
@State private var toUnits = &amp;ldquo;kilometers&amp;rdquo;
@FocusState private var distanceIsFocused: Bool&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let distanceUnits = \[&amp;quot;meters&amp;quot;, &amp;quot;kilometers&amp;quot;, &amp;quot;yards&amp;quot;, &amp;quot;miles&amp;quot;\]

var toDistance: Double {
 var meters = 0.0
 // convert the from distance to meters
 switch fromUnits {
 case &amp;quot;meters&amp;quot;: meters = fromDistance
 case &amp;quot;kilometers&amp;quot;: meters = fromDistance\*1000
 case &amp;quot;yards&amp;quot;: meters = fromDistance\*0.9144
 case &amp;quot;miles&amp;quot;: meters = fromDistance\*1609.34
 default: assert(false)
 }
 // convert the meters to the from distance
 switch toUnits {
 case &amp;quot;meters&amp;quot;: return meters
 case &amp;quot;kilometers&amp;quot;: return meters/1000
 case &amp;quot;yards&amp;quot;: return meters/0.9144
 case &amp;quot;miles&amp;quot;: return meters/1609.34
 default : assert(false)
 }
 return 0.0
}


var body: some View {
 NavigationView {
 Form {
 
 Section {
 TextField(&amp;quot;Distance&amp;quot;, value: $fromDistance, format: .number)
 .keyboardType(.decimalPad)
 .focused($distanceIsFocused)
 Picker(&amp;quot;Units&amp;quot;, selection: $fromUnits) {
 ForEach(distanceUnits, id: \\.self) {
 Text($0)
 }
 }
 .pickerStyle(.segmented)
 }
 header: {
 Text(&amp;quot;Distance&amp;quot;)
 }
 
 Section {
 if toDistance &amp;lt; 9.9 {
 Text(&amp;quot;\\(toDistance, specifier: &amp;quot;%.4f&amp;quot;)&amp;quot;)
 } else if toDistance &amp;gt; 999 {
 Text(&amp;quot;\\(toDistance, specifier: &amp;quot;%.0f&amp;quot;)&amp;quot;)
 } else {
 Text(&amp;quot;\\(toDistance, specifier: &amp;quot;%.2f&amp;quot;)&amp;quot;)
 }

 Picker(&amp;quot;Units&amp;quot;, selection: $toUnits) {
 ForEach(distanceUnits, id: \\.self) {
 Text($0)
 }
 }
 .pickerStyle(.segmented)
 }
 header: {
 Text(&amp;quot;Converted Distance&amp;quot;)
 }
 
 }
 .navigationTitle(&amp;quot;Distance Conversion&amp;quot;)
 .toolbar {
 ToolbarItemGroup(placement: .keyboard) {
 Spacer()
 Button(&amp;quot;Done&amp;quot;) {
 distanceIsFocused = false
 }
 }
 }
 }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/IanKulin/HSUnitConvert"&gt;Source on Github&lt;/a&gt;&lt;/p&gt;</description></item><item><title>$==Commitment</title><link>https://blog.iankulin.com/commitment/</link><pubDate>Sun, 28 Aug 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/commitment/</guid><description>&lt;p&gt;&lt;a href="https://www.hackingwithswift.com/100/swiftui"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-08-21-at-9.47.54-am.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Paul Hudson&amp;rsquo;s 100 Days of Swift UI course is free - the videos are on YouTube, all the reading and tasks are available on his website for free. Although I assumed he made his living from the prodigious number of Swift books he&amp;rsquo;s written, he doesn&amp;rsquo;t push them in the course (except for his free book &lt;a href="https://www.hackingwithswift.com/quick-start/swiftui"&gt;Swift UI by Example&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;He&amp;rsquo;s so unpushy, I didn&amp;rsquo;t realise till a few days ago that you could pay to become a &lt;a href="https://www.hackingwithswift.com/plus"&gt;&amp;ldquo;subscriber&amp;rdquo; to Hacking with Swift&lt;/a&gt;. Really, he&amp;rsquo;s too nice.&lt;/p&gt;
&lt;p&gt;There are some goodies that come with the + subscription (one I&amp;rsquo;m likely to use later is the book discount) , but really I was very happy to sign up for a year for a couple of reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Paul is a major contributor to the Swift community, and especially to beginners like me. The world is a better place if he can afford to keep doing that.&lt;/li&gt;
&lt;li&gt;This extra commitment, which is a tiny fraction of what I could spend on a bootcamp, makes it more likely I&amp;rsquo;ll complete the course, and puts a time limit on it - if I don&amp;rsquo;t complete it in the year, I&amp;rsquo;ll feel compelled to pay again.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Checkpoint 9</title><link>https://blog.iankulin.com/checkpoint-9/</link><pubDate>Sat, 20 Aug 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/checkpoint-9/</guid><description>&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/-JmAbcISEmY?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;/*
Your challenge is this: write a function that accepts an
optional array of integers, and returns one randomly.
If the array is missing or empty, return a random number
in the range 1 through 100.&lt;/p&gt;
&lt;p&gt;If that sounds easy, it’s because I haven’t explained
the catch yet: I want you to write your function in a
single line of code. No, that doesn’t mean you should
just write lots of code then remove all the line breaks
– you should be able to write this whole thing in one
line of code.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.hackingwithswift.com/quick-start/beginners/checkpoint-9"&gt;https://www.hackingwithswift.com/quick-start/beginners/checkpoint-9&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;*/&lt;/p&gt;
&lt;p&gt;func randomInt(_ numbers:[Int]?) -&amp;gt; Int { numbers?.randomElement() ?? Int.random(in:1&amp;hellip;100) }&lt;/p&gt;
&lt;p&gt;print(randomInt(nil))
print(randomInt([2,5]))&lt;/p&gt;</description></item><item><title>Checkpoint 8</title><link>https://blog.iankulin.com/checkpoint-8/</link><pubDate>Wed, 17 Aug 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/checkpoint-8/</guid><description>&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/Ga800-Qgft4?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;/*
Your challenge is this: make a protocol that describes a
building, adding various properties and methods, then
create two structs, House and Office, that conform to it.&lt;/p&gt;
&lt;p&gt;Your protocol should require the following:
A property storing how many rooms it has.
A property storing the cost as an integer
(e.g. 500,000 for a building costing $500,000.)
A property storing the name of the estate agent
responsible for selling the building.
A method for printing the sales summary of the building,
describing what it is along with its other properties.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.hackingwithswift.com/quick-start/beginners/checkpoint-8"&gt;https://www.hackingwithswift.com/quick-start/beginners/checkpoint-8&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;*/&lt;/p&gt;
&lt;p&gt;protocol Building {
var rooms: Int {get set}
var cost: Int {get set}
var realEstateAgent: String {get set}
}&lt;/p&gt;
&lt;p&gt;extension Building {
func printSalesSummary(){
print(&amp;ldquo;Rooms: \(rooms) Cost: £\(cost) Agent: \(realEstateAgent)&amp;rdquo;)
}
}&lt;/p&gt;
&lt;p&gt;struct House: Building {
var rooms: Int
var cost: Int
var realEstateAgent: String
var occupants: Int
}&lt;/p&gt;
&lt;p&gt;struct Office: Building {
var rooms: Int
var cost: Int
var realEstateAgent: String
var floorArea: Int
}&lt;/p&gt;
&lt;p&gt;var myOffice = Office(rooms: 5, cost:500_000, realEstateAgent: &amp;ldquo;Bloggs&amp;rdquo;, floorArea: 500)
var myHouse = House(rooms: 2, cost:200_000, realEstateAgent: &amp;ldquo;Fletchers&amp;rdquo;, occupants: 1)&lt;/p&gt;
&lt;p&gt;myOffice.printSalesSummary()
myHouse.printSalesSummary()&lt;/p&gt;</description></item><item><title>Checkpoint 7</title><link>https://blog.iankulin.com/checkpoint-7/</link><pubDate>Mon, 15 Aug 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/checkpoint-7/</guid><description>&lt;p&gt;&lt;a href="https://www.hackingwithswift.com/quick-start/beginners/checkpoint-7"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-08-08-at-8.43.44-pm.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;/*
Your challenge is this: make a class hierarchy
for animals, starting with Animal at the top,
then Dog and Cat as subclasses, then Corgi and
Poodle as subclasses of Dog, and Persian and Lion
as subclasses of Cat.&lt;/p&gt;
&lt;p&gt;But there’s more:
The Animal class should have a legs integer
property that tracks how many legs the animal has.
The Dog class should have a speak() method that
prints a generic dog barking string, but each of
the subclasses should print something slightly
different.
The Cat class should have a matching speak() method,
again with each subclass printing something
different.
The Cat class should have an isTame Boolean property,
provided using an initializer.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.hackingwithswift.com/quick-start/beginners/checkpoint-7"&gt;https://www.hackingwithswift.com/quick-start/beginners/checkpoint-7&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;*/&lt;/p&gt;
&lt;p&gt;class Animal {
var legs = 4
init(legs: Int) {
self.legs = legs
}
}&lt;/p&gt;
&lt;p&gt;class Dog: Animal {
func speak() { print(&amp;ldquo;woof&amp;rdquo;) }
}&lt;/p&gt;
&lt;p&gt;class Corgi: Dog {
override func speak() { print(&amp;ldquo;Your Majesty?&amp;rdquo;) }
}&lt;/p&gt;
&lt;p&gt;class Poodle: Dog {
override func speak() { print(&amp;ldquo;yip&amp;rdquo;) }
}&lt;/p&gt;
&lt;p&gt;class Cat: Animal {
var isTame: Bool&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;init (isTame: Bool, legs: Int) {
 self.isTame = isTame
 super.init(legs: legs)
}

func speak() { print(&amp;quot;meow&amp;quot;) }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;class Persian: Cat {
override func speak() { print(&amp;ldquo;hiss&amp;rdquo;) }&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;init() { super.init (isTame: true, legs: 4 ) }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;class Lion: Cat {
override func speak() { print(&amp;ldquo;rawr&amp;rdquo;) }&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;init() { super.init (isTame: false, legs: 4 ) }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;p&gt;let lion = Lion()
print(lion.legs)&lt;/p&gt;</description></item><item><title>Checkpoint 6</title><link>https://blog.iankulin.com/checkpoint-6/</link><pubDate>Sat, 23 Jul 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/checkpoint-6/</guid><description>&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;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;create a struct to store information about a car&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; including its model&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; number of seats&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;and&lt;/span&gt; current gear&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; then add a method to change gears up &lt;span style="color:#81a1c1;font-weight:bold"&gt;or&lt;/span&gt; down&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt; Have a think about variables &lt;span style="color:#81a1c1;font-weight:bold"&gt;and&lt;/span&gt; access control&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; what data should be a variable rather than a constant&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;and&lt;/span&gt; what data should be exposed publicly&lt;span style="color:#bf616a"&gt;?&lt;/span&gt; Should the gear&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;changing method validate its input somehow&lt;span style="color:#bf616a"&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;
&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;struct 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;static&lt;/span&gt; let maxGear &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;10&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;static&lt;/span&gt; let minGear &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;var&lt;/span&gt; model &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;no model&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;var&lt;/span&gt; seats &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; private &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;set&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;var&lt;/span&gt; currentGear &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; Car&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;minGear
&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; init &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;model&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; seats&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Int&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;self&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;model &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; model
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;self&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;seats &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; seats
&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; mutating &lt;span style="color:#81a1c1;font-weight:bold"&gt;func&lt;/span&gt; gearUp&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; currentGear &lt;span style="color:#81a1c1"&gt;&amp;lt;&lt;/span&gt; Car&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;maxGear&lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; currentGear &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 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; mutating &lt;span style="color:#81a1c1;font-weight:bold"&gt;func&lt;/span&gt; gearDown&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; currentGear &lt;span style="color:#81a1c1"&gt;&amp;gt;&lt;/span&gt; Car&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;minGear&lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; currentGear &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 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&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; myUte &lt;span style="color:#81a1c1"&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;Rodeo&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; seats&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;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;My \(myUte.model) has \(myUte.seats) seats and is in gear: \(myUte.currentGear)&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;myUte&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;gearDown&lt;span style="color:#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;My \(myUte.model) has \(myUte.seats) seats and is in gear: \(myUte.currentGear)&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;myUte&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;gearUp&lt;span style="color:#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;My \(myUte.model) has \(myUte.seats) seats and is in gear: \(myUte.currentGear)&amp;#34;&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>struct</title><link>https://blog.iankulin.com/struct/</link><pubDate>Mon, 18 Jul 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/struct/</guid><description>&lt;p&gt;Started on &lt;a href="https://www.hackingwithswift.com/100/swiftui/10"&gt;Day 10 of 100 days of etc etc&lt;/a&gt; today which is about structs. It was immediately clear when I first started looking at Swift and Swift UI that structs were going to be a big deal. I am used to structs being able to contain a collection of other types, but not methods. So I was confused at why tuples existed; that is now cleared up.&lt;/p&gt;
&lt;p&gt;If structs can have methods as well as properties, it answers the question of why tuples exist, but immediately asks the question, why have classes since structs have all this power? I already know (from my podcast consumption) one of the answers for this is that structs are value types rather than references. When you:&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 someConstant = someClass
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;someConstant now contains a copy of the pointer to someClass, as opposed to making a copy as similar code would do for a struct. So this code:&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 SomeStruct &lt;span style="color:#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; counter&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Int &lt;span style="color:#81a1c1"&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;var&lt;/span&gt; structInstance &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; SomeStruct&lt;span style="color:#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; someOtherStruct &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; structInstance
&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"&gt;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;structInstance:\(structInstance.counter)&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;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;someOtherStruct:\(someOtherStruct.counter)&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;someOtherStruct&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;counter &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"&gt;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;structInstance:\(structInstance.counter)&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;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;someOtherStruct:\(someOtherStruct.counter)&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:#81a1c1"&gt;print&lt;/span&gt;&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&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; SomeClass &lt;span style="color:#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; counter&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; Int &lt;span style="color:#81a1c1"&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;var&lt;/span&gt; classInstance &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; SomeClass&lt;span style="color:#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; someOtherClass &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; classInstance
&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"&gt;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;classInstance:\(classInstance.counter)&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;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;someOtherClass:\(someOtherClass.counter)&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;someOtherClass&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;counter &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"&gt;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;classInstance:\(classInstance.counter)&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;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;someOtherClass:\(someOtherClass.counter)&amp;#34;&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;Produces the output:&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;structInstance:0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;someOtherStruct:0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;structInstance:0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;someOtherStruct:1
&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;classInstance:0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;someOtherClass:0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;classInstance:1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;someOtherClass:1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The sample code for the Hello World app in playgrounds ONLY contains structs. The app is a struct, containing a view which is a struct. That&amp;rsquo;s basically all there is, so clearly structs are going to be a big deal.&lt;/p&gt;</description></item><item><title>Checkpoint 5</title><link>https://blog.iankulin.com/checkpoint-5/</link><pubDate>Sat, 16 Jul 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/checkpoint-5/</guid><description>&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;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Your input is this&lt;span 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; let luckyNumbers &lt;span style="color:#81a1c1"&gt;=&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 style="color:#b48ead"&gt;4&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#b48ead"&gt;38&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#b48ead"&gt;21&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#b48ead"&gt;16&lt;/span&gt;&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 style="color:#b48ead"&gt;12&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#b48ead"&gt;33&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#b48ead"&gt;31&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#b48ead"&gt;49&lt;/span&gt;&lt;span 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; Your job is to&lt;span 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; Filter out any numbers that are even
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Sort the array &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; ascending order
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Map them to strings &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; the format &lt;span style="color:#bf616a"&gt;“&lt;/span&gt;&lt;span style="color:#b48ead"&gt;7&lt;/span&gt; is a lucky number&lt;span style="color:#bf616a"&gt;”&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Print the resulting array&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; one item per line
&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; So&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; your output should be as follows&lt;span 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:#b48ead"&gt;7&lt;/span&gt; is a lucky number
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b48ead"&gt;15&lt;/span&gt; is a lucky number
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b48ead"&gt;21&lt;/span&gt; is a lucky number
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b48ead"&gt;31&lt;/span&gt; is a lucky number
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b48ead"&gt;33&lt;/span&gt; is a lucky number
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#b48ead"&gt;49&lt;/span&gt; is a lucky number
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;let luckyNumbers &lt;span style="color:#81a1c1"&gt;=&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 style="color:#b48ead"&gt;4&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#b48ead"&gt;38&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#b48ead"&gt;21&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#b48ead"&gt;16&lt;/span&gt;&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 style="color:#b48ead"&gt;12&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#b48ead"&gt;33&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#b48ead"&gt;31&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#b48ead"&gt;49&lt;/span&gt;&lt;span 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; isNumberOdd&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;number&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;Int&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; &lt;span style="color:#81a1c1;font-weight:bold"&gt;return&lt;/span&gt; number&lt;span style="color:#81a1c1"&gt;%&lt;/span&gt;&lt;span style="color:#b48ead"&gt;2&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 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;let filteredNumbers &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; luckyNumbers&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;filter&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;isNumberOdd&lt;span 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"&gt;//&lt;/span&gt; this closure effectively does nothing
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;let sortedNumbers &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; filteredNumbers&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;sorted&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;by&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;0&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;&amp;lt;$&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&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;let mappedNumbers &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; sortedNumbers&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;map&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 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:#81a1c1"&gt;+&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34; is a lucky number&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:#81a1c1;font-weight:bold"&gt;for&lt;/span&gt; i &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:#81a1c1"&gt;.&amp;lt;&lt;/span&gt;mappedNumbers&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;count &lt;span style="color:#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;mappedNumbers&lt;span style="color:#eceff4"&gt;[&lt;/span&gt;i&lt;span style="color:#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>Checkpoint 4</title><link>https://blog.iankulin.com/checkpoint-4/</link><pubDate>Wed, 13 Jul 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/checkpoint-4/</guid><description>&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;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; The challenge is this&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; write a function that accepts an integer from &lt;span style="color:#b48ead"&gt;1&lt;/span&gt; through &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;000&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;and&lt;/span&gt; returns the integer square root of that number&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt; That sounds easy&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; but there are some catches&lt;span 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; You can&lt;span style="color:#bf616a"&gt;’&lt;/span&gt;t use Swift&lt;span style="color:#bf616a"&gt;’&lt;/span&gt;s built&lt;span style="color:#81a1c1"&gt;-&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;sqrt&lt;/span&gt;&lt;span style="color:#eceff4"&gt;()&lt;/span&gt; function &lt;span style="color:#81a1c1;font-weight:bold"&gt;or&lt;/span&gt; similar &lt;span style="color:#bf616a"&gt;–&lt;/span&gt; you need to find the square root yourself&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; If the number is less than &lt;span style="color:#b48ead"&gt;1&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;or&lt;/span&gt; greater than &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;000&lt;/span&gt; you should throw an &lt;span style="color:#bf616a"&gt;“&lt;/span&gt;out of bounds&lt;span style="color:#bf616a"&gt;”&lt;/span&gt; error&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; You should only consider integer square roots &lt;span style="color:#bf616a"&gt;–&lt;/span&gt; don&lt;span style="color:#bf616a"&gt;’&lt;/span&gt;t worry about the square root of &lt;span style="color:#b48ead"&gt;3&lt;/span&gt; being &lt;span style="color:#b48ead"&gt;1.732&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; example&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; If you can&lt;span style="color:#bf616a"&gt;’&lt;/span&gt;t find the square root&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; throw a &lt;span style="color:#bf616a"&gt;“&lt;/span&gt;no root&lt;span style="color:#bf616a"&gt;”&lt;/span&gt; error&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:#81a1c1"&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;enum&lt;/span&gt; IntSqrtError&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;case&lt;/span&gt; low&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; high&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; noIntRoot
&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; calculateIntSqrt&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;_ number&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;Int&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; throws &lt;span style="color:#81a1c1"&gt;-&amp;gt;&lt;/span&gt; Int &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 lowerBound &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; let upperBound &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;10&lt;/span&gt;_000
&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; number &lt;span style="color:#81a1c1"&gt;&amp;lt;&lt;/span&gt; lowerBound &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;throw IntSqrtError&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;low&lt;span style="color:#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; number &lt;span style="color:#81a1c1"&gt;&amp;gt;&lt;/span&gt; upperBound &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;throw IntSqrtError&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;high&lt;span style="color:#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; brute force &lt;span style="color:#81a1c1"&gt;sqrt&lt;/span&gt; finder
&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; lowerBound&lt;span style="color:#81a1c1"&gt;...&lt;/span&gt;number &lt;span style="color:#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; i&lt;span style="color:#81a1c1"&gt;*&lt;/span&gt;i &lt;span style="color:#81a1c1"&gt;==&lt;/span&gt; number &lt;span style="color:#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; i
&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;//&lt;/span&gt; none found &lt;span style="color:#81a1c1;font-weight:bold"&gt;or&lt;/span&gt; we would have returned by now
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; throw IntSqrtError&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;noIntRoot
&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; try &lt;span style="color:#81a1c1"&gt;print&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;calculateIntSqrt&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;5929&lt;/span&gt;&lt;span style="color:#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; catch IntSqrtError&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;low &lt;span style="color:#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;Lower bound error&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; catch IntSqrtError&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;high &lt;span style="color:#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;Upper bound error&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; catch IntSqrtError&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;noIntRoot &lt;span style="color:#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;No integer root&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; catch &lt;span style="color:#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;assert&lt;/span&gt;&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;false&lt;/span&gt;&lt;span style="color:#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;Unknown error&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;</description></item><item><title>Tuple Pronunciation</title><link>https://blog.iankulin.com/tuple-pronunciation/</link><pubDate>Tue, 12 Jul 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/tuple-pronunciation/</guid><description>&lt;p&gt;Another advantage of the videos, that hadn&amp;rsquo;t occurred to me when I &lt;a href="https://blog.iankulin.com/cs193p/"&gt;mentioned it the other day&lt;/a&gt;, is learning the correct pronunciation of things you&amp;rsquo;ve only ever read in books.&lt;/p&gt;
&lt;p&gt;Apparently, tuple is pronounced two-pull, and not with the tup to rhyme with cup as I&amp;rsquo;d always imagined. Google has confirmed, so it&amp;rsquo;s not just a UK thing.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-07-09-at-12.06.59-pm.jpg" alt=""&gt;&lt;/p&gt;</description></item><item><title>Watch or Read</title><link>https://blog.iankulin.com/watch-or-read/</link><pubDate>Sun, 10 Jul 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/watch-or-read/</guid><description>&lt;p&gt;I&amp;rsquo;m appreciating, in the &lt;a href="https://www.hackingwithswift.com/100/swiftui"&gt;100 Days of Swift UI&lt;/a&gt;, that &lt;a href="https://twitter.com/twostraws"&gt;Paul Hudson&lt;/a&gt; has provided a video and a text description for each of the topics. Usually, I&amp;rsquo;ll read the text - a lot of these early topics cover ground well-trodden from my previous experience, so I can skim forwards to find the Swift specific bits I need. However, if I&amp;rsquo;m making up my hour per day of Swift by multitasking it with a meal, then the video is handy.&lt;/p&gt;
&lt;p&gt;The content in the videos and the text is not identical either (in the couple I&amp;rsquo;ve watched), so if a concept is not clear, having some slightly different explanations is handy.&lt;/p&gt;</description></item><item><title>Checkpoint 3</title><link>https://blog.iankulin.com/checkpoint-2/</link><pubDate>Fri, 08 Jul 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/checkpoint-2/</guid><description>&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; /*
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; If it’s a multiple of 3, print “Fizz”
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; If it’s a multiple of 5, print “Buzz”
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; If it’s a multiple of 3 and 5, print “FizzBuzz”
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Otherwise, just print the number.
&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;for i in 1...100 {
&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 isMultOfThree = (i % 3 == 0)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; let isMultOfFive = (i % 5 == 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; if (isMultOfFive &amp;amp;&amp;amp; isMultOfThree) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&amp;#34;FizzBuzz&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } else if isMultOfThree {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&amp;#34;Fizz&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } else if isMultOfFive {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&amp;#34;Buzz&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } else {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(i)
&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;</description></item><item><title>100 days of SwiftUI</title><link>https://blog.iankulin.com/100-days-of-swiftui/</link><pubDate>Tue, 05 Jul 2022 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/100-days-of-swiftui/</guid><description>&lt;p&gt;Paul Hudson&amp;rsquo;s intro to his &lt;a href="https://www.hackingwithswift.com/100/swiftui"&gt;IOS development course&lt;/a&gt; asks participants to post on social media each day as part of an effort to keep them motivated. The Insta tag &lt;a href="https://www.instagram.com/explore/tags/100daysofswiftui/?hl=en"&gt;#100daysofswiftui&lt;/a&gt; is full of curated photos of MacBook Pros showing Xcode 14. This also seems like handy marketing for Paul.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not a crazy idea, so I&amp;rsquo;ll add this to the long list of excitedly started and eventually abandoned blogs of the world. Perhaps a post will even help a forlorn google searcher out on day. I&amp;rsquo;ll aim to keep them short, so it does not become a large enough task to trigger it&amp;rsquo;s own procrastination.&lt;/p&gt;
&lt;p&gt;Other things I&amp;rsquo;ve found in the past to help conserve a sustained effort in new learning is to find some good weekly podcasts, and to have a clear short term goal. This will be to get a simple, but minimally useful app up published on the App Store.&lt;/p&gt;</description></item></channel></rss>