<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>iOS Development on Ivan: Thinking</title>
    <link>https://ivanthinking.net/tags/ios-development/</link>
    <description>Recent content in iOS Development on Ivan: Thinking</description>
    <generator>Hugo</generator>
    <language>en</language>
    <atom:link href="https://ivanthinking.net/tags/ios-development/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Little Memory 2.1: Fixes from 2.0 Feedback</title>
      <link>https://ivanthinking.net/2026/10/07/little-memory-2.1-fixes-from-2.0-feedback/</link>
      <pubDate>Wed, 07 Oct 2026 09:45:00 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/10/07/little-memory-2.1-fixes-from-2.0-feedback/</guid>
      <category>little-memory</category>
      <category>ios-development</category>
      <category>nerdtower</category>
      <description>&lt;p&gt;&lt;a href=&#34;https://thelittlememory.com&#34;&gt;Little Memory&lt;/a&gt; 2.1 is out. Most of the updates for it came from feedback on 2.0:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Fixed the iOS 27 bug I &lt;a href=&#34;https://ivanthinking.net/2026/10/03/theres-a-visual-bug-with-little-memory-on/&#34;&gt;mentioned a few days ago&lt;/a&gt;, where the paging dots under the diary didn&amp;rsquo;t match the top toolbar&lt;/li&gt;&#xA;&lt;li&gt;The import screen is clearer about the two toggles that need to be turned on to continue&lt;/li&gt;&#xA;&lt;li&gt;Opening a day in a large diary is faster&lt;/li&gt;&#xA;&lt;li&gt;When installing on a 2nd device, the app now knows you have memories in iCloud and welcomes you back; the old wording was confusing&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Thanks to everyone who sent in bug reports and feedback.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/little-memory/&#34;&gt;/little-memory&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <link>https://ivanthinking.net/2026/10/03/theres-a-visual-bug-with-little-memory-on/</link>
      <pubDate>Sat, 03 Oct 2026 15:03:55 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/10/03/theres-a-visual-bug-with-little-memory-on/</guid>
      <category>little-memory</category>
      <category>ios-development</category>
      <description>&lt;p&gt;There&amp;rsquo;s a visual bug with Little Memory on iOS 27 in the main diary view. The paging indicators have a different background than the top toolbar. Fix will come in 2.1. Working on bugs that have come in from 2.0 too. Thanks to everyone who&amp;rsquo;s given me feedback and bug reports.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/little-memory/&#34;&gt;/little-memory&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Ready Time 3.9: Live Activities</title>
      <link>https://ivanthinking.net/2026/10/02/ready-time-3.9-live-activities/</link>
      <pubDate>Fri, 02 Oct 2026 21:06:55 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/10/02/ready-time-3.9-live-activities/</guid>
      <category>ready-time</category>
      <category>ios-development</category>
      <category>nerdtower</category>
      <description>&lt;p&gt;&lt;a href=&#34;https://getreadytime.com&#34;&gt;Ready Time&lt;/a&gt; has always &lt;a href=&#34;https://ivanthinking.net/2026/04/07/ready-time/&#34;&gt;had push notifications&lt;/a&gt; when it&amp;rsquo;s&#xA;time to start each step. But in between each step, you had to unlock your iPhone and&#xA;open the app to see the status of your plan. Now a running plan stays on your Lock&#xA;Screen and in the Dynamic Island with &lt;strong&gt;Live Activities&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The whole idea of the app has always been to help you stay on time and to know what to do next, but&#xA;keeping the Ready Time app open constantly doesn&amp;rsquo;t always make sense. You may be driving on your&#xA;way to a meeting. You might be dealing with a screaming toddler during your &lt;a href=&#34;https://getreadytime.com/routines/getting-kids-out-the-door/&#34;&gt;morning routine&lt;/a&gt;, trying&#xA;to juggle texting your babysitter or partner at the same time.&lt;/p&gt;&#xA;&lt;p&gt;Being able to set your phone down and quickly glance on your Lock Screen to see the status&#xA;of your plan helps you make sure you&amp;rsquo;re on track to get to your meeting, your &lt;a href=&#34;https://getreadytime.com/routines/doctor-appointment/&#34;&gt;appointment&lt;/a&gt;, and&#xA;any deadline you&amp;rsquo;ve set on time. It even shows up on your Apple Watch.&lt;/p&gt;&#xA;&lt;div class=&#34;image-row&#34;&gt;&#xA;  &#xA;  &#xA;    &#xA;    &#xA;    &#xA;    &#xA;      &#xA;      &#xA;        &#xA;      &#xA;      &#xA;    &#xA;    &lt;img src=&#34;https://ivanthinking.net/2026/10/02/ready-time-3.9-live-activities/lockscreen_hu_f16af5a08e059998.webp&#34; alt=&#34;A School run plan on the Lock Screen, 38 minutes left with a bar for each step&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;&gt;&#xA;  &#xA;    &#xA;    &#xA;    &#xA;    &#xA;      &#xA;      &#xA;        &#xA;      &#xA;      &#xA;    &#xA;    &lt;img src=&#34;https://ivanthinking.net/2026/10/02/ready-time-3.9-live-activities/dynamicisland_hu_2389c635da026d40.webp&#34; alt=&#34;A Weekday Morning plan expanded in the Dynamic Island over the app&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;&gt;&#xA;  &#xA;&lt;/div&gt;&#xA;&#xA;&lt;h2 id=&#34;turning-it-on&#34;&gt;&#xA;  Turning it on&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#turning-it-on&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Live Activities are part of Premium. With Premium, every plan shows up on its own when it starts. If&#xA;there&amp;rsquo;s a plan you&amp;rsquo;d rather keep off your Lock Screen, tap the Live Activity button on the plan. And&#xA;if you don&amp;rsquo;t want them at all, you can disable Live Activities in Settings.&lt;/p&gt;&#xA;&lt;h2 id=&#34;onward&#34;&gt;&#xA;  Onward&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#onward&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;You can &lt;a href=&#34;https://apps.apple.com/app/getreadytime/id1412470365?pt=265310&amp;amp;ct=ready-time-39-live-activities&amp;amp;mt=8&#34;&gt;download Ready Time on the App Store&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;If you have feedback, we&amp;rsquo;d love to hear from you. &lt;a href=&#34;mailto:support@getreadytime.com&#34;&gt;Drop me a note&lt;/a&gt;.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/ready-time/&#34;&gt;/ready-time&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Ready Time 3.8: Weekdays and Two New Themes</title>
      <link>https://ivanthinking.net/2026/09/29/ready-time-3.8-weekdays-and-two-new-themes/</link>
      <pubDate>Tue, 29 Sep 2026 19:00:00 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/09/29/ready-time-3.8-weekdays-and-two-new-themes/</guid>
      <category>ready-time</category>
      <category>ios-development</category>
      <category>nerdtower</category>
      <description>&lt;p&gt;We&amp;rsquo;ve had &lt;a href=&#34;https://ivanthinking.net/2026/04/28/ready-time-3.2-shared-plans-and-daily-repeating-plans/&#34;&gt;daily and weekly repeating plans&lt;/a&gt; for&#xA;&lt;a href=&#34;https://getreadytime.com&#34;&gt;Ready Time&lt;/a&gt;, but some things we only do on weekdays. Welp,&#xA;now we&amp;rsquo;ve got &lt;strong&gt;Weekday&lt;/strong&gt; repeating plans (plans that repeat only Monday through Friday).&#xA;This is perfect for &lt;a href=&#34;https://getreadytime.com/routines/school-drop-off/&#34;&gt;school drop-off&lt;/a&gt;, the commute, the walk to the bus.&lt;/p&gt;&#xA;&lt;div class=&#34;image-row&#34;&gt;&#xA;  &#xA;  &#xA;    &#xA;    &#xA;    &#xA;    &#xA;      &#xA;      &#xA;        &#xA;      &#xA;      &#xA;    &#xA;    &lt;img src=&#34;https://ivanthinking.net/2026/09/29/ready-time-3.8-weekdays-and-two-new-themes/weekdays_hu_35845cc41f475d71.webp&#34; alt=&#34;The plan editor with Repeat set to Weekdays, Mon-Fri at 8:15 AM&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;&gt;&#xA;  &#xA;&lt;/div&gt;&#xA;&#xA;&lt;h2 id=&#34;repeat-is-easier-to-find&#34;&gt;&#xA;  Repeat is easier to find&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#repeat-is-easier-to-find&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Repeat used to live in the menu in the plan editor, and it was a bit hidden. It now sits at the top of the deadline section in the plan editor, next to the time it applies to. The time row also tells you which days it covers, like &amp;ldquo;Mon-Fri at 8:15 AM&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;While we were there, we made Buffer a bit easier to find too, so it&amp;rsquo;s right on the top toolbar.&lt;/p&gt;&#xA;&lt;h2 id=&#34;two-new-themes&#34;&gt;&#xA;  Two new themes&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#two-new-themes&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Kyoto.&lt;/strong&gt; Sakura, wisteria and pale silk. It&amp;rsquo;s the softest theme in the app. Inspired by our recent travels to &lt;a href=&#34;https://ivanthinking.net/categories/japan/&#34;&gt;Japan&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;COOLMOVES.&lt;/strong&gt; Synthwave purple with teal steps. So 80&amp;rsquo;s. What, you don&amp;rsquo;t know &lt;a href=&#34;https://coolmoves.fun/&#34;&gt;COOLMOVES&lt;/a&gt;?&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;div class=&#34;image-row&#34;&gt;&#xA;  &#xA;  &#xA;    &#xA;    &#xA;    &#xA;    &#xA;      &#xA;      &#xA;        &#xA;      &#xA;      &#xA;    &#xA;    &lt;img src=&#34;https://ivanthinking.net/2026/09/29/ready-time-3.8-weekdays-and-two-new-themes/kyoto_hu_4c7a7f8f13f6395c.webp&#34; alt=&#34;The Kyoto theme in light mode&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;&gt;&#xA;  &#xA;    &#xA;    &#xA;    &#xA;    &#xA;      &#xA;      &#xA;        &#xA;      &#xA;      &#xA;    &#xA;    &lt;img src=&#34;https://ivanthinking.net/2026/09/29/ready-time-3.8-weekdays-and-two-new-themes/coolmoves_hu_59d5b6338af1757e.webp&#34; alt=&#34;The COOLMOVES theme in Dark Mode&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;&gt;&#xA;  &#xA;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;Both are now in Premium.&lt;/p&gt;&#xA;&lt;p&gt;We also cleaned up some of the existing themes where the contrast would sometimes make it hard to read items in your plans. Sedona and Countdown Classic should now read better in Dark Mode.&lt;/p&gt;&#xA;&lt;h2 id=&#34;onward&#34;&gt;&#xA;  Onward&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#onward&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;You can &lt;a href=&#34;https://apps.apple.com/app/getreadytime/id1412470365?pt=265310&amp;amp;ct=ready-time-38-weekdays-and-new-themes&amp;amp;mt=8&#34;&gt;download Ready Time on the App Store&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;If you have feedback, we&amp;rsquo;d love to hear from you. &lt;a href=&#34;mailto:support@getreadytime.com&#34;&gt;Drop me a note&lt;/a&gt;.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/ready-time/&#34;&gt;/ready-time&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <link>https://ivanthinking.net/2026/09/29/brent-simmons-the-stock-mac-ui-is-designed/</link>
      <pubDate>Tue, 29 Sep 2026 13:28:06 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/09/29/brent-simmons-the-stock-mac-ui-is-designed/</guid>
      <category>software-development</category>
      <category>design</category>
      <category>apple</category>
      <category>ios-development</category>
      <description>&lt;p&gt;&lt;a href=&#34;https://inessential.com/2026/09/22/that-about-wraps-it-up-for.html&#34;&gt;Brent Simmons&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;The stock Mac UI is designed by Apple, the best designers in the world, and do you really think you can do better? Really?&lt;/p&gt;&#xA;&lt;p&gt;The app world is full of people who think they’re better and they’re really, really, really not.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;This is the same way I feel whenever I see a Porsche with a giant after-market spoiler on it. Do folks &lt;em&gt;really&lt;/em&gt; think they can do better than Porsche?&lt;/p&gt;&#xA;&lt;p&gt;He&amp;rsquo;s talking about Mac apps here, and I feel similarly about iOS apps.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/design/&#34;&gt;#design&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/apple/&#34;&gt;#apple&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Little Memory 2.0: Moving 15 years of memories off my servers</title>
      <link>https://ivanthinking.net/2026/09/22/little-memory-2.0-moving-15-years-of-memories-off-my-servers/</link>
      <pubDate>Tue, 22 Sep 2026 10:00:00 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/09/22/little-memory-2.0-moving-15-years-of-memories-off-my-servers/</guid>
      <category>software-development</category>
      <category>little-memory</category>
      <category>ios-development</category>
      <description>&lt;p&gt;In April, I &lt;a href=&#34;https://ivanthinking.net/2026/04/28/next-chapter-of-little-memory/&#34;&gt;started inviting folks to test out Little Memory 2.0&lt;/a&gt;, a full re-write of&#xA;the one-sentence-a-day journal I launched in 2011. I had most of the bones of the new app ready at that point, but, as most folks who develop&#xA;software professionally know, the last 20% can often be the most tricky, difficult, and filled with unforeseen bugs.&lt;/p&gt;&#xA;&lt;div class=&#34;image-row&#34;&gt;&#xA;  &#xA;  &#xA;    &#xA;    &#xA;    &#xA;    &#xA;      &#xA;      &#xA;        &#xA;      &#xA;      &#xA;    &#xA;    &lt;img src=&#34;https://ivanthinking.net/2026/09/22/little-memory-2.0-moving-15-years-of-memories-off-my-servers/01_MemoriesHero_hu_1854d98a7a83d798.webp&#34; alt=&#34;Little Memory 2.0 showing a memory from exactly one year ago&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;&gt;&#xA;  &#xA;    &#xA;    &#xA;    &#xA;    &#xA;      &#xA;      &#xA;        &#xA;      &#xA;      &#xA;    &#xA;    &lt;img src=&#34;https://ivanthinking.net/2026/09/22/little-memory-2.0-moving-15-years-of-memories-off-my-servers/02_EntryEditor_hu_1418ebf8752e402c.webp&#34; alt=&#34;Writing a one-sentence memory in Little Memory 2.0&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;&gt;&#xA;  &#xA;&lt;/div&gt;&#xA;&#xA;&lt;h2 id=&#34;what-the-first-testers-found&#34;&gt;&#xA;  What the first testers found&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#what-the-first-testers-found&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;In the first batch of testers, 4 longtime users imported thousands of memories, and the world didn&amp;rsquo;t crash and burn, yay!&lt;/li&gt;&#xA;&lt;li&gt;Lilmem previously had a way to write memories via SMS. Twilio sent a webhook request to us when these would arrive, and, for longer messages that were over 160 characters, I would get 2+ requests. However, they wouldn&amp;rsquo;t always come in chronological order. Moreover, they didn&amp;rsquo;t have a single message ID to let me know they were a single message. Because of a bug in the backend, this ended up letting some users have multiple entries on a single day (&lt;em&gt;oi!&lt;/em&gt;). I cleaned this up in the import into the app, but it did cause some issues for some folks who did their initial imports into the app.&lt;/li&gt;&#xA;&lt;li&gt;Some users really enjoy writing on their desktop before bed, and being able to add memories only from the iOS app was a problem. I didn&amp;rsquo;t want to end up back in a place where I was storing entries in my DB again, but I did figure something out: Drafts. Drafts lets Premium users write a memory from the web for the day, and when the iOS app is launched, it fetches the unsaved drafts and reconciles them so they end up living in the app.&lt;/li&gt;&#xA;&lt;li&gt;There were some users reporting that they didn&amp;rsquo;t see photos on days where they were sure they had photos saved. There was a concurrency issue where even though a photo would get downloaded, it wouldn&amp;rsquo;t get properly attached to a memory. After a good amount of logging and tracking this down, I got a build for testers that included a repair pass to get their photos back.&lt;/li&gt;&#xA;&lt;li&gt;So glad I did a few rounds of testing on TestFlight. These would have been super expensive things to handle after release.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;get-off-my-lawn-and-servers&#34;&gt;&#xA;  Get off my lawn! And servers!&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#get-off-my-lawn-and-servers&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve generally felt uncomfortable storing people&amp;rsquo;s memories on my own servers. What if I got hit by a bus today? Someone would have to make sure&#xA;the servers keep running so folks could keep reading and writing in their personal private journals. That&amp;rsquo;s definitely not something I&amp;rsquo;d want for&#xA;myself or for Lil&amp;rsquo;memmers.&lt;/p&gt;&#xA;&lt;p&gt;Not only that, but now that all your memories live on your phone, you can read/write offline, even without internet access. And it opens up&#xA;the door for some exciting new features. I can&amp;rsquo;t wait to get 2.0 into people&amp;rsquo;s hands and start iterating on the app with some ideas I&amp;rsquo;ve had&#xA;queued up for years.&lt;/p&gt;&#xA;&lt;h2 id=&#34;on-ai-privacy-and-your-diary&#34;&gt;&#xA;  On AI, privacy, and your diary&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#on-ai-privacy-and-your-diary&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;A diary is about the most personal data an app can hold, and every app holding it is one breach, acquisition, or bad decision away from leaking that extremely sensitive and private data. With AI becoming more and more capable every day, the possibility of servers getting hacked grows. I’ve never used any Little Memory data for AI training, but even the possibility that your data could be read or used for training is something I would rather avoid.&lt;/p&gt;&#xA;&lt;p&gt;With Little Memory 2.0, the iPhone app is offline-first. Memories are written and stored on the phone, you can keep a diary without an account, and your private and personal data stays in your control and in your phone rather than in the cloud.&lt;/p&gt;&#xA;&lt;h2 id=&#34;whats-new&#34;&gt;&#xA;  What&amp;rsquo;s new?&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#whats-new&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Your memories are imported into your iPhone&lt;/li&gt;&#xA;&lt;li&gt;All your memories are now synced across your devices with iCloud&lt;/li&gt;&#xA;&lt;li&gt;You can now add/edit memories even when offline (&lt;em&gt;yay!&lt;/em&gt;)&lt;/li&gt;&#xA;&lt;li&gt;A ground-up redesign of the whole app&lt;/li&gt;&#xA;&lt;li&gt;The app is now free to download, and if you already paid for it, you keep Premium&lt;/li&gt;&#xA;&lt;li&gt;After you update and import your memories, you&amp;rsquo;ll no longer get email reminders&lt;/li&gt;&#xA;&lt;li&gt;If you want to write from the web or from your desktop, after importing to the app, you can still visit the website and write an entry for Today with Drafts (Premium feature)&lt;/li&gt;&#xA;&lt;li&gt;There&amp;rsquo;s a new Photo Recap feature that lets you view photos for a day alongside your memory for that day&lt;/li&gt;&#xA;&lt;li&gt;Still here: charts, reminders, streaks, Dropbox backups, export&lt;/li&gt;&#xA;&lt;li&gt;Little Memory supports Dark Mode now, &lt;em&gt;woo-hoo!&lt;/em&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;div class=&#34;image-row&#34;&gt;&#xA;  &#xA;  &#xA;    &#xA;    &#xA;    &#xA;    &#xA;      &#xA;      &#xA;        &#xA;      &#xA;      &#xA;    &#xA;    &lt;img src=&#34;https://ivanthinking.net/2026/09/22/little-memory-2.0-moving-15-years-of-memories-off-my-servers/03_Reflection_hu_feb8d9a23e2a211f.webp&#34; alt=&#34;Little Memory 2.0 stats: streaks, people, and places from your year&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;&gt;&#xA;  &#xA;    &#xA;    &#xA;    &#xA;    &#xA;      &#xA;      &#xA;        &#xA;      &#xA;      &#xA;    &#xA;    &lt;img src=&#34;https://ivanthinking.net/2026/09/22/little-memory-2.0-moving-15-years-of-memories-off-my-servers/06_Charts_hu_83345d708029ac3f.webp&#34; alt=&#34;Little Memory 2.0 charts of the people and feelings in your entries&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;&gt;&#xA;  &#xA;&lt;/div&gt;&#xA;&#xA;&lt;h2 id=&#34;the-blast-from-the-past-is-back-again&#34;&gt;&#xA;  The blast from the past is back again&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#the-blast-from-the-past-is-back-again&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Little Memory 2.0 is now live in the &lt;a href=&#34;https://apps.apple.com/app/little-memory-one-line-a-day/id514233379?pt=265310&amp;amp;ct=ivanthinking-2-0-post&amp;amp;mt=8&#34;&gt;App Store&lt;/a&gt; with all the latest updates.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Longtime users:&lt;/strong&gt; Your memories are ready to come home to your iPhone. &lt;a href=&#34;https://apps.apple.com/app/little-memory-one-line-a-day/id514233379?pt=265310&amp;amp;ct=ivanthinking-2-0-post&amp;amp;mt=8&#34;&gt;Download the new app&lt;/a&gt; and import them to your device so you can read and write without needing to be online.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;New users:&lt;/strong&gt; Welcome! Write one sentence about your day in less than 15 seconds, and start seeing your memories come back to you.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m excited to share Little Memory 2.0 with you. It&amp;rsquo;s been a joy getting it ready for users, and I&amp;rsquo;ve been wanting to get it&#xA;into people&amp;rsquo;s hands to make the meaningful experience of reflection and journaling even easier and more enjoyable.&lt;/p&gt;&#xA;&lt;p&gt;I think you&amp;rsquo;re going to really like the new app, and please &lt;a href=&#34;mailto:help@thelittlememory.com&#34;&gt;reach out&lt;/a&gt; if you have any feedback or find any issues.&lt;/p&gt;&#xA;&lt;h2 id=&#34;some-thanks&#34;&gt;&#xA;  Some thanks&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#some-thanks&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;A big thanks to the design and user research done by &lt;a href=&#34;https://rayinoue.com&#34;&gt;Ray Inoue&lt;/a&gt;. Much of the learnings that came out of Ray&amp;rsquo;s work informed the design decisions that ultimately went into the 2.0 rebuild. It&amp;rsquo;s taken me way too long to get these changes into production, but it&amp;rsquo;s finally here! Sorry it took me so long, Ray!&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/categories/little-memory/&#34;&gt;/little-memory&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <link>https://ivanthinking.net/2026/09/18/i-too-have-been-concerned-about-the-vertical/</link>
      <pubDate>Fri, 18 Sep 2026 22:53:21 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/09/18/i-too-have-been-concerned-about-the-vertical/</guid>
      <category>software-development</category>
      <category>apple</category>
      <category>design</category>
      <category>ios-development</category>
      <description>&lt;p&gt;I, too, have been concerned about the vertical tab bar on the iPhone Duo not having space for labels. But looks like &lt;a href=&#34;https://www.manton.org/2026/09/18/one-of-my-concerns-with.html&#34;&gt;tap and hold reveals them&lt;/a&gt;. Nice solution.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/apple/&#34;&gt;#apple&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/design/&#34;&gt;#design&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Action Runner iOS Architecture</title>
      <link>https://ivanthinking.net/2026/09/15/action-runner-ios-architecture/</link>
      <pubDate>Tue, 15 Sep 2026 15:15:23 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/09/15/action-runner-ios-architecture/</guid>
      <category>software-development</category>
      <category>ios-development</category>
      <description>&lt;p&gt;Around mid-2021, I&amp;rsquo;d started developing SwiftUI apps in a new way. I&amp;rsquo;d previously considered architectures like MVC, MVVM, VIPER, etc&#xA;and many of them never felt right. I often found myself spending time considering where to put business logic. With SwiftUI,&#xA;it wasn&amp;rsquo;t entirely clear where data-level changes should be taking place.&lt;/p&gt;&#xA;&lt;h3 id=&#34;controllers&#34;&gt;&#xA;  Controllers?&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#controllers&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Most (or any?) business logic certainly shouldn&amp;rsquo;t be living in the views, but should I be building controllers to manage data?&#xA;When I did that, I ended up struggling to define the boundaries of each controller. Dependencies would start to get messy,&#xA;and I&amp;rsquo;d start getting circular dependencies across controllers that called each other.&#xA;Controllers would start to get really big, especially for authentication,&#xA;and I&amp;rsquo;d often struggle to quickly find code I was looking for.&lt;/p&gt;&#xA;&lt;h3 id=&#34;whats-an-action&#34;&gt;&#xA;  What&amp;rsquo;s an &lt;code&gt;Action&lt;/code&gt;?&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#whats-an-action&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;In my work using React, the one-way data flow in Redux and how it handled actions was an idea that came to mind to try&#xA;to tame these large controllers I was noticing in my projects. I started with adding a simple &lt;code&gt;struct&lt;/code&gt; in Swift that would work&#xA;like an action that a view or another action could call:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;LoginAction&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;email&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;password&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;throws&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With a call site like:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;action&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;LoginAction&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;email&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;...&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;password&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;...&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;try&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;action&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I essentially pulled out each function from my controllers and now had them each as their own structure.&lt;/p&gt;&#xA;&lt;p&gt;Having a dedicated struct for each action gave me some nice things:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;I can create helper functions for the main &lt;code&gt;execute()&lt;/code&gt; without exposing them to other actions, whereas if this were all part of a controller it would be less isolated&lt;/li&gt;&#xA;&lt;li&gt;If I wanted to expose that helper function to other actions, I could do so with its own dedicated &lt;code&gt;Action&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;I could write unit tests for that specific action without needing to worry about dependencies that a full controller might carry&lt;/li&gt;&#xA;&lt;li&gt;Chaining &lt;code&gt;Action&lt;/code&gt;s could be done within an &lt;code&gt;execute&lt;/code&gt; like: &lt;code&gt;otherAction.execute()&lt;/code&gt; (this gets even cooler later when we pass through &lt;code&gt;RunContext&lt;/code&gt;)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;What about dependencies? In the case of this &lt;code&gt;LoginAction&lt;/code&gt;, I&amp;rsquo;d have to bring in some sort of API to actually make an HTTP request and&#xA;execute that request.&lt;/p&gt;&#xA;&lt;h3 id=&#34;enter-actionrunner-and-runcontext&#34;&gt;&#xA;  Enter &lt;code&gt;ActionRunner&lt;/code&gt; and &lt;code&gt;RunContext&lt;/code&gt;&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#enter-actionrunner-and-runcontext&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;ActionRunner&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Sendable&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;runContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RunContext&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;A&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RunnableAction&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;_&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;action&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;A&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;throws&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;A&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Output&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;action&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;in&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;runContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;protocol&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;RunnableAction&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Sendable&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;associatedtype&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Output&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RunContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;throws&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Output&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;RunContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Sendable&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ApiServices&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And then we update &lt;code&gt;LoginAction&lt;/code&gt; to:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;LoginAction&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RunnableAction&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;email&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;password&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RunContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;throws&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;LoginResult&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;service&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;account&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;response&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;service&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;login&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;email&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;email&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;password&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;password&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;An &lt;code&gt;ActionRunner&lt;/code&gt; can be added to SwiftUI&amp;rsquo;s &lt;code&gt;EnvironmentValues&lt;/code&gt; to make them easy to get from your views:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;LoginView&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;View&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Environment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;\&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;actionRunner&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;runner&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;login&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;loginAction&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;LoginAction&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;email&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;...&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;password&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;...&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;kc&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;loginResult&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;runner&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;loginAction&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;catch&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;c1&#34;&gt;// ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And this is how chaining would look now:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;LoginAction&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RunnableAction&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RunContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;throws&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;LoginResult&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;service&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;account&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;response&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;service&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;login&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;email&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;email&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;password&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;password&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;storeTokenAction&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;StoreTokenAction&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;storeTokenAction&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;in&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;analytics&#34;&gt;&#xA;  Analytics&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#analytics&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;I ended up adding a helper function as an extension to &lt;code&gt;RunnableAction&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;extension&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;RunnableAction&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;withAnalytics&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;_&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;event&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;AnalyticsEvent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;_&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;perform&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;throws&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;rethrows&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Result&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So we can call it in our &lt;code&gt;Action&lt;/code&gt;s thusly:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;LoginAction&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RunnableAction&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;email&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;password&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RunContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;throws&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;LoginResult&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;service&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;account&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;withAnalytics&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;login&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;response&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;service&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;login&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;email&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;email&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;password&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;password&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;c1&#34;&gt;// ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;action-runners&#34;&gt;&#xA;  Action Runners&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#action-runners&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve been calling this the Action Runner Architecture. It&amp;rsquo;s served me well across multiple projects in offline-first apps, CoreData apps,&#xA;API driven apps, and App Clips. I&amp;rsquo;ve codified it into its own package, and I continue to use it in my projects today.&lt;/p&gt;&#xA;&lt;p&gt;There are still issues that are not yet fully addressed here:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Infinite recursion can happen with what we have now&lt;/li&gt;&#xA;&lt;li&gt;We need to define an &lt;code&gt;ActionRunner&lt;/code&gt; for testing so that dependencies are stubbed in a testing &lt;code&gt;RunContext&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;I group related &lt;code&gt;Action&lt;/code&gt;s in a single file, but I still need make decisions on where things go&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;who-else-has-done-this&#34;&gt;&#xA;  Who else has done this?&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#who-else-has-done-this&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;I found it interesting that Whatnot, in 2022, came up with a similar pattern that they show in their blog post: &lt;a href=&#34;https://medium.com/whatnot-engineering/reacting-to-native-how-we-rebuilt-a-unicorn-ios-app-in-4-months-b453d045b98f&#34;&gt;Reacting to Native: How we rebuilt a unicorn iOS app in 4 months&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Their example of their &lt;code&gt;BookmarkLivestreamAction&lt;/code&gt; smells very similar to what I&amp;rsquo;ve done with my own pattern:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;BookmarkLivestreamAction&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;completion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;escaping&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;FollowLivetreamRequestCompletionHandler&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;       &lt;span class=&#34;c1&#34;&gt;/// ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A more modern implementation would likely use Swift&amp;rsquo;s structured concurrency rather than the escaped closure, and I&#xA;prefer to make sure my actions don&amp;rsquo;t hold state, so mine are always structs.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Shopify is moving from React Native to native Swift/Kotlin</title>
      <link>https://ivanthinking.net/2026/09/14/shopify-is-moving-from-react-native-to-native-swift/kotlin/</link>
      <pubDate>Mon, 14 Sep 2026 10:20:55 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/09/14/shopify-is-moving-from-react-native-to-native-swift/kotlin/</guid>
      <category>software-development</category>
      <category>ios-development</category>
      <category>ai</category>
      <description>&lt;p&gt;&lt;a href=&#34;https://shopify.engineering/back-to-native&#34;&gt;Native is now the future of mobile at Shopify&lt;/a&gt; (&lt;a href=&#34;https://news.ycombinator.com/item?id=49643982&#34;&gt;via&lt;/a&gt;):&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Native still means building and maintaining software on two platforms, that cost has not disappeared. What changed is that agents can now do enough of the implementation, translation, testing, and review work that it’s no longer the deciding factor it was in 2020.&lt;/p&gt;&#xA;&lt;p&gt;React Native apps can be fast. Ours are. We are making this change because agents have reduced the advantages of sharing implementation, while the advantages of building for each platform remain. Native keeps us closer to platform capabilities and first-party tooling, with fewer framework and dependency layers between our code and the platform.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve noted before that React Native can make more sense for larger teams that have more bandwidth for more testers and have expertise in React/JS tooling&#xA;as well as native code/tooling, which helps manage the &amp;ldquo;write once, debug everywhere&amp;rdquo; problem. With smaller teams, I find that it&amp;rsquo;s harder to&#xA;manage, as code changes can easily scatter bugs across platforms. And with React Native, you still need platform expertise, but now you need both&#xA;native expertise and React Native expertise.&lt;/p&gt;&#xA;&lt;p&gt;From their &lt;a href=&#34;https://shopify.engineering/shop-app-migration&#34;&gt;engineering blog&lt;/a&gt;, it also looks like they were bumping into issues managing code re-use between the React Native and native layers:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;For the Shop App, this coincided with our next major React Native investment: adopting the New Architecture. That work would have required us to revisit native module integrations, rendering, and the boundaries between shared and platform-specific code.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;I imagine some of the code re-use complications come from trying to build native widgets, extensions (looking at you,&#xA;&lt;a href=&#34;https://developer.apple.com/documentation/usernotifications/unnotificationserviceextension&#34;&gt;Notifcation Service Extension&lt;/a&gt;),&#xA;and things like App Clips. It&amp;rsquo;s already&#xA;complicated enough to manage and architect modules and shared code for a single platform (with varying platform versions too!):&#xA;now think about having to consider how to do it across&#xA;multiple platforms!&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ai/&#34;&gt;#ai&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Rewarding the user for wondering and trying</title>
      <link>https://ivanthinking.net/2026/09/06/rewarding-the-user-for-wondering-and-trying/</link>
      <pubDate>Sun, 06 Sep 2026 15:08:48 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/09/06/rewarding-the-user-for-wondering-and-trying/</guid>
      <category>software-development</category>
      <category>design</category>
      <category>ios-development</category>
      <description>&lt;p&gt;&lt;a href=&#34;https://unsung.aresluna.org/ivorys-account-switching&#34;&gt;Ivory&amp;rsquo;s account switching&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;There is also something great in seeing an interface that grows with you, or one where you can say &amp;ldquo;I wonder if…&amp;rdquo; based on your prior interactions and expectations, and the interface actually rewarding you for that thought.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;I can get pretty strict about the use of terms, symbols, and gestures in an app. This kind of discovery of features is what that kind of strictness and consistency allows for. I see it very similar to game design, where you &lt;a href=&#34;https://ivanthinking.net/2026/04/05/design-in-mega-man-x/&#34;&gt;teach the user the rules of your interactions and visual vocabulary&lt;/a&gt;, and once they&amp;rsquo;re in that world they know what to expect.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/design/&#34;&gt;#design&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Ready Time 3.6: Routines and App Clips</title>
      <link>https://ivanthinking.net/2026/09/04/ready-time-3.6-routines-and-app-clips/</link>
      <pubDate>Fri, 04 Sep 2026 14:46:30 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/09/04/ready-time-3.6-routines-and-app-clips/</guid>
      <category>ready-time</category>
      <category>ios-development</category>
      <category>nerdtower</category>
      <description>&lt;p&gt;Now it’s faster and easier to add a new plan on Ready Time. With Ready Time 3.6, you can now select from &lt;a href=&#34;https://getreadytime.com/routines/&#34;&gt;46 pre-defined routines to start your plan&lt;/a&gt;, including:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://getreadytime.com/routines/morning-routine-time-blindness/&#34;&gt;Morning routine.&lt;/a&gt; Getting up can be tricky for some of us, and having a regular routine definitely helps out.&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://getreadytime.com/routines/doctor-appointment/&#34;&gt;Doctor appointment.&lt;/a&gt; You scheduled it, so you gotta make sure you get out the door and to the doctor’s office in time.&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://getreadytime.com/routines/concert-night/&#34;&gt;Concert night.&lt;/a&gt; Don’t wanna miss the headliner!&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;And now, for folks who are discovering Ready Time from the web, if you visit a routine page from Safari on iOS, you can start an App Clip right from the page to quickly start planning for a deadline without even needing to install the full app! Just open a routine page like &lt;a href=&#34;https://getreadytime.com/routines/getting-out-of-bed/&#34;&gt;Getting out of bed&lt;/a&gt; and open the App Clip right from Safari.&lt;/p&gt;&#xA;&lt;div class=&#34;image-row&#34;&gt;&#xA;  &#xA;  &#xA;    &#xA;    &#xA;    &#xA;    &#xA;      &#xA;      &#xA;        &#xA;      &#xA;      &#xA;    &#xA;    &lt;img src=&#34;https://ivanthinking.net/2026/09/04/ready-time-3.6-routines-and-app-clips/appclip_hu_674bca9e71c4c1a7.webp&#34; alt=&#34;The Ready Time App Clip card opening from a routine page in Safari&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;&gt;&#xA;  &#xA;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;The App Clip lets you get notifications for each step in your plan, just like the full app. And when you’re ready to create more plans or customize your plans more, you can quickly &lt;a href=&#34;https://apps.apple.com/app/getreadytime/id1412470365?pt=265310&amp;amp;ct=ready-time-36-routines-and-app-clips&amp;amp;mt=8&#34;&gt;download the full Ready Time app&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;We hope these updates will make using and trying out Ready Time faster and easier.&lt;/p&gt;&#xA;&lt;p&gt;Let me know if you have any feedback. I love to hear from you.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/ready-time/&#34;&gt;/ready-time&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <link>https://ivanthinking.net/2026/08/25/revisiting-using-cursor-for-trying-out-more-non-claude/</link>
      <pubDate>Tue, 25 Aug 2026 21:14:35 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/08/25/revisiting-using-cursor-for-trying-out-more-non-claude/</guid>
      <category>software-development</category>
      <category>ai</category>
      <category>ios-development</category>
      <description>&lt;p&gt;Revisiting using Cursor for trying out more non-Claude agents, which I haven&amp;rsquo;t done too much of recently. I&amp;rsquo;ve generally been running Claude Code on my laptop, especially for iOS/Xcode projects, but doing a bit more with cloud agents with my web projects.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ai/&#34;&gt;#ai&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Debugging Little Memory 2.0 Diary View Scrolling</title>
      <link>https://ivanthinking.net/2026/08/20/debugging-little-memory-2.0-diary-view-scrolling/</link>
      <pubDate>Thu, 20 Aug 2026 14:32:50 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/08/20/debugging-little-memory-2.0-diary-view-scrolling/</guid>
      <category>little-memory</category>
      <category>software-development</category>
      <category>ai</category>
      <category>ios-development</category>
      <category>nerdtower</category>
      <description>&lt;p&gt;I was working on fixing a bug in &lt;a href=&#34;https://thelittlememory.com/&#34;&gt;Lilmem&lt;/a&gt; 2.0 where switching from the diary&amp;rsquo;s Calendar mode to Memories&#xA;mode caused a bug where the diary&amp;rsquo;s content &amp;ldquo;stuck&amp;rdquo; in a specific day. When I&amp;rsquo;d try to swipe between&#xA;days after switching modes, the same day would re-appear after swiping.&lt;/p&gt;&#xA;&lt;p&gt;I asked Claude to help me take a look and consider a fix. It came back with its usual &lt;a href=&#34;https://zachahn.com/posts/1787191554&#34;&gt;vomit of text&lt;/a&gt;,&#xA;and it left me rather confused. Rather than press it to give me even more text, I asked it to&#xA;give me a visual explanation for what it was trying to do instead.&lt;/p&gt;&#xA;&lt;p&gt;Here it&amp;rsquo;s explaining first the layout of the view so that we knew we were on the same page (ha!):&lt;/p&gt;&#xA;&lt;p&gt;&lt;img class=&#34;page-img&#34; src=&#34;https://ivanthinking.net/2026/08/20/debugging-little-memory-2.0-diary-view-scrolling/01_anatomy_hu_d93c653eba7939e4.png&#34; alt=&#34;Claude&amp;rsquo;s diagram of the Diary screen: a toolbar over a strip view over a content pager, with the SwiftUI view that owns each region called out on the right&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;  /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;It outlined the various ways &lt;code&gt;selectedPage&lt;/code&gt; changes can happen and its effects. And then it brings in a new concept of a &amp;ldquo;pinned&amp;rdquo; date key, which wasn&amp;rsquo;t clear to me in its wall of text:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img class=&#34;page-img&#34; src=&#34;https://ivanthinking.net/2026/08/20/debugging-little-memory-2.0-diary-view-scrolling/02_the_hub_hu_171699d50113a676.png&#34; alt=&#34;Claude&amp;rsquo;s diagram of selectedPage as a hub: swipes and date selection write to it, and its didSet fans out to the strip positions, the toolbar title, and pinnedDateKey&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;  /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;And then it explains how and where the pinned date key is used:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img class=&#34;page-img&#34; src=&#34;https://ivanthinking.net/2026/08/20/debugging-little-memory-2.0-diary-view-scrolling/03_two_modes_lists_hu_b7ba5da4c87b6d19.png&#34; alt=&#34;Claude&amp;rsquo;s diagram of the two page lists: Calendar mode is a contiguous run of days, Memories mode is a sparse list of anchor days plus a pinned day and a reflection page&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;  /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;After seeing Claude&amp;rsquo;s diagrams and explanation, I was able to get what it was talking about&#xA;and start collaborating with it from there.&lt;/p&gt;&#xA;&lt;p&gt;Super helpful getting these visual explanations. I was inspired by how I&amp;rsquo;ve&#xA;seen Simon Willison has gotten Claude to make&#xA;&lt;a href=&#34;https://simonwillison.net/guides/agentic-engineering-patterns/interactive-explanations/&#34;&gt;interactive explanations&lt;/a&gt;&#xA;for him to better understand what the agent is talking about.&lt;/p&gt;&#xA;&lt;p&gt;This view/interaction is a core part of the UX of Little Memory, and getting it right&#xA;is critical to the experience of the app, so I had to make sure Claude wasn&amp;rsquo;t just&#xA;doing something too wild. Having it show me (rather than just tell me) was&#xA;incredibly useful to make sure I knew what it was trying to accomplish.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/little-memory/&#34;&gt;/little-memory&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ai/&#34;&gt;#ai&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>iOS Dev Weekly Issue 763 is a game changer</title>
      <link>https://ivanthinking.net/2026/08/14/ios-dev-weekly-issue-763-is-a-game-changer/</link>
      <pubDate>Fri, 14 Aug 2026 12:17:13 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/08/14/ios-dev-weekly-issue-763-is-a-game-changer/</guid>
      <category>software-development</category>
      <category>ios-development</category>
      <category>ai</category>
      <description>&lt;p&gt;I&amp;rsquo;ve been reading &lt;a href=&#34;https://iosdevweekly.com&#34;&gt;iOS Dev Weekly&lt;/a&gt; for years now. Before this past May, the newsletter&#xA;was written by &lt;a href=&#34;https://daveverwer.com&#34;&gt;Dave Verwer&lt;/a&gt; who I&amp;rsquo;d emailed a handful of times (he responded!) and always had great&#xA;commentary about the state of the iOS development world. Sometimes, he&amp;rsquo;d be honest&#xA;and note he didn&amp;rsquo;t have much to say that week, but he always brought interesting&#xA;thoughts and ideas to the table.&lt;/p&gt;&#xA;&lt;p&gt;Since May, the &lt;a href=&#34;https://iosdevweekly.com/issues/751/&#34;&gt;newsletter was handed over&lt;/a&gt; to&#xA;Mobile Sessions who would take the helm and keep it going. I&amp;rsquo;ve been skimming the&#xA;latest issues, as it helps to read about new frameworks or blog posts from the community&#xA;to keep up with what&amp;rsquo;s happening on iOS. There&amp;rsquo;s a rotation of writers who have been&#xA;taking turns on the newsletter for each week. It wasn&amp;rsquo;t Dave, but I still felt like&#xA;I was connecting with people in the community.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://iosdevweekly.com/issues/763/&#34;&gt;latest issue of iOS Dev Weekly&lt;/a&gt;, though,&#xA;was just riddled with AI/LLM-isms that kept irking me:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;A new set of App Store marketing assets arrives this autumn, and the work is design work rather than engineering, which means it probably isn’t on anyone’s board yet.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Ugh, this &amp;ldquo;and the work is&amp;hellip;&amp;rdquo; just keeps showing up.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Most of us open Instruments, look at the call tree, and quietly close it again. The hierarchy is honest but it buries the answer.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;And this &amp;ldquo;quietly&amp;rdquo; and &amp;ldquo;X is Y but Z&amp;rdquo; pattern as well.&lt;/p&gt;&#xA;&lt;p&gt;And even in the And Finally&amp;hellip;, which, IMO is an easy way to just bust something out by hand:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Somewhere a product manager shouted ship it, nobody asked a follow-up question, and CarPlay is now in a pontoon boat while some automakers are still workshopping their objections.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Oi. This &amp;ldquo;nobody asked X&amp;rdquo; pattern that AI loves so much and is so dramatic about.&lt;/p&gt;&#xA;&lt;p&gt;Looking back at previous issues by Juan Marin, and &lt;a href=&#34;https://iosdevweekly.com/issues/759/&#34;&gt;Issue 759&lt;/a&gt;&#xA;was also by him. Still pretty AI/agentic.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not against agents writing and helping to write (though, I know some folks are adamantly against any use of AI in writing), but&#xA;IMO this is a real sloppy use of it. I read enough Claude-isms all day from my agents&#xA;in Claude Code, and it&amp;rsquo;s already exhausting there. When I&amp;rsquo;m reading a newsletter&#xA;like iOS Dev Weekly, I&amp;rsquo;m expecting more than just the first pass of what gets spit out.&#xA;I think it&amp;rsquo;s lazy and really leaves a bad taste in my mind. A little editing of the&#xA;original draft can easily clean these up and give the writer a bit more of their own&#xA;voice and make it lot more interesting to read.&lt;/p&gt;&#xA;&lt;p&gt;BTW: for my own blog here, I don&amp;rsquo;t use AI for writing/drafting, but I do have it help with&#xA;proofreading/editing sometimes. For some of my projects, I do have it help out with marketing copy,&#xA;but even there I steer it with my own voice guidelines and give a bit more personality/context&#xA;than just its default generic style of prose.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ai/&#34;&gt;#ai&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>0xdead10cc</title>
      <link>https://ivanthinking.net/2026/07/23/0xdead10cc/</link>
      <pubDate>Thu, 23 Jul 2026 12:46:38 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/07/23/0xdead10cc/</guid>
      <category>software-development</category>
      <category>ios-development</category>
      <category>nerdtower</category>
      <description>&lt;p&gt;Debugging a crash on the new Little Memory 2.0 app today, and found a crash report with the termination code: &lt;code&gt;0xdead10cc&lt;/code&gt;.&#xA;Interesting hex code, or rather, &lt;a href=&#34;https://en.wikipedia.org/wiki/Hexspeak&#34;&gt;hexspeak&lt;/a&gt;.&#xA;Seems there&amp;rsquo;s &lt;a href=&#34;https://ivanthinking.net/2022/06/07/coredata-all-that-is-left-to-us-is-honor/&#34;&gt;some fun&lt;/a&gt; Apple&amp;rsquo;s done with termination codes:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;0xdead10cc&lt;/code&gt; - &amp;ldquo;dead lock&amp;rdquo; - an application holds on to a system resource while running in the background&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;0x8badf00d&lt;/code&gt; — &amp;ldquo;ate bad food&amp;rdquo; — the watchdog kill for blocking the main thread too long at launch/resume&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;0xc00010ff&lt;/code&gt; — &amp;ldquo;cool off&amp;rdquo; — killed for thermal pressure&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;0xdeadfa11&lt;/code&gt; — &amp;ldquo;dead fall&amp;rdquo; — user force-quit an unresponsive app&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;0xbad22222&lt;/code&gt; - &amp;ldquo;bad too repeatedly&amp;rdquo; - a VoIP application has been terminated by iOS because it resumed too frequently&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;0xbaaaaaad&lt;/code&gt; — &amp;ldquo;baaaaad&amp;rdquo; - the log is a stackshot of the entire system, not a crash report&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;0xdeadfeed&lt;/code&gt; - &amp;ldquo;dead feed&amp;rdquo; - crash when a timeout occurs spawning a service&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Ready Time&#39;s on TinyLaunch</title>
      <link>https://ivanthinking.net/2026/06/01/ready-times-on-tinylaunch/</link>
      <pubDate>Mon, 01 Jun 2026 16:20:24 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/06/01/ready-times-on-tinylaunch/</guid>
      <category>ready-time</category>
      <category>ios-development</category>
      <category>nerdtower</category>
      <description>&lt;p&gt;&lt;a href=&#34;https://getreadytime.com/&#34;&gt;Ready Time&lt;/a&gt; is &lt;a href=&#34;https://www.tinylaunch.com/launch/13954&#34;&gt;moving up the ranks at TinyLaunch&lt;/a&gt;. Go give it an upvote!&lt;/p&gt;&#xA;&lt;a href=&#34;https://tinylaunch.com&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&#xA;    &lt;img src=&#34;https://ivanthinking.net/images/tinylaunch-featured-on.svg&#34; alt=&#34;TinyLaunch Badge&#34; style=&#34;width:202px; height:auto;&#34; /&gt;&#xA;&lt;/a&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/ready-time/&#34;&gt;/ready-time&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Ready Time 3.3: Travel Time</title>
      <link>https://ivanthinking.net/2026/05/21/ready-time-3.3-travel-time/</link>
      <pubDate>Thu, 21 May 2026 10:21:57 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/05/21/ready-time-3.3-travel-time/</guid>
      <category>ready-time</category>
      <category>ios-development</category>
      <category>nerdtower</category>
      <description>&lt;p&gt;I built &lt;a href=&#34;https://getreadytime.com&#34;&gt;Ready Time&lt;/a&gt; to take the guesswork out of leaving on time. You tell it when you need to be somewhere and it tells you when to start each step.&lt;/p&gt;&#xA;&lt;p&gt;There was one part of setting up steps that&amp;rsquo;s always been tedious and annoying: travel time.&lt;/p&gt;&#xA;&lt;p&gt;Drive to the doctor, bike to soccer, walk to dinner. I&amp;rsquo;d switch to&#xA;Apple Maps, search for the address, get the travel time, come back, put in the step duration.&lt;/p&gt;&#xA;&lt;p&gt;&lt;em&gt;There had to be a better way.&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;I mean, I built Ready Time to save time and not have to do tedious work, right? Welp, here&#xA;I was doing this tedious back and forth between the app and Maps and wasting my time.&lt;/p&gt;&#xA;&lt;h2 id=&#34;whats-new&#34;&gt;&#xA;  What&amp;rsquo;s new&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#whats-new&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Ready Time 3.3 adds Travel to steps. When you edit a step, pick a place,&#xA;choose driving, cycling, or walking, and the travel time becomes part of the plan.&#xA;Change the destination or the mode and the rest of the plan adjusts.&#xA;No more jumping back and forth between apps, needing to remember, or even entering the duration anymore.&lt;/p&gt;&#xA;&lt;p&gt;One less thing to keep in your head, and one less reason to be running late.&lt;/p&gt;&#xA;&lt;h2 id=&#34;onward&#34;&gt;&#xA;  Onward&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#onward&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;d love to hear from you, especially if you find a use for this I didn&amp;rsquo;t think of. &lt;a href=&#34;mailto:support@getreadytime.com&#34;&gt;Drop me a&#xA;note&lt;/a&gt;.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/ready-time/&#34;&gt;/ready-time&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Next Chapter of Little Memory</title>
      <link>https://ivanthinking.net/2026/04/28/next-chapter-of-little-memory/</link>
      <pubDate>Tue, 28 Apr 2026 21:17:59 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/04/28/next-chapter-of-little-memory/</guid>
      <category>little-memory</category>
      <category>ios-development</category>
      <description>&lt;p&gt;I&amp;rsquo;ve been in the weeds getting &lt;a href=&#34;https://thelittlememory.com/&#34;&gt;Little Memory&lt;/a&gt; 2.0 ready. Well, honestly, it actually never had a proper&#xA;1.0 version!&lt;/p&gt;&#xA;&lt;p&gt;One thing that&amp;rsquo;s bothered me over the years is having all these entries in my database. I&amp;rsquo;d rather have&#xA;people&amp;rsquo;s memories storied on their own devices where they&amp;rsquo;re safe and accessible even when offline.&lt;/p&gt;&#xA;&lt;p&gt;I have most of the bones of 2.0 ready, but if you&amp;rsquo;ve ever worked in software, you know that last 20%&#xA;(and sometimes 5-10%) can be the trickiest to make sure everything&amp;rsquo;s buttoned up and ready. I think I&amp;rsquo;m&#xA;at that stage now.&lt;/p&gt;&#xA;&lt;p&gt;Soon, I&amp;rsquo;ll be asking some long-time users if they&amp;rsquo;d like to try out a TestFlight build to get some&#xA;feedback. I&amp;rsquo;m excited to share it with the world.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/little-memory/&#34;&gt;/little-memory&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Ready Time 3.2: Shared Plans and Daily Repeating Plans</title>
      <link>https://ivanthinking.net/2026/04/28/ready-time-3.2-shared-plans-and-daily-repeating-plans/</link>
      <pubDate>Tue, 28 Apr 2026 14:39:12 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/04/28/ready-time-3.2-shared-plans-and-daily-repeating-plans/</guid>
      <category>ready-time</category>
      <category>ios-development</category>
      <category>nerdtower</category>
      <description>&lt;p&gt;This one&amp;rsquo;s been a long time coming. Ready Time 3.2 is here!&lt;/p&gt;&#xA;&lt;h2 id=&#34;shared-plans&#34;&gt;&#xA;  Shared Plans&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#shared-plans&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Having a backwards planning tool has been great, and my wife and I use it all the time to make sure we&#xA;&lt;a href=&#34;https://getreadytime.com/routines/leaving-the-house/&#34;&gt;get out the door on time&lt;/a&gt;. But we found it redundant to each have to setup our own plans, especially if&#xA;we were going to be on the same flight or going to the same event together (which happens a lot).&lt;/p&gt;&#xA;&lt;p&gt;We used to take screenshots for each others&amp;rsquo; plans so we knew what our plan was. But we knew there&#xA;had to be a better way.&lt;/p&gt;&#xA;&lt;p&gt;Enter: shared plans. With shared plans, you can now coordinate using the same plan, and you both get alerts for each step along the way to the deadline. No more screenshots that cluttered our photo libraries.&#xA;Just share a plan to someone and they&amp;rsquo;ll get notifications just like you, and if you update the plan,&#xA;they&amp;rsquo;ll see the updates and their alerts will update too.&lt;/p&gt;&#xA;&lt;h2 id=&#34;daily-repeating-plans&#34;&gt;&#xA;  Daily Repeating Plans&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#daily-repeating-plans&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;We previously only had weekly repeating plans. This was useful for things like weekly meetings or&#xA;regular appointments I had on a specific day of the week. But this wasn&amp;rsquo;t necessarily that helpful for&#xA;plans where I wanted it to repeat every day of the week: morning routines, &lt;a href=&#34;https://getreadytime.com/routines/toddler-bedtime/&#34;&gt;baby&amp;rsquo;s bedtime routine&lt;/a&gt;,&#xA;workout routines. Now you can setup a plan to do every day without needing to have to duplicate or re-enter&#xA;them each day.&lt;/p&gt;&#xA;&lt;h2 id=&#34;templates&#34;&gt;&#xA;  Templates&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#templates&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;For new users, some folks were finding it tricky to get started. We&amp;rsquo;ve got some more ideas turning&#xA;in our heads on how to make this experience better, but our first stab at it now is to get you templates.&#xA;With a template, there are pre-defined sequences of steps like &amp;ldquo;Catch a Flight&amp;rdquo; or &amp;ldquo;Dinner Party.&amp;rdquo; Some folks liked it so much that they thought it would be good to have it for everyone, not just new users. So&#xA;now you can select a template to start new plans with.&lt;/p&gt;&#xA;&lt;h2 id=&#34;onward&#34;&gt;&#xA;  Onward&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#onward&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;We hope you enjoy the new features. We&amp;rsquo;re especially excited about shared plans, as it&amp;rsquo;s something we&amp;rsquo;ve&#xA;been working toward for a long time. There were lot of pieces that had to come into play to make it work,&#xA;so it&amp;rsquo;s been awesome seeing it all come together.&lt;/p&gt;&#xA;&lt;p&gt;Feedback? Bugs? Comments?&lt;/p&gt;&#xA;&lt;p&gt;As always, &lt;a href=&#34;mailto:support@getreadytime.com&#34;&gt;we love hearing from you&lt;/a&gt;!&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/ready-time/&#34;&gt;/ready-time&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Technology is not enough</title>
      <link>https://ivanthinking.net/2026/04/20/technology-is-not-enough/</link>
      <pubDate>Mon, 20 Apr 2026 16:47:31 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/04/20/technology-is-not-enough/</guid>
      <category>software-development</category>
      <category>ios-development</category>
      <category>design</category>
      <description>&lt;p&gt;&lt;a href=&#34;https://daringfireball.net/2023/02/making_our_hearts_sing&#34;&gt;Daring Fireball&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Either you know that software can be art, and often should be, or you think what I’m talking about here is akin to astrology. One thing I learned long ago is that people who prioritize &lt;a href=&#34;https://ivanthinking.net/tags/design/&#34;&gt;design&lt;/a&gt;, UI, and UX in the software they prefer can empathize with and understand the choices made by people who prioritize other factors (e.g. raw feature count, or the ability to tinker with their software at the system level, or software being free-of-charge). But it doesn’t work the other way: most people who prioritize other things can’t fathom why anyone cares deeply about design/UI/UX &lt;em&gt;because they don’t perceive it&lt;/em&gt;. Thus they chalk up iOS and native Mac-app enthusiasm to being hypnotized by marketing.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;What I think also ends up happening is that good design is something that becomes invisible when it&amp;rsquo;s good. Simple/good designs disappear when they take practically no cognitive load; well, except for the times when you want to purposely introduce friction for the user to slow them down like confirmation dialogues when they&amp;rsquo;re doing something dangerous.&lt;/p&gt;&#xA;&lt;p&gt;This &amp;ldquo;invisible&amp;rdquo; is akin to sysadmins who are never seen or considered until the system breaks down and everyone&amp;rsquo;s upset that things aren&amp;rsquo;t working. Well, no one was &lt;a href=&#34;https://sysadminday.com&#34;&gt;busy saying thanks&lt;/a&gt; when the sysadmins were keeping things running smoothly without any issues.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/design/&#34;&gt;#design&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Moving along on Ready Time relaunch</title>
      <link>https://ivanthinking.net/2026/04/15/moving-along-on-ready-time-relaunch/</link>
      <pubDate>Wed, 15 Apr 2026 22:33:19 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/04/15/moving-along-on-ready-time-relaunch/</guid>
      <category>ready-time</category>
      <category>ios-development</category>
      <category>nerdtower</category>
      <description>&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.reddit.com/r/SideProject/comments/1sml19i/relaunched_my_app_backward_planner_for_people_who/&#34;&gt;Posted on r/SideProject&lt;/a&gt;. Had a little hiccup since my original post was filtered by reddit&amp;rsquo;s spam filter (boo!), but re-submitted a day later with fewer links in the post. I&amp;rsquo;d also been dormant on reddit for a while, so it may have smelled like spam with the initial post.&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Found out there&amp;rsquo;s an &lt;a href=&#34;https://www.readyby.app&#34;&gt;app by the name of ReadyBy&lt;/a&gt; with the same concept. After checking it out, it reinforces that I think my change from subscription to one-time premium upgrade makes sense (ReadyBy is a subscription model).&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;Published &lt;a href=&#34;https://ivanthinking.net/2026/04/14/3am-bring-the-car-seat/&#34;&gt;blog post&lt;/a&gt; about how Ready Time saved the day when wife went into labor. This has been a long time coming. Finally buckled down to write this thing out.&lt;/li&gt;&#xA;&lt;li&gt;Submitted to &lt;a href=&#34;https://indieappcatalog.com&#34;&gt;Indie App Catalog&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Shared with some friends who&amp;rsquo;ve been giving me some feedback on the app.&lt;/li&gt;&#xA;&lt;li&gt;Been warming up my profile on &lt;a href=&#34;https://www.indiehackers.com&#34;&gt;Indie Hackers&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/ready-time/&#34;&gt;/ready-time&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>It&#39;s like gonna have like this tab</title>
      <link>https://ivanthinking.net/2026/04/14/its-like-gonna-have-like-this-tab/</link>
      <pubDate>Tue, 14 Apr 2026 11:30:37 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/04/14/its-like-gonna-have-like-this-tab/</guid>
      <category>software-development</category>
      <category>design</category>
      <category>ios-development</category>
      <category>parenting</category>
      <category>ai</category>
      <description>&lt;p&gt;&lt;a href=&#34;https://www.swiftjectivec.com/kids-and-vibe-coding-ios-apps/&#34;&gt;Kids and Vibe Coding&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;It’s easy for &lt;em&gt;us&lt;/em&gt; to spot the issues. However, the bit that’s surprised me, though, is that they are…aware of it? Even as a 9 year old. Is this a potential byproduct of growing up in the digital age? They know it doesn’t feel right, and they can’t really say why yet, they just know it feels off.&lt;/p&gt;&#xA;&lt;p&gt;That’s hardly surprising, as &lt;a href=&#34;https://ivanthinking.net/tags/design/&#34;&gt;design&lt;/a&gt; and user experience are things that take years and years to develop.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Oh, what a fun thing to do with kids!&lt;/p&gt;&#xA;&lt;p&gt;When I&amp;rsquo;m working on making an interface simple and easy to use, this &amp;ldquo;feels off&amp;rdquo; is something I often&#xA;find myself bumping into. I know something is wrong, but I have to identify it and work out a solution.&#xA;Oftentimes, once you figure out the right solution, it feels obvious. Then, when you show it to other&#xA;folks, it can seem trite or overly simplistic about what the solution was.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;You can throw all the skill files you want at an LLM (And you should! They’re great!), but they still have critical limitations. Take text, for example. What would you instinctively do if you have a view whose text was truncated, clipping, or otherwise not fitting into a container?&lt;/p&gt;&#xA;&lt;p&gt;The layman would say, “Can you make this text not clip?” The hardened iOS veteran might say, “Apply a .minimumScaleFactor modifier to this text, and ensure you’re using a Text style and not hard coding the font size.”&lt;/p&gt;&#xA;&lt;p&gt;Off the cuff? Visually, you may get the same result at first. But, well — if you know, you know. One is objectively better. And those of us who can speak the language will still be making the best apps.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;I don&amp;rsquo;t fully trust Claude with interaction design (yet?), at least on a high level. Maybe it&amp;rsquo;s just&#xA;that it doesn&amp;rsquo;t really understand the pain-points that humans have to go through when dealing with&#xA;UX/UI. It seems to be able to tell me that some things are on screen and where they are, but deciding how&#xA;an interaction feels or how it feels to use it seems less fluent about. We may get there, but at least&#xA;right now I do a good amount of hand-holding to direct it with design rather than let it do too much of&#xA;UX. I do, however, often ask it for design ideas, which I work together with Claude when brainstorming&#xA;solutions.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/design/&#34;&gt;#design&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/parenting/&#34;&gt;#parenting&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ai/&#34;&gt;#ai&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Ready Time</title>
      <link>https://ivanthinking.net/2026/04/07/ready-time/</link>
      <pubDate>Tue, 07 Apr 2026 16:32:03 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/04/07/ready-time/</guid>
      <category>ready-time</category>
      <category>nerdtower</category>
      <category>ios-development</category>
      <description>&lt;p&gt;We&amp;rsquo;re always underestimating how long things take:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://getreadytime.com/routines/school-drop-off/&#34;&gt;mornings with a kid&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://getreadytime.com/routines/pre-flight-routine/&#34;&gt;catching a flight&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://getreadytime.com/routines/leaving-the-house/&#34;&gt;getting out the door for dinner&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://getreadytime.com/routines/doctor-appointment/&#34;&gt;appointments you can&amp;rsquo;t be late to&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;It always feels like there&amp;rsquo;s enough time until suddenly there isn&amp;rsquo;t.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://getreadytime.com&#34;&gt;Ready Time&lt;/a&gt; works backward from your deadline. You add your steps: shower, pack lunch, get the kid&#xA;dressed, drive. Then you set when you need to be done. It calculates when to start each step and sends you a&#xA;notification when it&amp;rsquo;s time.&lt;/p&gt;&#xA;&lt;p&gt;I built it because my family needed it: I liked getting to the airport super early,&#xA;and my wife preferred to arrive just in time for departure. Originally, it was called Little Countdown,&#xA;but folks kept thinking it was a countdown to new years eve ball drops, which wasn&amp;rsquo;t really what&#xA;I was going for. And so, today we&amp;rsquo;re introducing the new name which better captures the spirit of&#xA;the app: &lt;strong&gt;Ready Time&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;p&gt;I hope Ready Time can help you and your family too.&lt;/p&gt;&#xA;&lt;p&gt;&amp;ndash; Ivan&lt;/p&gt;&#xA;&lt;p&gt;Available now in the App Store.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://apps.apple.com/us/app/ready-time/id1412470365?pt=265310&amp;ct=ready-time&amp;mt=8&#34;&gt;&lt;img src=&#34;https://ivanthinking.net/images/download-on-the-app-store.svg&#34; alt=&#34;Download on the App Store&#34; style=&#34;height:44px&#34;&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;image-row&#34;&gt;&#xA;  &#xA;  &#xA;    &#xA;    &#xA;    &#xA;    &#xA;      &#xA;      &#xA;        &#xA;      &#xA;      &#xA;    &#xA;    &lt;img src=&#34;https://ivanthinking.net/2026/04/07/ready-time/01_HeroFlightPlan_hu_83fe56bad77a2dd9.webp&#34; alt=&#34;Ready Time - Flight Plan&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;&gt;&#xA;  &#xA;    &#xA;    &#xA;    &#xA;    &#xA;      &#xA;      &#xA;        &#xA;      &#xA;      &#xA;    &#xA;    &lt;img src=&#34;https://ivanthinking.net/2026/04/07/ready-time/03_Notifications_hu_6cef715ea9634d0e.webp&#34; alt=&#34;Ready Time - Notifications&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;&gt;&#xA;  &#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/ready-time/&#34;&gt;/ready-time&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Getting Ready for Ready Time</title>
      <link>https://ivanthinking.net/2026/03/30/getting-ready-for-ready-time/</link>
      <pubDate>Mon, 30 Mar 2026 20:53:39 -0700</pubDate>
      <guid>https://ivanthinking.net/2026/03/30/getting-ready-for-ready-time/</guid>
      <category>ready-time</category>
      <category>ios-development</category>
      <category>nerdtower</category>
      <description>&lt;p&gt;&lt;a href=&#34;https://getreadytime.com&#34;&gt;Ready Time&lt;/a&gt; is the rebranded name for &lt;a href=&#34;https://ivanthinking.net/categories/little-countdown/&#34;&gt;Little Countdown&lt;/a&gt;. Domain: check. Website: &lt;a href=&#34;https://getreadytime.com&#34;&gt;check&lt;/a&gt;. App update: almost.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/ready-time/&#34;&gt;/ready-time&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Time Lock, New Premium Feature</title>
      <link>https://ivanthinking.net/2026/01/14/time-lock-new-premium-feature/</link>
      <pubDate>Wed, 14 Jan 2026 06:11:12 +0000</pubDate>
      <guid>https://ivanthinking.net/2026/01/14/time-lock-new-premium-feature/</guid>
      <category>ready-time</category>
      <category>software-development</category>
      <category>nerdtower</category>
      <category>ios-development</category>
      <description>&lt;p&gt;Sometimes you need a task to happen at a specific time. Maybe you need to leave for the airport at exactly 3:00 PM, or you have a hard deadline to be at a meeting at 10:00 AM. Little Countdown normally schedules all your tasks backward from your deadline, but that doesn&amp;rsquo;t work when one task needs to happen at a fixed time.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Time Lock&lt;/strong&gt; lets you set a fixed start time for any Task in your Countdown. When you lock a task to a specific time, Little Countdown schedules the rest of your timeline around it:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Tasks before the time lock&lt;/strong&gt; are scheduled backward from your deadline (as usual)&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Tasks after the time lock&lt;/strong&gt; are scheduled forward from the lock time&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;This gives you the flexibility to plan around fixed commitments while still letting the app handle all the math for everything else.&lt;/p&gt;&#xA;&lt;h2 id=&#34;example&#34;&gt;&#xA;  Example&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#example&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;You have a flight tomorrow at 6:00 PM, and you need to leave for the airport at exactly 3:00 PM. Here&amp;rsquo;s how you&amp;rsquo;d set it up:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Create a Countdown with your flight as the deadline (6:00 PM)&lt;/li&gt;&#xA;&lt;li&gt;Add your tasks: &lt;a href=&#34;https://getreadytime.com/routines/packing-night-before-trip/&#34;&gt;&amp;ldquo;Pack bags&amp;rdquo;&lt;/a&gt; (30 min), &amp;ldquo;Call Lyft&amp;rdquo; (5 min), &amp;ldquo;Drive to airport&amp;rdquo; (45 min), &amp;ldquo;Get through TSA&amp;rdquo; (30 min), &amp;ldquo;Find gate&amp;rdquo; (15 min)&lt;/li&gt;&#xA;&lt;li&gt;Time Lock the &amp;ldquo;Call Lyft&amp;rdquo; task to 3:00 PM&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Little Countdown will:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Schedule &amp;ldquo;Pack bags&amp;rdquo; to finish just before 3:00 PM&lt;/li&gt;&#xA;&lt;li&gt;Lock &amp;ldquo;Call Lyft&amp;rdquo; to start at 3:00 PM&lt;/li&gt;&#xA;&lt;li&gt;Schedule everything after (drive, TSA, find gate) forward from 3:00 PM to ensure you make your 6:00 PM flight&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;You get alerts for everything, and you don&amp;rsquo;t have to calculate when to start earlier tasks—the app handles it all.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-this-matters&#34;&gt;&#xA;  Why This Matters&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#why-this-matters&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Time Lock solves a real problem we’ve heard from users: sometimes you need to work around fixed times in your schedule. Maybe it&amp;rsquo;s a &lt;a href=&#34;https://getreadytime.com/routines/picking-up-kids-on-time/&#34;&gt;carpool pickup time&lt;/a&gt;, a specific appointment slot, or a deadline that can&amp;rsquo;t move. Before Time Lock, you&amp;rsquo;d have to manually calculate when to start earlier tasks, which defeats the purpose of having Little Countdown do the math for you.&lt;/p&gt;&#xA;&lt;p&gt;Now you can set that one fixed point in your timeline, and everything else falls into place automatically. It&amp;rsquo;s one less thing to worry about, which is exactly what Little Countdown is designed to help with.&lt;/p&gt;&#xA;&lt;p&gt;Time Lock is available now as part of&#xA;&lt;a href=&#34;https://apps.apple.com/us/app/ready-time/id1412470365?pt=265310&amp;ct=time-lock-new-premium-feature&amp;mt=8&#34;&gt;Little Countdown&lt;/a&gt;&#xA;Premium. If you&amp;rsquo;re already a Premium subscriber, you&amp;rsquo;ll see the Time Lock option when editing any task (except the last one in your Countdown, since that&amp;rsquo;s already determined by your deadline).&lt;/p&gt;&#xA;&lt;p&gt;We hope this makes planning your day even easier. As always, &lt;a href=&#34;mailto:feedback@littlecountdown.com?subject=Little%20Countdown%20Time%20Lock&#34;&gt;we&amp;rsquo;d love to hear your feedback&lt;/a&gt;!&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/ready-time/&#34;&gt;/ready-time&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Development login credentials in Xcode</title>
      <link>https://ivanthinking.net/2023/04/24/development-login-credentials-in-xcode/</link>
      <pubDate>Mon, 24 Apr 2023 12:08:51 -0400</pubDate>
      <guid>https://ivanthinking.net/2023/04/24/development-login-credentials-in-xcode/</guid>
      <category>software-development</category>
      <category>nerdtower</category>
      <category>apple</category>
      <category>ios-development</category>
      <description>&lt;p&gt;&lt;a href=&#34;https://www.danijelavrzan.com/posts/2023/04/login-xcode-breakpoint/&#34;&gt;Danijela Vrzan&lt;/a&gt; posted&#xA;a very cool solution to streamlining entering login credentials in Xcode and the iOS Simulator&#xA;using breakpoints. I&amp;rsquo;ll admit that I probably haven&amp;rsquo;t fully unlocked the&#xA;full potential of Xcode breakpoints in my development workflow.&lt;/p&gt;&#xA;&lt;p&gt;If you&amp;rsquo;re an app developer, no doubt you&amp;rsquo;ve run into the case where you&amp;rsquo;re tediously&#xA;typing in your auth username and passwords every time you rebuild/relaunch&#xA;the Simulator. It gets old pretty quickly when you&amp;rsquo;re trying to quickly&#xA;make updates or fix a bug in your app.&lt;/p&gt;&#xA;&lt;p&gt;You might consider hard coding some credentials temporarily in your code, but&#xA;really, you know that&amp;rsquo;s not what you should be doing, as it clutters up your&#xA;code and you run the risk of forgetting to remove that code before releasing&#xA;the app (eep!).&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve setup my own solution on how I like to safetly handle this repetitive&#xA;task in development and avoid needing to type in credentials with the Simulator&#xA;keyboard.&lt;/p&gt;&#xA;&lt;h2 id=&#34;development-scheme&#34;&gt;&#xA;  Development Scheme&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#development-scheme&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;For every new project, I setup a dedicated &lt;a href=&#34;https://developer.apple.com/documentation/xcode/customizing-the-build-schemes-for-a-project/&#34;&gt;scheme in Xcode&lt;/a&gt; for development. The&#xA;way I set it up is:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;I like to name this scheme something like &lt;code&gt;Appname (dev)&lt;/code&gt; or, if I&amp;rsquo;m working against a staging environment, I&amp;rsquo;ll use &lt;code&gt;Appname (staging)&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;In &lt;code&gt;Run &amp;gt; Arguments&lt;/code&gt;, add environment variables with keys such as:&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;AUTH_DEFAULT_USERNAME&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;AUTH_DEFAULT_PASSWORD&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;Don&amp;rsquo;t select &lt;strong&gt;Shared&lt;/strong&gt; so you don&amp;rsquo;t commit these values into &lt;code&gt;git&lt;/code&gt;, especially if these are credentials for a production environment.&lt;/li&gt;&#xA;&lt;li&gt;You could also have multiples of the above variables to quickly switch between different credentials. Xcode lets us choose which ones we want enabled by selecting the checkmark&#xA;next to each variable.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;pre-filling-the-auth-form&#34;&gt;&#xA;  Pre-filling the Auth Form&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#pre-filling-the-auth-form&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;SwiftUI&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;env&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ProcessInfo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;processInfo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;environment&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;LoginForm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;View&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;State&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;username&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;env&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;AUTH_DEFAULT_USERNAME&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;??&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;State&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;password&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;env&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;AUTH_DEFAULT_PASSWORD&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;??&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;// ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;build-and-run&#34;&gt;&#xA;  Build and Run&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#build-and-run&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Now, with the development scheme you created above enabled, you can run the app and the&#xA;login form should be pre-filled with the auth credentials in the environment&#xA;variables.&lt;/p&gt;&#xA;&lt;p&gt;&lt;em&gt;Voila!&lt;/em&gt; No more tedious typing and no credentials stored in code.&lt;/p&gt;&#xA;&lt;p&gt;As noted previously, you can add multiple credentials into a single scheme, or&#xA;you could do something like create a new scheme for different environments as well.&lt;/p&gt;&#xA;&lt;h2 id=&#34;onward&#34;&gt;&#xA;  Onward&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#onward&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I love seeing tips like the one &lt;a href=&#34;https://twitter.com/dvrzan&#34;&gt;Danijela&lt;/a&gt; has to&#xA;speed up the develoment workflow. One plus of her method is that it doesn&amp;rsquo;t require&#xA;you to make code changes like what we&amp;rsquo;ve done here. Her method&amp;rsquo;s certainly made&#xA;me think more about working with breakpoints in my work.&lt;/p&gt;&#xA;&lt;p&gt;If we can speed up and optimize the development/test&#xA;cycle without too much hassle, I consider it a win.&lt;/p&gt;&#xA;&lt;p&gt;I hope my little tip here can help you out in your own use of Xcode and app&#xA;development. Let me know if you&amp;rsquo;ve got more tips like this or if it helped you out.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/apple/&#34;&gt;#apple&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Little Countdown 2.0</title>
      <link>https://ivanthinking.net/2022/11/03/little-countdown-2.0/</link>
      <pubDate>Thu, 03 Nov 2022 13:28:18 -0400</pubDate>
      <guid>https://ivanthinking.net/2022/11/03/little-countdown-2.0/</guid>
      <category>software-development</category>
      <category>ready-time</category>
      <category>nerdtower</category>
      <category>ios-development</category>
      <description>&lt;p&gt;&lt;img class=&#34;page-img&#34; src=&#34;https://ivanthinking.net/images/countdown/preview-flight_hu_a57ee314903dde26.gif&#34; alt=&#34;Preview&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;  /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://apps.apple.com/us/app/ready-time/id1412470365?pt=265310&amp;ct=little-countdown-2&amp;mt=8&#34;&gt;Little Countdown&lt;/a&gt;&#xA;2.0 is now available for iOS. Just in time for helping you stay on time and stress free for your holiday travels. We rebuilt the app from the ground up and streamlined adding and editing Countdowns.&lt;/p&gt;&#xA;&lt;p&gt;Your meeting&amp;rsquo;s at 9. Packing up for the kids takes 15 minutes. Dropping them off takes half an hour. Getting to the office is another 20 minutes. What time do you head out the door?&lt;/p&gt;&#xA;&lt;p&gt;Leave math problems for school.&#xA;&lt;a href=&#34;https://apps.apple.com/us/app/ready-time/id1412470365?pt=265310&amp;ct=little-countdown-2&amp;mt=8&#34;&gt;Use Little Countdown&lt;/a&gt;&#xA;.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;We did a lot of housecleaning to make Countdown better:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Edit Tasks inline in the editor&lt;/li&gt;&#xA;&lt;li&gt;Swipe to delete Tasks or Countdowns&lt;/li&gt;&#xA;&lt;li&gt;Hold and drag to reorder Tasks&lt;/li&gt;&#xA;&lt;li&gt;Alerts now show the next Task&lt;/li&gt;&#xA;&lt;li&gt;Autocomplete of Countdown title is now more responsive&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.mikwat.com&#34;&gt;Mike Watts&lt;/a&gt; and I had a blast rebuilding the app from the ground up for a better and more streamlined experience. We hope you enjoy it.&lt;/p&gt;&#xA;&lt;p&gt;Onward!&lt;/p&gt;&#xA;&lt;h2 id=&#34;more&#34;&gt;&#xA;  More&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#more&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://twitter.com/littlecountdown&#34;&gt;Twitter&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/categories/ready-time/&#34;&gt;/ready-time&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>iOS Developer Resources</title>
      <link>https://ivanthinking.net/2022/10/04/ios-developer-resources/</link>
      <pubDate>Tue, 04 Oct 2022 23:58:18 -0400</pubDate>
      <guid>https://ivanthinking.net/2022/10/04/ios-developer-resources/</guid>
      <category>software-development</category>
      <category>nerdtower</category>
      <category>apple</category>
      <category>ios-development</category>
      <description>&lt;h2 id=&#34;developer-news-and-community&#34;&gt;&#xA;  Developer News and Community&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#developer-news-and-community&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;If I could give any advice to all new iOS developers, it&amp;rsquo;s that they should&#xA;keep up with the ever-changing iOS ecosystem. Apple pushes out a new&#xA;version of iOS every year, so it&amp;rsquo;s important (and exciting!) to keep up&#xA;with the latest going on in the world of iOS Developmenet. I like to keep&#xA;my ears on the ground with these resources.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://iosdevweekly.com&#34;&gt;iOS Dev Weekly&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://ios-developers.io&#34;&gt;iOS Developers HQ&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;developer-blogs&#34;&gt;&#xA;  Developer Blogs&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#developer-blogs&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;There&amp;rsquo;s a lot of noise on the web with developers asking questions on how&#xA;to write code for iOS. Be wary of code you see on Stack Overflow: often it&#xA;feels like the blind leading the blind there. These blogs are sources I&amp;rsquo;ve&#xA;consistently found to have good and useful patterns that have helped me&#xA;out and to improve my own skills.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.hackingwithswift.com/articles&#34;&gt;Hacking with Swift&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.swiftbysundell.com&#34;&gt;Swift by Sundell&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.donnywals.com/the-blog/&#34;&gt;Donny Wals&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.avanderlee.com/recents/&#34;&gt;SwiftLee&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://swiftwithmajid.com&#34;&gt;Swift with Majid&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.objc.io/blog/&#34;&gt;objc.io&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://nshipster.com&#34;&gt;NSHipster&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;other-news&#34;&gt;&#xA;  Other News&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#other-news&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://daringfireball.net&#34;&gt;Daring Fireball&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.macrumors.com&#34;&gt;MacRumors&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://9to5mac.com&#34;&gt;9to5Mac&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;apple-resources&#34;&gt;&#xA;  Apple Resources&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#apple-resources&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://appstoreconnect.apple.com/&#34;&gt;App Store Connect&lt;/a&gt;: every iOS developer&amp;rsquo;s favorite site to re-login to.&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://developer.apple.com&#34;&gt;Apple Developer&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/design/human-interface-guidelines/guidelines/overview&#34;&gt;Human Interface Guidelines&lt;/a&gt; (aka the HIG)&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons#specifications&#34;&gt;App Icon Sizes&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/design/human-interface-guidelines/foundations/typography/#specifications&#34;&gt;Dynamic Type Sizes&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://help.apple.com/app-store-connect/#/devd274dd925&#34;&gt;Screenshot Specifications&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners&#34;&gt;Smart App Banners&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;software&#34;&gt;&#xA;  Software&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#software&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://apps.apple.com/us/app/app-store-connect/id1234793120&#34;&gt;App Store Connect&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://apps.apple.com/us/app/apple-developer/id640199958&#34;&gt;Apple Developer&lt;/a&gt;: watch WWDC videos.&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/sf-symbols/&#34;&gt;SF Symbols&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://xcodereleases.com&#34;&gt;Xcode Releases&lt;/a&gt;: I don&amp;rsquo;t like updating from the App Store so I can keep multiple copies of Xcode on my machine. Instead, I use the direct download links on this site.&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.postman.com/downloads/&#34;&gt;Postman&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;other&#34;&gt;&#xA;  Other&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#other&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.osstatus.com&#34;&gt;OSStatus.com&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://frankrausch.com/ios-navigation&#34;&gt;Modern iOS Navigation Patterns&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.iosfontsizes.com&#34;&gt;iOS Font Sizes&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://mar.codes/apple-colors&#34;&gt;Apple Colors&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://fuckingapproachableswiftconcurrency.com/en/&#34;&gt;Fucking Approachable Swift Concurrency&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/system-status/&#34;&gt;Apple System Status&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/apple/&#34;&gt;#apple&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>SwiftUI: Link phone numbers, emails, URLs in Text without Markdown</title>
      <link>https://ivanthinking.net/2022/09/20/swiftui-link-phone-numbers-emails-urls-in-text-without-markdown/</link>
      <pubDate>Tue, 20 Sep 2022 17:54:51 -0400</pubDate>
      <guid>https://ivanthinking.net/2022/09/20/swiftui-link-phone-numbers-emails-urls-in-text-without-markdown/</guid>
      <category>software-development</category>
      <category>nerdtower</category>
      <category>apple</category>
      <category>ios-development</category>
      <description>&lt;p&gt;SwiftUI provides an easy way to link URLs and emails inside of a &lt;code&gt;Text&lt;/code&gt; view&#xA;using &lt;code&gt;AttributedString&lt;/code&gt;&amp;rsquo;s markdown initializer:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;text&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;Jenny&amp;#39;s at 867-5309.&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34; Email her at jenny@tutone.net.&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34; Find out more: https://en.wikipedia.org/wiki/867-5309/Jenny&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;VStack&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;Text&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;try&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;AttributedString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;markdown&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, this has a few shortcomings:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;the phone number isn&amp;rsquo;t linked/tappable&lt;/li&gt;&#xA;&lt;li&gt;we may not want all the markdown rendering: bullets (&lt;code&gt;*&lt;/code&gt;) and brackets (&lt;code&gt;[]&lt;/code&gt;) in the text may end up rendering with unexpected results&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;We can use &lt;code&gt;NSDataDetector&lt;/code&gt; instead to help us construct our own&#xA;&lt;code&gt;AttributedString&lt;/code&gt; that can detect phone numbers and also prevent the user&#xA;from accidentally injecting markdown:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;dataDetector&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;NSDataDetector&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;types&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;NSTextCheckingResult&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;CheckingType&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;link&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;phoneNumber&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;init&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;types&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;types&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rawValue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;JennyView&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;View&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;body&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;some&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;View&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;VStack&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;Text&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;attributedString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;attributedString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;AttributedString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;attributed&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;AttributedString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;fullRange&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;NSMakeRange&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;count&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;matches&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;dataDetector&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matches&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;in&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;options&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[],&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;range&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fullRange&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;guard&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;matches&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;isEmpty&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;result&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;matches&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;k&#34;&gt;guard&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;range&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Range&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AttributedString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Index&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;range&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;in&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;attributed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                &lt;span class=&#34;k&#34;&gt;continue&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;k&#34;&gt;switch&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;resultType&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;k&#34;&gt;case&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;phoneNumber&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                &lt;span class=&#34;k&#34;&gt;guard&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;phoneNumber&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;phoneNumber&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;url&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;URL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;sms://&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;\(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;phoneNumber&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                    &lt;span class=&#34;k&#34;&gt;break&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                &lt;span class=&#34;n&#34;&gt;attributed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;range&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;link&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;url&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;k&#34;&gt;case&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;link&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                &lt;span class=&#34;k&#34;&gt;guard&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;url&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;url&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                    &lt;span class=&#34;k&#34;&gt;break&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                &lt;span class=&#34;n&#34;&gt;attributed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;range&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;link&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;url&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;k&#34;&gt;default&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                &lt;span class=&#34;k&#34;&gt;break&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;attributed&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here, I&amp;rsquo;ve used the &lt;code&gt;sms://&lt;/code&gt; scheme to open the Messaging app when the user&#xA;taps on a phone number. You could also use the &lt;code&gt;tel://&lt;/code&gt; scheme as well.&lt;/p&gt;&#xA;&lt;p&gt;Now go forth, tap that phone number, and make Jenny your own.&lt;/p&gt;&#xA;&lt;h2 id=&#34;additional-resources&#34;&gt;&#xA;  Additional Resources&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#additional-resources&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/documentation/foundation/nsdatadetector&#34;&gt;Apple Documentation: NSDataDetector&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://nshipster.com/nsdatadetector/&#34;&gt;NSHipster: NSDataDetector&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/apple/&#34;&gt;#apple&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>CoreData: All that is left to us is honor</title>
      <link>https://ivanthinking.net/2022/06/07/coredata-all-that-is-left-to-us-is-honor/</link>
      <pubDate>Tue, 07 Jun 2022 17:08:51 -0700</pubDate>
      <guid>https://ivanthinking.net/2022/06/07/coredata-all-that-is-left-to-us-is-honor/</guid>
      <category>software-development</category>
      <category>nerdtower</category>
      <category>apple</category>
      <category>ios-development</category>
      <description>&lt;p&gt;&lt;img class=&#34;page-img&#34; src=&#34;https://ivanthinking.net/images/coredata-all-that-is-left-honor.png&#34; alt=&#34;NSMAnagedObjectContext__MultiThreading_Violation_AllThatIsLeftToUsIsHonor__&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;  /&gt;&#xA;&#xA;&lt;em&gt;My life for Aiur&lt;/em&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;how-to-do&#34;&gt;&#xA;  How to do?&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#how-to-do&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Add &lt;code&gt;-com.apple.CoreData.ConcurrencyDebug 1&lt;/code&gt; to your Xcode &lt;a href=&#34;https://www.donnywals.com/using-launch-arguments-for-easier-core-data-debugging/&#34;&gt;launch arguments&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Execute some &lt;a href=&#34;https://medium.com/bpxl-craft/some-lessons-learned-on-core-data-5f095ecb1882&#34;&gt;non-thread safe CoreData code&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Crash, but at least you&amp;rsquo;ll still have your dignity.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;see-also&#34;&gt;&#xA;  See also&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#see-also&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://davedelong.com/blog/2018/05/09/the-laws-of-core-data/&#34;&gt;The Laws of Core Data&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/questions/33575664/xcode-multithreading-violationall-that-is-left-to-us-is-honor-cocoa&#34;&gt;Stack Overflow&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/apple/&#34;&gt;#apple&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Beginnings: Birth of Little Memory</title>
      <link>https://ivanthinking.net/2021/04/30/beginnings-birth-of-little-memory/</link>
      <pubDate>Fri, 30 Apr 2021 16:01:51 -0700</pubDate>
      <guid>https://ivanthinking.net/2021/04/30/beginnings-birth-of-little-memory/</guid>
      <category>little-memory</category>
      <category>nerdtower</category>
      <category>ios-development</category>
      <description>&lt;p&gt;In 2011, I was &lt;a href=&#34;https://nerdtower.com/&#34;&gt;co-founder of Dojo&lt;/a&gt;, a startup focused on helping people build and keep habits. We set out to truly make a difference in people&amp;rsquo;s lives by helping them do the things they always wanted to do: work out, meditate, stop smoking, create art, and a whole list of other practices that we were learning from our early beta testers.&lt;/p&gt;&#xA;&lt;p&gt;Even though Dojo ultimately didn&amp;rsquo;t succeed, we were honored to see many of our learnings adopted on other sites (like streaks!).&lt;/p&gt;&#xA;&lt;p&gt;&lt;img class=&#34;page-img&#34; src=&#34;https://ivanthinking.net/2021/04/30/beginnings-birth-of-little-memory/dojo-streak.jpeg&#34; alt=&#34;Streaks for Dojo&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;  /&gt;&#xA;&#xA;&lt;em&gt;Sketching on ideas about streaks for Dojo&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;The most valuable and yet seemingly simple learning I had was that it was really hard for people to start a new habit like &amp;ldquo;Go to the gym 2 times a week,&amp;rdquo; but  a habit like &amp;ldquo;Floss everyday&amp;rdquo; was easy for people to pick up.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;The regularity of doing something simple and daily seemed to help a lot in getting the habit sticking.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;When I started work on Little Memory, I had the following in my mind:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;I want you to spend less than 5 minutes a day using Little Memory and still get value out of using it. The time in your life is important, and I&amp;rsquo;d rather you be out in the world enjoying your life rather than spending your whole day on your computer/phone.&lt;/li&gt;&#xA;&lt;li&gt;I&amp;rsquo;d been following &lt;a href=&#34;https://gretchenrubin.com/blog/&#34;&gt;Gretchen Rubin&amp;rsquo;s blog&lt;/a&gt; and had just read her book: &lt;a href=&#34;https://amzn.to/3ud58Uj&#34;&gt;The Happiness Project&lt;/a&gt;. I loved the idea of a &lt;a href=&#34;https://thelittlememory.com/five-year-journal-app/&#34;&gt;5-year one sentence journal&lt;/a&gt;, as it helps in the practice of daily reflection both for the current day but also in how today relates to the same day in past years.&lt;/li&gt;&#xA;&lt;li&gt;Do something little that has a lot of meaning in your life. Every day is important, even if we don&amp;rsquo;t realize it at the time.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I also loved the idea of streaks. :)&lt;/p&gt;&#xA;&lt;p&gt;&lt;img class=&#34;page-img&#34; src=&#34;https://ivanthinking.net/2021/04/30/beginnings-birth-of-little-memory/gretchen-rubin-happiness-project-book-signing.jpeg&#34; alt=&#34;Gretchen Rubin book signing&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;  /&gt;&#xA;&#xA;&lt;em&gt;Book signing for The Happiness Project with Gretchen Rubin, January 2010&lt;/em&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;birth-of-a-name&#34;&gt;&#xA;  Birth of a Name&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#birth-of-a-name&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;With the above in mind, I developed and deployed my first version of the web app. I just called it: &lt;code&gt;diary&lt;/code&gt;. Nothing fancy. I would later start telling my friends about how I was working on &amp;ldquo;my little diary project,&amp;rdquo; and as I continued sharing it with friends, they would tell me that they enjoyed writing their memories and seeing little memories from their past. I liked the sound of that, and thus, a name was born: The Little Memory.&lt;/p&gt;&#xA;&lt;h2 id=&#34;remember-reminders&#34;&gt;&#xA;  Remember Reminders?&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#remember-reminders&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;The first version of the web app was so bare bones that all it was was a website with a simple way to enter and view today&amp;rsquo;s memory. I&amp;rsquo;d open up the page every evening, write my memory, and go to bed. Of course, most of us don&amp;rsquo;t have the kind of memory (&lt;em&gt;little&lt;/em&gt; memory?) or discipline to remember to open up the same website every night (OK, except maybe for social media sites).&lt;/p&gt;&#xA;&lt;p&gt;My friend, &lt;a href=&#34;https://www.thebehavioralscientist.com/&#34;&gt;Jason Hreha&lt;/a&gt;, suggested adding email reminders. It was a great idea and a no brainer to add next. I added reminders to be sent to your inbox in the evenings, and in the reminder, you&amp;rsquo;d also see a reminder from exactly 1 year, 1 month, or 1 week ago in it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-highly-acclaimed-ios-app&#34;&gt;&#xA;  The Highly Acclaimed iOS App&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#the-highly-acclaimed-ios-app&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;With my own growing usage of the iPhone, it became apparent that I really wanted an app for Little Memory. Emails and the website were fine, but I could see the potential for a more refined experience within an iPhone app. I brushed up on my Objective-C skills, fired up Xcode, and got to work&amp;ndash;launching the first version of iOS app in 2013.&lt;/p&gt;&#xA;&lt;p&gt;The app would go on to be featured in the Apple App Store in:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;iTunes Staff Favorites&lt;/li&gt;&#xA;&lt;li&gt;Dear Diary&lt;/li&gt;&#xA;&lt;li&gt;Apps for Writers&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I was delightfully surprised to see the amount of love and support from both Apple and from users who sent me emails about how much they enjoyed how simple the design of the iOS app was and also how meaningful the experience of using Little Memory was.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-story-continues-one-day-at-a-time&#34;&gt;&#xA;  The Story Continues, One Day at a Time&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#the-story-continues-one-day-at-a-time&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;This is by no means a complete history of Little Memory, but it does show how a humble little idea can grow from a tiny kernel. And just as Little Memory keeps growing, so do I. I&amp;rsquo;ve learned new skills, &lt;a href=&#34;https://ivanthinking.net/2021/03/23/self-growth-get-out-comfort-zone/&#34;&gt;stepped out of my comfort zone&lt;/a&gt;, and also made many friends along this journey.&lt;/p&gt;&#xA;&lt;p&gt;I recognize that you, as a Lil&amp;rsquo;memmer, are part of this journey as well, and I&amp;rsquo;m always grateful to be able to be a part of your personal journey.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve always said I&amp;rsquo;ve wanted Little Memory to be here for over 100 years, and I continue to work on making it a reality.&lt;/p&gt;&#xA;&lt;p&gt;Every day is a new beginning, and beginning starts with a single step. I hope to make that first step easier for more people. Self growth doesn&amp;rsquo;t have to be hard. Your journey of personal growth doesn&amp;rsquo;t have to be you climbing up a steep cliff every day. It can be simple. Just like &lt;a href=&#34;https://thelittlememory.com/one-line-a-day-app/&#34;&gt;writing a single sentence every day&lt;/a&gt;.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/little-memory/&#34;&gt;/little-memory&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>iPhone Upgrade Program vs Buying, Trade In</title>
      <link>https://ivanthinking.net/2020/12/05/iphone-upgrade-program-vs-buying-trade-in/</link>
      <pubDate>Sat, 05 Dec 2020 17:22:26 -0700</pubDate>
      <guid>https://ivanthinking.net/2020/12/05/iphone-upgrade-program-vs-buying-trade-in/</guid>
      <category>apple</category>
      <category>ios-development</category>
      <description>&lt;p&gt;Every year, when Apple makes its big announcement about the new iPhone being the most amazing device ever, you&amp;rsquo;re wondering whether you should upgrade or not. You consider whether the new features are worth it or whether you want a better camera or a better battery life.&lt;/p&gt;&#xA;&lt;p&gt;Maybe your iPhone&amp;rsquo;s becoming gruelingly slow because you&amp;rsquo;re 3 years behind. Or maybe you can tell that your battery life is incrementally dying away with each night&amp;rsquo;s recharge.&lt;/p&gt;&#xA;&lt;p&gt;On top of all this, worrying about what to do with your old device can add another stress point.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m a fan of the &lt;a href=&#34;https://www.apple.com/shop/iphone/iphone-upgrade-program&#34;&gt;iPhone Upgrade Program&lt;/a&gt; (IUP) to relieve a lot of the hassle of making these decisions every year.&lt;/p&gt;&#xA;&lt;h2 id=&#34;is-apples-iphone-upgrade-program-worth-it&#34;&gt;&#xA;  Is Apple&amp;rsquo;s iPhone Upgrade Program worth it?&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#is-apples-iphone-upgrade-program-worth-it&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s why I like it:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;I want to stay up to date and get the latest iPhone every year.&lt;/strong&gt; This was a bigger concern when I was a &lt;a href=&#34;https://www.nerdtower.com&#34;&gt;full time iOS developer&lt;/a&gt;.  I  do still have &lt;a href=&#34;https://thelittlememory.com/&#34;&gt;apps with users that depend on me&lt;/a&gt; in the App Store, however, and it&amp;rsquo;s important for me to stay relevant.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Fewer decisions for me to make.&lt;/strong&gt; Similar to how I&amp;rsquo;ve designed my &lt;a href=&#34;https://ivanthinking.net/2019/04/29/mens-capsule-wardrobe/&#34;&gt;capsule wardrobe&lt;/a&gt;, I actually would prefer not to make a choice about whether I should upgrade or not. I just do it every year.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;I don&amp;rsquo;t want to own my device.&lt;/strong&gt; The last iPhone I still own is my 3GS that I keep on my home Computing Museum shelf. I have an old iPad Mini that&amp;rsquo;s soon to be trash, as it&amp;rsquo;s way too slow to be worthwhile to use it. I wouldn&amp;rsquo;t know what to do with an old iPhone.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Less e-waste in the world.&lt;/strong&gt; If Apple knows how to &lt;a href=&#34;https://www.apple.com/newsroom/2019/04/apple-expands-global-recycling-programs/&#34;&gt;recycle iPhones&lt;/a&gt; to help manufacture the next generation of devices, I&amp;rsquo;m happy about it.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;I don&amp;rsquo;t care too much about AppleCare, but it&amp;rsquo;s a nice addition.&lt;/strong&gt; I don&amp;rsquo;t keep a case on my iPhone and never have. Even adults have fidget spinners, and the iPhone is one of mine. AppleCare+ lets me care a little less about having a mishap. I like not having to baby my device. Scratches on the screen that naturally develop over time don&amp;rsquo;t bother me either. That&amp;rsquo;s one less thing on my mind.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;sounds-like-some-luxury-expensive-shit&#34;&gt;&#xA;  Sounds like some luxury expensive shit&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#sounds-like-some-luxury-expensive-shit&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Indeed.&lt;/p&gt;&#xA;&lt;p&gt;I realize that the above reasons for being enrolled in the IUP are pretty &lt;a href=&#34;https://www.urbandictionary.com/define.php?term=Bad%20and%20Bougie&#34;&gt;bougie&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not looking at it as just a fully monetary choice. Just like my &lt;a href=&#34;https://ivanthinking.net/2019/04/29/mens-capsule-wardrobe/&#34;&gt;capsule wardrobe&lt;/a&gt;, it&amp;rsquo;s more than just about the economics.&#xA;For me to be focused on &lt;a href=&#34;https://ivanthinking.net/2020/03/11/walking-the-dog/&#34;&gt;things&lt;/a&gt; that &lt;a href=&#34;https://ivanthinking.net/2020/03/04/alicia-gaylord/&#34;&gt;matter to me&lt;/a&gt;, I prefer to reduce decisions in one area of my life for an economic price.&#xA;I&amp;rsquo;m &lt;a href=&#34;https://ivanthinking.net/2019/05/21/house-cleaner/&#34;&gt;getting value&lt;/a&gt;, and that&amp;rsquo;s what I&amp;rsquo;m paying for.&lt;/p&gt;&#xA;&lt;h2 id=&#34;lets-break-it-down&#34;&gt;&#xA;  Let&amp;rsquo;s break it down&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#lets-break-it-down&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Consider the iPhone 12 Pro that Apple released in 2020.&#xA;The 128GB model retailed full price at $999, and IUP&amp;rsquo;s monthly for it was $49.91/month.&#xA;In 2021, Apple will likely release a comparable iPhone 13 for the same price.&#xA;We can also do some rough estimates of &lt;a href=&#34;https://swappa.com/blog/how-much-is-my-iphone-worth/&#34;&gt;what trade in values may be&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s look at what this looks like compared to other scenarios such as:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Buying the iPhone and trading it in for a new one at a later year&lt;/li&gt;&#xA;&lt;li&gt;Buying the iPhone, not trading it in, and buying a new one at a later year&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;img class=&#34;page-img&#34; src=&#34;https://ivanthinking.net/2020/12/05/iphone-upgrade-program-vs-buying-trade-in/iup-analysis-assumptions.png&#34; alt=&#34;IUP analysis and assumptions&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;  /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;We&amp;rsquo;ll look at how much you&amp;rsquo;ll have spent as a &lt;em&gt;Lifetime Total&lt;/em&gt; for each year over the course of 6 years.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img class=&#34;page-img&#34; src=&#34;https://ivanthinking.net/2020/12/05/iphone-upgrade-program-vs-buying-trade-in/iup-buy-trade-scenarios_hu_83e9f0977a0797b3.png&#34; alt=&#34;iPhone Upgrade Program, Buying, Trade In 6 years Analysis&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;  /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Green&lt;/strong&gt; is what I value. I think most folks would agree that they want to pay less. I also favor&#xA;not owning any devices and being able to use more devices.&lt;/p&gt;&#xA;&lt;h2 id=&#34;how-bougie-is-it&#34;&gt;&#xA;  How bougie is it?&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#how-bougie-is-it&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Annually &lt;a href=&#34;https://www.apple.com/shop/trade-in&#34;&gt;trading in&lt;/a&gt; is clearly a bad choice. You pay the most, and you don&amp;rsquo;t get AppleCare+, but at least you get the latest device every year. With no credit checks, I wonder if this may be the best choice if you&amp;rsquo;re a high roller with a cash only business.&#xA;&lt;em&gt;Drug dealers?&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;If you&amp;rsquo;re looking for a purely economic choice, going with the IUP and upgrading after 3 years seems like a no brainer. Not only do you pay the least out of these scenarios over time, you also end up with devices that you could sell if you wanted to.&lt;/p&gt;&#xA;&lt;p&gt;If you want to try the latest iPhone every year, though, IUP with an annual upgrade comes at a premium. You&amp;rsquo;re not as bad as buying keeping the device for 2 years, but you&amp;rsquo;re getting diminishing economic returns compared to the other strategies here. And if your credit is bad, this is not going to be the best path for you.&lt;/p&gt;&#xA;&lt;p&gt;Looking at this laid out, I&amp;rsquo;m fine with the premium I pay for the annual IUP. And over the longer term, we can see it diverging more and more from the IUP with 3 year upgrade.&lt;/p&gt;&#xA;&lt;h2 id=&#34;scenarios-not-considered&#34;&gt;&#xA;  Scenarios not considered&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#scenarios-not-considered&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I don&amp;rsquo;t consider the scenarios where you buy your phone and subsequently sell it rather than trade it in. I haven&amp;rsquo;t ever sold anything on eBay before, so I can&amp;rsquo;t speak too much for it.&lt;/p&gt;&#xA;&lt;p&gt;If you have the time to sell your device every few years, it could end up making sense, but I encourage you to do your own math for such a scenario. There&amp;rsquo;s a bit of risk that should be accounted for, as you may not get the price that you&amp;rsquo;re expecting when you bought your iPhone.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-best-choice-is-based-on-your-own-values&#34;&gt;&#xA;  The best choice is based on your own values&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#the-best-choice-is-based-on-your-own-values&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;What do you care about?&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th style=&#34;text-align: left&#34;&gt;You want&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: left&#34;&gt;Optimal choice&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;More devices at lowest cost&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;IAP, upgrade annually&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;Save most money over time&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;IAP, upgrade @3yrs&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;No credit checks at lowest cost&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;Buy, keep for 3 years&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;If it&amp;rsquo;s just the economics, the IUP with an upgrade every 3 years is your best bet. If you want to own your devices and have more of them, then buying and keeping for 3 years seems like a good choice. If you&amp;rsquo;re like me and want to stay up to date with the latest iPhone features and don&amp;rsquo;t want to actually own the device, then go with the annual IUP cycle.&lt;/p&gt;&#xA;&lt;p&gt;Hope this helps you out in your own decision process!&lt;/p&gt;&#xA;&lt;h2 id=&#34;additional-resources&#34;&gt;&#xA;  Additional Resources&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#additional-resources&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.apple.com/shop/iphone/iphone-upgrade-program&#34;&gt;Apple&amp;rsquo;s iPhone Upgrade Program&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.tomsguide.com/us/iphone-upgrade-program-faq,news-21583.html&#34;&gt;Tom&amp;rsquo;s Guide: Is Apple&amp;rsquo;s iPhone Upgrade Program worth It?&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://forums.macrumors.com/threads/is-the-iphone-upgrade-program-worth-it.2308617/&#34;&gt;MacRumors Forums: Is the iPhone Upgrade Program worth it?&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/tags/apple/&#34;&gt;#apple&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Swift: Immutable Struct Singleton Initialization</title>
      <link>https://ivanthinking.net/2020/05/29/swift-immutable-struct-singleton-initialization/</link>
      <pubDate>Fri, 29 May 2020 11:10:01 -0700</pubDate>
      <guid>https://ivanthinking.net/2020/05/29/swift-immutable-struct-singleton-initialization/</guid>
      <category>software-development</category>
      <category>nerdtower</category>
      <category>apple</category>
      <category>ios-development</category>
      <description>&lt;p&gt;How can you initialize a &lt;code&gt;struct&lt;/code&gt; singleton that has dependencies? You likely chose to use a &lt;code&gt;struct&lt;/code&gt; because you&amp;rsquo;re trying to reduce state and keep things immutable in your code. You&amp;rsquo;re also likely wanting to use the singleton pattern for a shared service or model in your project.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m a fan of keeping things immutable if possible, and I &lt;a href=&#34;https://developer.apple.com/documentation/swift/choosing_between_structures_and_classes&#34;&gt;default&lt;/a&gt; to &lt;code&gt;struct&lt;/code&gt;s and &lt;code&gt;let&lt;/code&gt;s before using &lt;code&gt;class&lt;/code&gt;es, or &lt;code&gt;var&lt;/code&gt;s.&lt;/p&gt;&#xA;&lt;p&gt;One place I&amp;rsquo;ll use this pattern is when making services that depend on an external API like so:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;ApiClient&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;secret&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;init&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;secret&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kc&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;secret&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;secret&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;LoginService&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;apiClient&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ApiClient&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;shared&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;LoginService&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;apiClient&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ApiClient&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;secret&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;CLIENT_SECRET&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;LoginService&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;apiClient&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;apiClient&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Notice that &lt;code&gt;LoginService&lt;/code&gt; is immutable (look Ma, no &lt;code&gt;var&lt;/code&gt;s!), and we can be sure that our &lt;code&gt;shared&lt;/code&gt; instance will always be the same instance when we call &lt;code&gt;LoginService.shared&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Often, our &lt;code&gt;shared&lt;/code&gt; assignment is more complex than this. For example, the &lt;code&gt;secret&lt;/code&gt; here used to initialize the API Client in a more mature project would not be a magic string like we have here and can instead be dependent on launch arguments or an environment variable.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/apple/&#34;&gt;#apple&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Fastlane: Downloading AppStore Metadata</title>
      <link>https://ivanthinking.net/2020/05/28/fastlane-downloading-appstore-metadata/</link>
      <pubDate>Thu, 28 May 2020 10:38:01 -0700</pubDate>
      <guid>https://ivanthinking.net/2020/05/28/fastlane-downloading-appstore-metadata/</guid>
      <category>software-development</category>
      <category>nerdtower</category>
      <category>apple</category>
      <category>ios-development</category>
      <description>&lt;p&gt;Just like we use &lt;code&gt;git&lt;/code&gt; to be able to audit changes to our code over time, doing so with our app&amp;rsquo;s AppStore metadata lets us keep an eye on changes made to our AppStore page as well.&lt;/p&gt;&#xA;&lt;p&gt;The first step in getting more control of our metadata is by having it live in our project&amp;rsquo;s repository. Keeping a copy of our metadata in &lt;code&gt;git&lt;/code&gt; lets us keep track of changes to our app&amp;rsquo;s description, keywords, and even screenshots over time.&lt;/p&gt;&#xA;&lt;p&gt;To do so, we use Fastlane&amp;rsquo;s &lt;code&gt;deliver&lt;/code&gt; &lt;a href=&#34;https://docs.fastlane.tools/actions/deliver/&#34;&gt;command&lt;/a&gt; with the &lt;code&gt;download_metadata&lt;/code&gt; subcommand:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;fastlane deliver download_metadata&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here&amp;rsquo;s what a run of this for &lt;a href=&#34;https://getreadytime.com/&#34;&gt;Little Countdown&lt;/a&gt; looks like:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;% fastlane deliver download_metadata&#xA;[✔] 🚀&#xA;[10:52:02]: Login to App Store Connect (appstore@nerdtower.com)&#xA;[10:52:03]: Login successful&#xA;&#xA;+--------------------------------------+-------------------------+&#xA;|                    deliver 2.148.1 Summary                     |&#xA;+--------------------------------------+-------------------------+&#xA;| screenshots_path                     | ./fastlane/screenshots  |&#xA;| metadata_path                        | ./fastlane/metadata     |&#xA;| username                             | appstore@nerdtower.com  |&#xA;| app_identifier                       | com.nerdtower.Countdown |&#xA;| platform                             | ios                     |&#xA;| edit_live                            | false                   |&#xA;| use_live_version                     | false                   |&#xA;| skip_binary_upload                   | false                   |&#xA;| skip_screenshots                     | false                   |&#xA;| skip_metadata                        | false                   |&#xA;| skip_app_version_update              | false                   |&#xA;| force                                | false                   |&#xA;| overwrite_screenshots                | false                   |&#xA;| submit_for_review                    | false                   |&#xA;| reject_if_possible                   | false                   |&#xA;| automatic_release                    | false                   |&#xA;| phased_release                       | false                   |&#xA;| reset_ratings                        | false                   |&#xA;| team_id                              | [hidden]                |&#xA;| dev_portal_team_id                   | [hidden]                |&#xA;| run_precheck_before_submit           | true                    |&#xA;| precheck_default_rule_level          | warn                    |&#xA;| individual_metadata_items            | []                      |&#xA;| ignore_language_directory_validation | false                   |&#xA;| precheck_include_in_app_purchases    | true                    |&#xA;+--------------------------------------+-------------------------+&#xA;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/en-US/description.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/en-US/keywords.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/en-US/release_notes.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/en-US/support_url.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/en-US/marketing_url.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/en-US/promotional_text.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/en-US/name.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/en-US/subtitle.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/en-US/privacy_url.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/en-US/apple_tv_privacy_policy.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/copyright.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/primary_category.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/secondary_category.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/primary_first_sub_category.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/primary_second_sub_category.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/secondary_first_sub_category.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/secondary_second_sub_category.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/trade_representative_contact_information/trade_name.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/trade_representative_contact_information/first_name.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/trade_representative_contact_information/last_name.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/trade_representative_contact_information/address_line1.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/trade_representative_contact_information/address_line2.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/trade_representative_contact_information/address_line3.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/trade_representative_contact_information/city_name.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/trade_representative_contact_information/state.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/trade_representative_contact_information/country.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/trade_representative_contact_information/postal_code.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/trade_representative_contact_information/phone_number.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/trade_representative_contact_information/email_address.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/trade_representative_contact_information/is_displayed_on_app_store.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/review_information/first_name.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/review_information/last_name.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/review_information/phone_number.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/review_information/email_address.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/review_information/demo_user.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/review_information/demo_password.txt&amp;#39;&#xA;[10:52:15]: Writing to &amp;#39;./fastlane/metadata/review_information/notes.txt&amp;#39;&#xA;[10:52:15]: Successfully created new configuration files.&#xA;[10:52:15]: Successfully downloaded large app icon&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After a successful download, you should now have your metadata in your project&amp;rsquo;s &lt;code&gt;./fastlane/metadata&lt;/code&gt;. Great!&lt;/p&gt;&#xA;&lt;p&gt;Something to notice above is that things like the app&amp;rsquo;s description, keywords, and release notes live inside of localized subdirectories for each locale such as &lt;code&gt;en-US&lt;/code&gt;. If we had AppStore metadata localized into other locales, we&amp;rsquo;d see other subdirectories for each of those as well.&lt;/p&gt;&#xA;&lt;p&gt;At this point, you can consider:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Having your &lt;code&gt;./fastlane/metadata&lt;/code&gt; be the source of truth of your release notes so that they&amp;rsquo;re bound directly to a commit or tag in your repository&lt;/li&gt;&#xA;&lt;li&gt;Using &lt;code&gt;deliver&lt;/code&gt;&amp;rsquo;s subcommand &lt;code&gt;submit_build&lt;/code&gt; to &lt;a href=&#34;https://docs.fastlane.tools/actions/deliver/#submit-build&#34;&gt;submit your builds for review&lt;/a&gt; with changes you make to metadata in your repository. This opens the door to have our CI/CD pipeline manage submission of our build instead of having to login to AppStore Connect&amp;rsquo;s website and &amp;ldquo;pressing the button.&amp;rdquo;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;With your AppStore metadata downloaded and stored in your project&amp;rsquo;s repository, you gain visibility and control of your presence in the AppStore and the data on your app&amp;rsquo;s page. Furthermore, you open up the path to automating the management of this metadata as well.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/apple/&#34;&gt;#apple&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Swift: Converting String to Date</title>
      <link>https://ivanthinking.net/2020/05/20/swift-converting-string-to-date/</link>
      <pubDate>Wed, 20 May 2020 12:20:01 -0700</pubDate>
      <guid>https://ivanthinking.net/2020/05/20/swift-converting-string-to-date/</guid>
      <category>software-development</category>
      <category>nerdtower</category>
      <category>apple</category>
      <category>ios-development</category>
      <description>&lt;p&gt;When consuming an API response, we&amp;rsquo;ll often see an &lt;a href=&#34;https://en.wikipedia.org/wiki/ISO_8601&#34;&gt;ISO 8601 formatted date&lt;/a&gt; &lt;code&gt;String&lt;/code&gt; like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2020-04-20T16:20:42+02:00&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As a &lt;code&gt;String&lt;/code&gt;, it&amp;rsquo;s not really useful for us, as it&amp;rsquo;s not really something you want to show your users.&#xA;We may want to be able to extract just the hour, calculate the time interval between this date and others, or just format it in a way that&amp;rsquo;s more meaningful for the user.&#xA;To do that, we can convert the &lt;code&gt;String&lt;/code&gt; to a &lt;code&gt;Date&lt;/code&gt; object.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s add an extension to &lt;code&gt;String&lt;/code&gt; with a new function &lt;code&gt;asDate()&lt;/code&gt;. I like doing this so we don&amp;rsquo;t need to add additional classes or add more to the global namespace.&lt;/p&gt;&#xA;&lt;p&gt;We can start by adding some tests:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;String_ExtensionsTests&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;XCTestCase&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;testAsDate_invalid_notIso&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;throws&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;invalidString&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;2020-04-20 16:20:42 GMT+02:00&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;date&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;invalidString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;asDate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;XCTAssertNil&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;date&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;should not have converted string to date&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;testAsDate_invalid_missingTimeDelimiter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;throws&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;missingTimeDelimiterString&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;2020-04-20 16:20:42+02:00&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;date&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;missingTimeDelimiterString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;asDate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;XCTAssertNil&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;date&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;should not have converted string to date&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;testAsDate_valid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;throws&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;isoDateString&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;2020-04-20T16:20:42+02:00&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;date&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;isoDateString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;asDate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;XCTAssertNotNil&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;date&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;should have converted string to date&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;calendar&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Calendar&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;identifier&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;gregorian&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;twoHoursInSeconds&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;60&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;60&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;amsterdamTimeZone&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;TimeZone&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;secondsFromGMT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;twoHoursInSeconds&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;components&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;calendar&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;dateComponents&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;in&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;amsterdamTimeZone&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;date&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;!)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;XCTAssertEqual&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;components&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;year&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2020&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;has correct year&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;XCTAssertEqual&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;components&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;month&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;has correect month&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;XCTAssertEqual&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;components&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;day&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;20&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;has correct day 🌲&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;XCTAssertEqual&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;components&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;hour&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;16&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;has correct hour&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;XCTAssertEqual&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;components&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;minute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;20&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;has correct minute&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;XCTAssertEqual&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;components&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;second&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;42&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;has correct second&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;XCTAssertEqual&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;components&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;timeZone&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;amsterdamTimeZone&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;has correct time zone&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Some things to note here:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Even though we&amp;rsquo;re likely OK with using &lt;code&gt;Calendar.current&lt;/code&gt;, I like being as explicit as possible in my tests, since we don&amp;rsquo;t always know if the device running our tests will actually be set to a Gregorian calendar (but, honestly, it&amp;rsquo;ll likely be so).&lt;/li&gt;&#xA;&lt;li&gt;I&amp;rsquo;ve set the time zone to match the &lt;code&gt;String&lt;/code&gt; we&amp;rsquo;re testing. This just makes it more clear and easy to understand without having to do additional math in my own head when asserting (or if the tests fail in the future).&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Great, with our tests failing, it&amp;rsquo;s time to implement the actual code and get those checkmarks green. Luckily for us, Swift &lt;a href=&#34;https://developer.apple.com/documentation/foundation/iso8601dateformatter&#34;&gt;gives us &lt;code&gt;ISO8601DateFormatter&lt;/code&gt;&lt;/a&gt;. This makes things super easy for us:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;extension&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;String&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;asDate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Date&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;formatter&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ISO8601DateFormatter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;formatter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;date&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What? That&amp;rsquo;s it? Yup. Not a whole lot of code (vs our test code), but we can be sure we&amp;rsquo;re handling date &lt;code&gt;String&lt;/code&gt;s properly when we encounter them. Moreover, we&amp;rsquo;ve documented how we expect &lt;code&gt;asDate&lt;/code&gt; should work, so other developers on our team know what to expect when using it.&lt;/p&gt;&#xA;&lt;p&gt;🌲🌲🌲&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/apple/&#34;&gt;#apple&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Horizontal Stack Content Hugging</title>
      <link>https://ivanthinking.net/2020/05/19/horizontal-stack-content-hugging/</link>
      <pubDate>Tue, 19 May 2020 11:14:01 -0700</pubDate>
      <guid>https://ivanthinking.net/2020/05/19/horizontal-stack-content-hugging/</guid>
      <category>software-development</category>
      <category>nerdtower</category>
      <category>apple</category>
      <category>ios-development</category>
      <description>&lt;p&gt;In your iOS app, you&amp;rsquo;ve got a &lt;a href=&#34;https://developer.apple.com/documentation/uikit/uistackview&#34;&gt;Horizontal Stack&lt;/a&gt; with subviews layed out like so:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img class=&#34;page-img&#34; src=&#34;https://ivanthinking.net/images/software-dev/hstack-hugging01.png&#34; alt=&#34;Horizontal Stack: default content hugging&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;  /&gt;&#xA;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;override&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;viewDidLoad&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kc&#34;&gt;super&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;viewDidLoad&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;loveLabel&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;UILabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;loveLabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;text&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;LOVE&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;loveLabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;backgroundColor&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;systemRed&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;kissesLabel&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;UILabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;kissesLabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;text&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;Kisses&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;kissesLabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;backgroundColor&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;systemYellow&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;xoxoLabel&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;UILabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;xoxoLabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;text&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;XOXO&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;xoxoLabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;backgroundColor&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;systemBlue&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;stack&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;UIStackView&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;arrangedSubviews&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;loveLabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;kissesLabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;xoxoLabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;])&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;stack&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;translatesAutoresizingMaskIntoConstraints&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;stack&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;axis&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;horizontal&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;stack&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;spacing&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;4&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;view&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;addSubview&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;stack&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;NSLayoutConstraint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;activate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;([&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;stack&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;centerXAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;constraint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;equalTo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;view&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;centerXAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;stack&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;centerYAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;constraint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;equalTo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;view&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;centerYAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;stack&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;leftAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;constraint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;equalToSystemSpacingAfter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;view&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;leftAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;multiplier&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;view&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rightAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;constraint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;equalToSystemSpacingAfter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;stack&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rightAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;multiplier&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;])&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;hmm-a-bit-too-much-love&#34;&gt;&#xA;  Hmm, a bit too much LOVE&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#hmm-a-bit-too-much-love&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;But what if you really want is to see less of that &lt;code&gt;LOVE&lt;/code&gt; and more of those &lt;code&gt;Kisses&lt;/code&gt;?&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;The &lt;code&gt;LOVE&lt;/code&gt; label is taking too much horizontal space: you want &lt;code&gt;Kisses&lt;/code&gt; to be streching out as much as possible.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;What we need to do is let Auto Layout know how to prioritize our views&#39;:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Content Hugging&lt;/li&gt;&#xA;&lt;li&gt;Compression Resistance&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;In this case, we care about:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The &lt;code&gt;LOVE&lt;/code&gt; label&amp;rsquo;s &lt;code&gt;Content Hugging&lt;/code&gt; priority&lt;/li&gt;&#xA;&lt;li&gt;On the &lt;strong&gt;Horizontal axis&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s give &lt;code&gt;LOVE&lt;/code&gt; a high &lt;code&gt;Content Hugging&lt;/code&gt; priority so we can smother it with our coder love:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;loveLabel&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;UILabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;loveLabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;text&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;LOVE&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;loveLabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;backgroundColor&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;systemRed&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;loveLabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;setContentHuggingPriority&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;defaultHigh&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;horizontal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img class=&#34;page-img&#34; src=&#34;https://ivanthinking.net/images/software-dev/hstack-hugging02.png&#34; alt=&#34;Horizontal Stack: content hugging love&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;  /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;Excellent.&lt;/p&gt;&#xA;&lt;p&gt;By just adding a single line of code, we were able to tell Auto Layout: &amp;ldquo;Hey, I want you to prioritize hugging this content (the &lt;code&gt;LOVE&lt;/code&gt; label) and let other views on the &lt;strong&gt;Horizontal axis&lt;/strong&gt; to take up more visual space.&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;Love and Kisses. XOXO ❤️&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/apple/&#34;&gt;#apple&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Adding an Instructional Overlay for first time users to your app in iOS</title>
      <link>https://ivanthinking.net/2020/05/16/adding-an-instructional-overlay-for-first-time-users-to-your-app-in-ios/</link>
      <pubDate>Sat, 16 May 2020 13:04:01 -0700</pubDate>
      <guid>https://ivanthinking.net/2020/05/16/adding-an-instructional-overlay-for-first-time-users-to-your-app-in-ios/</guid>
      <category>software-development</category>
      <category>nerdtower</category>
      <category>apple</category>
      <category>ios-development</category>
      <category>design</category>
      <description>&lt;p&gt;Sometimes called &lt;em&gt;Coachmarks&lt;/em&gt;, a &lt;em&gt;Wizard&lt;/em&gt;, or an &lt;em&gt;Onboarding Flow&lt;/em&gt;, this common UX pattern is used to guide first time users to an app on its features in your UI.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img class=&#34;page-img&#34; src=&#34;https://ivanthinking.net/images/software-dev/coach-marks01_hu_3181419715b4adf7.png&#34; alt=&#34;Instructional Overlay&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;  /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;Here’s a quick and simple way to implement this pattern:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;buildButton&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;with&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;UIButton&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;button&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;UIButton&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;system&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;button&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;backgroundColor&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;systemOrange&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;button&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;setTitle&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;normal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;button&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;override&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;viewDidLoad&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kc&#34;&gt;super&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;viewDidLoad&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;buttonText&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;Put me in coach!&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;button&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;buildButton&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;with&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;buttonText&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;button&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;translatesAutoresizingMaskIntoConstraints&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;overlay&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;UIView&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;frame&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;zero&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;overlay&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;translatesAutoresizingMaskIntoConstraints&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;overlay&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;backgroundColor&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;UIColor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;black&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;withAlphaComponent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;0.5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;coachButton&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;buildButton&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;with&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;buttonText&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;coachButton&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;translatesAutoresizingMaskIntoConstraints&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;coachButton&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;isUserInteractionEnabled&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;coachLabel&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;UILabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;frame&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;zero&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;coachLabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;translatesAutoresizingMaskIntoConstraints&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;coachLabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;textColor&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;white&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;coachLabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;text&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;Get player in the game&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;view&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;addSubview&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;button&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;view&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;addSubview&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;overlay&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;view&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;addSubview&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;coachButton&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;view&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;addSubview&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;coachLabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;NSLayoutConstraint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;activate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;([&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;button&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;centerXAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;constraint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;equalTo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;view&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;centerXAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;button&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;centerYAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;constraint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;equalTo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;view&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;centerYAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;coachButton&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;centerXAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;constraint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;equalTo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;button&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;centerXAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;coachButton&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;centerYAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;constraint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;equalTo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;button&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;centerYAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;coachLabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;centerXAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;constraint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;equalTo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;coachButton&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;centerXAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;coachLabel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;centerYAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;constraint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;equalTo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;coachButton&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;bottomAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;constant&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;20&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;overlay&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;topAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;constraint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;equalTo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;view&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;topAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;overlay&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;bottomAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;constraint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;equalTo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;view&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;bottomAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;overlay&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;leftAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;constraint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;equalTo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;view&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;leftAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;overlay&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rightAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;constraint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;equalTo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;view&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rightAnchor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;])&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The idea here is that you have a duplicate button on top of a semi-transparent overlay. We use Auto Layout to constrain &lt;code&gt;coachButton&lt;/code&gt; to the real &lt;code&gt;button&lt;/code&gt;, and we also disable  &lt;code&gt;isUserInteractionEnabled&lt;/code&gt; since we don’t actually want the user to be able to select it in this flow.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img class=&#34;page-img&#34; src=&#34;https://ivanthinking.net/images/software-dev/coach-marks02_hu_4a89a0c8d2fe619b.png&#34; alt=&#34;Instructional Overlay Views Exploded&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;  /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;Also, note how we’re using &lt;code&gt;buildButton(with:)&lt;/code&gt; to instantiate the buttons so we make sure we’re actually constructing the buttons the same way for both &lt;code&gt;button&lt;/code&gt; and &lt;code&gt;coachButton&lt;/code&gt;.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/apple/&#34;&gt;#apple&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/design/&#34;&gt;#design&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Little Countdown iOS app adds alerts for upcoming flights</title>
      <link>https://ivanthinking.net/2019/09/12/little-countdown-ios-app-adds-alerts-for-upcoming-flights/</link>
      <pubDate>Thu, 12 Sep 2019 00:00:00 +0000</pubDate>
      <guid>https://ivanthinking.net/2019/09/12/little-countdown-ios-app-adds-alerts-for-upcoming-flights/</guid>
      <category>ready-time</category>
      <category>software-development</category>
      <category>nerdtower</category>
      <category>ios-development</category>
      <category>apple</category>
      <description>&lt;h2 id=&#34;with-latest-update-to-ios-app-little-countdown-users-can-easily-add-a-new-countdown-pre-filled-with-upcoming-flight-details&#34;&gt;&#xA;  With latest update to iOS app, Little Countdown users can easily add a new Countdown pre-filled with upcoming flight details&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#with-latest-update-to-ios-app-little-countdown-users-can-easily-add-a-new-countdown-pre-filled-with-upcoming-flight-details&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;A new update to the Little Countdown app for iOS makes planning even easier for frequent flyers by notifying users of upcoming flights and letting them immediately add a Countdown to get ready for their day of departure.&lt;/p&gt;&#xA;&lt;p&gt;This latest release of Little Countdown (v1.7.0) furthers its &lt;a href=&#34;https://ivanthinking.net/thoughts/little-countdown-calendar-integration/&#34;&gt;integration with the iOS Calendar&lt;/a&gt;.&#xA;In addition to being able to easily create a Countdown with autocomplete with Calendar events, users will now get notifications to start a Countdown the evening before their upcoming flight.&lt;/p&gt;&#xA;&lt;p&gt;&amp;ldquo;Having to remember to start a Countdown is one less thing frequent flyers need to remember now,&amp;rdquo; says Ivan Tse, creator of Little Countdown. &amp;ldquo;I&amp;rsquo;m incredibly excited to share this new update with travelers. We know how schedules can be hectic, and having Little Countdown be aware of your calendars makes planning process simpler and saves time that can be more productively used somewhere else.&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;Alerts for upcoming flights are available for users who have enabled iOS Calendar access to Little Countdown on their iPhone or iPad.&lt;/p&gt;&#xA;&lt;p&gt;Little Countdown is currently available on the iOS App Store for $2.99.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://itunes.apple.com/us/app/little-countdown/id1412470365&#34;&gt;Link to download iOS App&lt;/a&gt; &lt;br&gt;&#xA;&lt;a href=&#34;https://getreadytime.com/&#34;&gt;Link to website&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/ready-time/&#34;&gt;/ready-time&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/apple/&#34;&gt;#apple&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Bitrise: Mobile CI Service</title>
      <link>https://ivanthinking.net/2019/06/06/bitrise-mobile-ci-service/</link>
      <pubDate>Thu, 06 Jun 2019 19:53:01 -0700</pubDate>
      <guid>https://ivanthinking.net/2019/06/06/bitrise-mobile-ci-service/</guid>
      <category>software-development</category>
      <category>nerdtower</category>
      <category>apple</category>
      <category>ios-development</category>
      <description>&lt;p&gt;With the small iOS team at Hipmunk, having to maintain a box to run Xcode Server would have been costly in terms of:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Buying hardware&lt;/li&gt;&#xA;&lt;li&gt;Upgrading/maintaining the hardware&lt;/li&gt;&#xA;&lt;li&gt;Keeping the OS/software up to date&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Furthermore, we’d have to find a separate solution for our Android team and gain expertise on maintaining their CI as well.&lt;/p&gt;&#xA;&lt;p&gt;We ended up deciding to use &lt;a href=&#34;https://www.bitrise.io&#34;&gt;Bitrise&lt;/a&gt;, a continuous integration and delivery service designed primarily for mobile platforms.&lt;/p&gt;&#xA;&lt;h2 id=&#34;which-plan&#34;&gt;&#xA;  Which Plan?&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#which-plan&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;We’d started out with the &lt;strong&gt;Standard Plan&lt;/strong&gt;, which gave us:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;12-15 minute builds&lt;/li&gt;&#xA;&lt;li&gt;2 concurrencies&lt;/li&gt;&#xA;&lt;li&gt;$1200 annual&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;That&amp;rsquo;s definitely a lot cheaper than self-hosting our own machine and spending the time to maintain it!&lt;/p&gt;&#xA;&lt;p&gt;In my mind, generally, a 10 minute build time is a good number to stay under. Furthermore, with 2 engineers on each platform, we generally needed bandwidth for each engineer to run a build concurrently without slowing each other down. Being able to run a build for release during tighter times was important. Maybe we didn’t need 6 concurrents, but we thought 4 would generally give us enough room to work with.&lt;/p&gt;&#xA;&lt;p&gt;We started looking at &lt;strong&gt;Bitrise’s Elite Plan&lt;/strong&gt; with its beefier boxes, and we were able to do a trial to get some numbers out:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;8-9 minute builds&lt;/li&gt;&#xA;&lt;li&gt;4 concurrencies&lt;/li&gt;&#xA;&lt;li&gt;$4800 annual&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;These costs were minor in comparison to the time we were saving on the team and the value we were getting out of quick iteration of our commits. Definitely still cheaper than running and maintaining our own machines.&lt;/p&gt;&#xA;&lt;p&gt;It made a lot of sense to go ahead and upgrade to the Elite Plan, so we moved forward with that, and months later continued to be happy with our decision.&lt;/p&gt;&#xA;&lt;h2 id=&#34;running-on-multiple-stacks&#34;&gt;&#xA;  Running on Multiple Stacks&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#running-on-multiple-stacks&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;When Apple releases a new major version of Xcode and accompanying SDKs, you generally need to build your app on the new version and ensure you&amp;rsquo;ve handled all the warnings, deprecations, and sometimes bugs that crop up because of it.&lt;/p&gt;&#xA;&lt;p&gt;A strategy I like to use here is to open a PR for the new changes on a branch like &lt;code&gt;xcode11&lt;/code&gt; and start pushing changes there. I can then setup Bitrise to use the new stack and ensure that our CI will continue operating properly with the new version.&lt;/p&gt;&#xA;&lt;p&gt;Bitrise does a great job of making sure beta versions of Xcode are available as soon as possible, so you can get running on the new versions within days of Apple&amp;rsquo;s announcements.&lt;/p&gt;&#xA;&lt;p&gt;&lt;em&gt;UPDATE&lt;/em&gt;: John Sundell&amp;rsquo;s written a &lt;a href=&#34;https://blog.bitrise.io/building-with-parallel-xcode-versions-on-bitrise&#34;&gt;great post on not needing to branch&lt;/a&gt; to handle the two Xcode versions. Very clever! I haven&amp;rsquo;t given it a try yet, but I think it makes a lot of sense and will report back after giving it a shot.&lt;/p&gt;&#xA;&lt;h2 id=&#34;gotchas&#34;&gt;&#xA;  Gotchas&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#gotchas&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;We were using a &lt;code&gt;fastlane&lt;/code&gt; lane to run our tests, and we would intermittently get failures with the error:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Testing failed:&#xA;Command CompileSwift failed with a nonzero exit code&#xA;Command CodeSign failed with a nonzero exit code&#xA;Testing cancelled because the build failed.&#xA;** TEST FAILED **&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We switched over to using Bitrise’s own &lt;code&gt;Xcode Test for iOS&lt;/code&gt; Step, and haven’t had any issues since. I saw the same phenomenon when using Bitrise for &lt;a href=&#34;https://getreadytime.com/&#34;&gt;little countdown&lt;/a&gt;.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/apple/&#34;&gt;#apple&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Automated Beta/Internal Change Notes with Fastlane</title>
      <link>https://ivanthinking.net/2019/06/02/automated-beta/internal-change-notes-with-fastlane/</link>
      <pubDate>Sun, 02 Jun 2019 16:51:01 -0700</pubDate>
      <guid>https://ivanthinking.net/2019/06/02/automated-beta/internal-change-notes-with-fastlane/</guid>
      <category>software-development</category>
      <category>nerdtower</category>
      <category>apple</category>
      <category>ios-development</category>
      <description>&lt;p&gt;Do your TestFlight or Crashlytics have change notes that say something useless like &lt;code&gt;Fixed bugs&lt;/code&gt;? This is probably as useful as the equally ambiguous commit message &lt;code&gt;fixed&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Or maybe you painstakingly look through your PRs to find out what changes went in since your last release and scribble something out quickly so you can just get the build ready for your product or testing teams.&lt;/p&gt;&#xA;&lt;p&gt;This process of writing up beta/internal change notes can be streamlined and save devs precious brain cells that can be used for more valuable decision making.&lt;/p&gt;&#xA;&lt;h2 id=&#34;step-1-ensure-youre-tagging-your-submission-commits&#34;&gt;&#xA;  Step 1: Ensure you’re tagging your submission commits&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#step-1-ensure-youre-tagging-your-submission-commits&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I like to tag submission commits with tags shaped like:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;submission/v4.2.0&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Tagging with this strategy:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Marks a time in history of exactly what the repo looked like when v4.2.0 was submitted to the App Store&lt;/li&gt;&#xA;&lt;li&gt;Groups all submissions together so they can be easily referenced in the future&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;step-2-fastlane---get-change-notes&#34;&gt;&#xA;  Step 2: Fastlane - Get Change Notes&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#step-2-fastlane---get-change-notes&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;If you’re not already using &lt;a href=&#34;https://fastlane.tools/&#34;&gt;fastlane&lt;/a&gt;, I highly recommend adding it to your project. If anything, at least use &lt;a href=&#34;https://docs.fastlane.tools/actions/match/&#34;&gt;fastlane match&lt;/a&gt; to manage your certificates and provisioning profiles to save you and your team’s sanity.&lt;/p&gt;&#xA;&lt;p&gt;Add this new lane to your &lt;code&gt;Fastfile&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;desc&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Get current change notes&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;lane&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;:get_change_notes&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;# use subject/title of each git commit and bullet each line&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;changelog_from_git_commits&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;ss&#34;&gt;pretty&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;- %s&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;ss&#34;&gt;tag_match_pattern&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;submission/*&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;ss&#34;&gt;merge_commit_filtering&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;exclude_merges&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;step-3-fastlane---add-change-notes-to-pilotcrashlytics&#34;&gt;&#xA;  Step 3: Fastlane - Add Change Notes to Pilot/Crashlytics&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#step-3-fastlane---add-change-notes-to-pilotcrashlytics&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Now you can take the output from &lt;code&gt;get_change_notes&lt;/code&gt; and use it in your call to &lt;code&gt;pilot&lt;/code&gt; or &lt;code&gt;crashlytics&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;pilot&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;changelog&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;get_change_notes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;crashlytics&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;ss&#34;&gt;notes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;get_change_notes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;&#xA;&lt;p&gt;I helped set this up as part of the Hipmunk iOS app’s release process. It was  a preliminary step that was needed in order to have automated releases that could be deployed from CI, and it saved the team countless hours since it was implemented.&lt;/p&gt;&#xA;&lt;p&gt;There just doesn’t need to be any need to think about what to go into these notes anymore, and teammates outside of the dev team have clear visibility into what’s going into builds.&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/apple/&#34;&gt;#apple&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Little Countdown app integrates with iOS Calendar</title>
      <link>https://ivanthinking.net/2019/03/25/little-countdown-app-integrates-with-ios-calendar/</link>
      <pubDate>Mon, 25 Mar 2019 00:00:00 +0000</pubDate>
      <guid>https://ivanthinking.net/2019/03/25/little-countdown-app-integrates-with-ios-calendar/</guid>
      <category>ready-time</category>
      <category>software-development</category>
      <category>nerdtower</category>
      <category>ios-development</category>
      <category>apple</category>
      <description>&lt;h2 id=&#34;latest-little-countdown-update-lets-users-quickly-add-new-countdowns-based-on-ios-calendar-events&#34;&gt;&#xA;  Latest Little Countdown update lets users quickly add new Countdowns based on iOS Calendar events&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#latest-little-countdown-update-lets-users-quickly-add-new-countdowns-based-on-ios-calendar-events&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;img class=&#34;page-img&#34; src=&#34;https://ivanthinking.net/2019/03/25/little-countdown-app-integrates-with-ios-calendar/calendar-integration_hu_88d2634f11ad24d5.png&#34; alt=&#34;Little Countdown Calendar integration&#34; style=&#34;max-width:100%;max-height:480px;width:auto;height:auto&#34;  /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;A new version of Little Countdown is now available on the Apple App Store that&#xA;integrates with the iOS Calendar. Now, when users begin typing a Countdown title, they&amp;rsquo;ll be&#xA;presented with a list of upcoming Calendar events that they can select to start the&#xA;Countdown with.&lt;/p&gt;&#xA;&lt;p&gt;The latest version of Little Countdown (v1.3.0) makes adding new Countdown entries based off of&#xA;iOS Calendar events quick and simple. When users select an upcoming Calendar event, Countdown will&#xA;immediately:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Set the deadline of the Countdown to the time of the selected Calendar event&lt;/li&gt;&#xA;&lt;li&gt;Set the title of the Countdown to be the name of the Calendar event&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&amp;ldquo;It&amp;rsquo;s now even easier and faster to add a Countdown,&amp;rdquo; says Ivan Tse, creator of Little Countdown. &amp;ldquo;If&#xA;you have a flight coming up in your Calendar, you won&amp;rsquo;t have to remember the time or flight number&#xA;anymore: just let Little Countdown to the work for you. It&amp;rsquo;s a little thing, but we&amp;rsquo;re constantly working&#xA;on ways to make it easier and simpler for users to use Countdown. We think users are really going to&#xA;love this, and it&amp;rsquo;ll streamline setting up new Countdowns.&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;To see upcoming Calendar events in Little Countdown, users can type a title matching an upcoming Calendar event&#xA;when adding a new Countdown.&lt;/p&gt;&#xA;&lt;p&gt;Little Countdown is currently available on the iOS App Store for $1.99.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://itunes.apple.com/us/app/little-countdown/id1412470365&#34;&gt;Link to download iOS App&lt;/a&gt; &lt;br&gt;&#xA;&lt;a href=&#34;https://getreadytime.com/&#34;&gt;Link to website&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/ready-time/&#34;&gt;/ready-time&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/apple/&#34;&gt;#apple&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Little Countdown iOS app adds support for Siri Shortcuts</title>
      <link>https://ivanthinking.net/2019/01/07/little-countdown-ios-app-adds-support-for-siri-shortcuts/</link>
      <pubDate>Mon, 07 Jan 2019 00:00:00 +0000</pubDate>
      <guid>https://ivanthinking.net/2019/01/07/little-countdown-ios-app-adds-support-for-siri-shortcuts/</guid>
      <category>ready-time</category>
      <category>software-development</category>
      <category>nerdtower</category>
      <category>ios-development</category>
      <category>apple</category>
      <description>&lt;h2 id=&#34;with-latest-update-to-ios-app-little-countdown-users-can-now-hear-their-current-task-by-asking-siri&#34;&gt;&#xA;  With latest update to iOS app, Little Countdown users can now hear their current task by asking Siri.&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#with-latest-update-to-ios-app-little-countdown-users-can-now-hear-their-current-task-by-asking-siri&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;A new update to the Little Countdown app for iOS helps users save time by not having to open the app to check their current task. With a simple voice command such as, &amp;ldquo;Hey Siri, get my current task,&amp;rdquo; users will hear the task that they&amp;rsquo;ve scheduled to be working on at that time. Users can also add shortcuts for getting the current Countdown or for creating new Countdowns. Useful shortcuts will be suggested in Spotlight searches.&lt;/p&gt;&#xA;&lt;p&gt;This latest release of Little Countdown (v1.1.0) includes integration with Siri Shortcuts to perform common useful actions such as:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Getting the current task&lt;/li&gt;&#xA;&lt;li&gt;Getting the current Countdown&lt;/li&gt;&#xA;&lt;li&gt;Creating a new Countdown&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&amp;ldquo;This latest update is great for the mornings when you&amp;rsquo;re rushing around the house and trying to make sure you&amp;rsquo;ve handled everything you need for the day,&amp;rdquo; says Ivan Tse, creator of Little Countdown. &amp;ldquo;When drinking that morning coffee or taking a shower, it&amp;rsquo;s not always easy to have the phone in hand. It&amp;rsquo;s so much faster and easier to run a Siri Shortcut to just ask for your current task with your voice. Little Countdown is designed to help streamline your tasks and minimize extra things in your daily life, and this update does exactly that.&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;Siri Shortcuts and custom commands for Little Countdown can be activated from the Siri &amp;amp; Search menu in the Settings app for iPhone or iPad.&lt;/p&gt;&#xA;&lt;p&gt;Little Countdown is currently available on the iOS App Store for $1.99.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://itunes.apple.com/us/app/little-countdown/id1412470365&#34;&gt;Link to download iOS App&lt;/a&gt; &lt;br&gt;&#xA;&lt;a href=&#34;https://getreadytime.com/&#34;&gt;Link to website&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/ready-time/&#34;&gt;/ready-time&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/apple/&#34;&gt;#apple&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Introducing the Little Countdown iOS app: helping travelers get to the gate on time</title>
      <link>https://ivanthinking.net/2018/12/10/introducing-the-little-countdown-ios-app-helping-travelers-get-to-the-gate-on-time/</link>
      <pubDate>Mon, 10 Dec 2018 00:00:00 +0000</pubDate>
      <guid>https://ivanthinking.net/2018/12/10/introducing-the-little-countdown-ios-app-helping-travelers-get-to-the-gate-on-time/</guid>
      <category>ready-time</category>
      <category>software-development</category>
      <category>nerdtower</category>
      <category>ios-development</category>
      <description>&lt;h2 id=&#34;new-app-from-creator-of-the-mindfulness-app-little-memory-helps-relieve-traveler-stress-by-getting-them-ready-and-out-the-door-to-the-airport-on-time&#34;&gt;&#xA;  New app from creator of the mindfulness app, Little Memory, helps relieve traveler stress by getting them ready and out the door to the airport on time.&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#new-app-from-creator-of-the-mindfulness-app-little-memory-helps-relieve-traveler-stress-by-getting-them-ready-and-out-the-door-to-the-airport-on-time&#34;&gt;&#xA;    &lt;i class=&#34;fa fa-link&#34; aria-hidden=&#34;true&#34;&gt;&lt;/i&gt;&#xA;  &lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Available today, Little Countdown for iOS helps travelers prepare for their trip and stay on schedule to avoid missing their flight. After entering tasks they need to accomplish before their flight, users of Little Countdown will see when to start their plan and begin receiving alerts for each task when it&amp;rsquo;s time for each one.&lt;/p&gt;&#xA;&lt;p&gt;With this release of Little Countdown, users can:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Work back from the flight&amp;rsquo;s boarding time to schedule when to leave for the airport&lt;/li&gt;&#xA;&lt;li&gt;Enter and order tasks that need to be accomplished before leaving, such as: packing, calling a car for the airport, and going through security&lt;/li&gt;&#xA;&lt;li&gt;Alerts for each task to stay on schedule&lt;/li&gt;&#xA;&lt;li&gt;Save plans to be re-used and modified for future flights&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&amp;ldquo;There&amp;rsquo;s a lot of stress and anxiety when flying, especially during the holidays,&amp;rdquo; explains Ivan Tse, creator of Little Countdown. &amp;ldquo;It&amp;rsquo;s always tricky to figure out all the things you need to do and when you need to do them before leaving for the airport. Little Countdown is a companion for travelers to help take the mind off the clock, spend some time to breathe, and just enjoy the moment.&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;Little Countdown is currently available on the iOS App Store for $1.99.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://itunes.apple.com/us/app/little-countdown/id1412470365&#34;&gt;Link to download iOS App&lt;/a&gt; &lt;br&gt;&#xA;&lt;a href=&#34;https://getreadytime.com/&#34;&gt;Link to website&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;p class=&#34;feed-terms&#34;&gt;&lt;a href=&#34;https://ivanthinking.net/categories/ready-time/&#34;&gt;/ready-time&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/categories/software-development/&#34;&gt;/software-development&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/nerdtower/&#34;&gt;#nerdtower&lt;/a&gt; &lt;a href=&#34;https://ivanthinking.net/tags/ios-development/&#34;&gt;#ios-development&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
  </channel>
</rss>
