<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>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>
      <description>&lt;p&gt;Ready Time 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;</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>
      <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;</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>
      <description>&lt;p&gt;I&amp;rsquo;ve been in the weeds getting Little Memory 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;</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>
      <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;</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>
      <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;</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>
      <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;</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>
      <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;</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>
      <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;    &lt;img src=&#34;01_HeroFlightPlan.png&#34; alt=&#34;Ready Time - Flight Plan&#34;&gt;&#xA;  &#xA;    &#xA;    &#xA;    &lt;img src=&#34;03_Notifications.png&#34; alt=&#34;Ready Time - Notifications&#34;&gt;&#xA;  &#xA;&lt;/div&gt;&#xA;&#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>
      <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/category/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;</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>
      <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;</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>
      <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; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SwiftUI&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; env: [String : String] { ProcessInfo.processInfo.environment }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;LoginForm&lt;/span&gt;: View {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  @State &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; username: String = env[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;AUTH_DEFAULT_USERNAME&amp;#34;&lt;/span&gt;] ?? &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  @State &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; password: String = env[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;AUTH_DEFAULT_PASSWORD&amp;#34;&lt;/span&gt;] ?? &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#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;</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>
      <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;</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>
      <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; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; text = &lt;span style=&#34;color:#e6db74&#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 style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; Email her at jenny@tutone.net.&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#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 style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;VStack {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Text(&lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt;! AttributedString(markdown: text))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#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; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; dataDetector: NSDataDetector = {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; types: NSTextCheckingResult.CheckingType = [.link, .phoneNumber]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt;! .&lt;span style=&#34;color:#66d9ef&#34;&gt;init&lt;/span&gt;(types: types.rawValue)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;JennyView&lt;/span&gt;: View {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; body: some View {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        VStack {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Text(attributedString(from: text)&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;attributedString&lt;/span&gt;(from text: String) -&amp;gt; AttributedString? {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; attributed = AttributedString(text)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; fullRange = NSMakeRange(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, text.count)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; matches = dataDetector.matches(&lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt;: text, options: [], range: fullRange)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;guard&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;matches.isEmpty &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; { &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; result &lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; matches {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;guard&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; range = Range&amp;lt;AttributedString.Index&amp;gt;(result.range, &lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt;: attributed) &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;switch&lt;/span&gt; result.resultType {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; .phoneNumber:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;guard&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; phoneNumber = result.phoneNumber,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; url = URL(string: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sms://&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;\(&lt;/span&gt;phoneNumber&lt;span style=&#34;color:#e6db74&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                attributed[range].link = url&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; .link:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;guard&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; url = result.url &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                attributed[range].link = url&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;default&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; attributed&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#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;</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>
      <description>&lt;p&gt;&#xA;&#xA;  &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;  /&gt;&#xA;&#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;</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>
      <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;&#xA;&#xA;  &#xA;  &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;  /&gt;&#xA;&#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;&#xA;&#xA;  &#xA;  &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.png&#34; alt=&#34;iPhone Upgrade Program, Buying, Trade In 6 years Analysis&#34;  /&gt;&#xA;&#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;</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>
      <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; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ApiClient&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; secret: String&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;init&lt;/span&gt;(secret: String) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;self&lt;/span&gt;.secret = secret&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;LoginService&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; apiClient: ApiClient&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; shared: LoginService = {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; apiClient = ApiClient(secret: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;CLIENT_SECRET&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; LoginService(apiClient: apiClient)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#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;</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>
      <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://littlecountdown.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;</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>
      <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; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&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; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;String_ExtensionsTests&lt;/span&gt;: XCTestCase {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;testAsDate_invalid_notIso&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;throws&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; invalidString = &lt;span style=&#34;color:#e6db74&#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 style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; date = invalidString.asDate()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        XCTAssertNil(date, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;should not have converted string to date&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;testAsDate_invalid_missingTimeDelimiter&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;throws&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; missingTimeDelimiterString = &lt;span style=&#34;color:#e6db74&#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 style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; date = missingTimeDelimiterString.asDate()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        XCTAssertNil(date, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;should not have converted string to date&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;testAsDate_valid&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;throws&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; isoDateString = &lt;span style=&#34;color:#e6db74&#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 style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; date = isoDateString.asDate()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        XCTAssertNotNil(date, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;should have converted string to date&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; calendar = Calendar(identifier: .gregorian)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; twoHoursInSeconds = &lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; amsterdamTimeZone = TimeZone(secondsFromGMT: twoHoursInSeconds)&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; components = calendar.dateComponents(&lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt;: amsterdamTimeZone, from: date!)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        XCTAssertEqual(components.year, &lt;span style=&#34;color:#ae81ff&#34;&gt;2020&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;has correct year&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        XCTAssertEqual(components.month, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;has correect month&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        XCTAssertEqual(components.day, &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;has correct day 🌲&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        XCTAssertEqual(components.hour, &lt;span style=&#34;color:#ae81ff&#34;&gt;16&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;has correct hour&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        XCTAssertEqual(components.minute, &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;has correct minute&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        XCTAssertEqual(components.second, &lt;span style=&#34;color:#ae81ff&#34;&gt;42&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;has correct second&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        XCTAssertEqual(components.timeZone, amsterdamTimeZone, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;has correct time zone&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#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; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;extension&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;String&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;asDate&lt;/span&gt;() -&amp;gt; Date? {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; formatter = ISO8601DateFormatter()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; formatter.date(from: &lt;span style=&#34;color:#66d9ef&#34;&gt;self&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#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;</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>
      <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;&#xA;&#xA;  &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;  /&gt;&#xA;&#xA;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;viewDidLoad&lt;/span&gt;() {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;super&lt;/span&gt;.viewDidLoad()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; loveLabel = UILabel()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    loveLabel.text = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;LOVE&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    loveLabel.backgroundColor = .systemRed&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; kissesLabel = UILabel()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    kissesLabel.text = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Kisses&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    kissesLabel.backgroundColor = .systemYellow&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; xoxoLabel = UILabel()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    xoxoLabel.text = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;XOXO&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    xoxoLabel.backgroundColor = .systemBlue&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; stack = UIStackView(arrangedSubviews: [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        loveLabel,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        kissesLabel,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        xoxoLabel,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ])&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    stack.translatesAutoresizingMaskIntoConstraints = &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    stack.axis = .horizontal&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    stack.spacing = &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    view.addSubview(stack)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    NSLayoutConstraint.activate([&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        stack.centerXAnchor.constraint(equalTo: view.centerXAnchor),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        stack.centerYAnchor.constraint(equalTo: view.centerYAnchor),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        stack.leftAnchor.constraint(equalToSystemSpacingAfter: view.leftAnchor, multiplier: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        view.rightAnchor.constraint(equalToSystemSpacingAfter: stack.rightAnchor, multiplier: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ])&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#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; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; loveLabel = UILabel()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    loveLabel.text = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;LOVE&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    loveLabel.backgroundColor = .systemRed&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    loveLabel.setContentHuggingPriority(.defaultHigh, &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt;: .horizontal)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&#xA;&#xA;  &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;  /&gt;&#xA;&#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;</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>
      <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;&#xA;&#xA;  &lt;img class=&#34;page-img&#34; src=&#34;https://ivanthinking.net/images/software-dev/coach-marks01.png&#34; alt=&#34;Instructional Overlay&#34;  /&gt;&#xA;&#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; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-swift&#34; data-lang=&#34;swift&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;buildButton&lt;/span&gt;(with title: String) -&amp;gt; UIButton {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; button = UIButton(type: .system)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    button.backgroundColor = .systemOrange&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    button.setTitle(title, &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt;: .normal)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; button&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;viewDidLoad&lt;/span&gt;() {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;super&lt;/span&gt;.viewDidLoad()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; buttonText = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Put me in coach!&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; button = buildButton(with: buttonText)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    button.translatesAutoresizingMaskIntoConstraints = &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; overlay = UIView(frame: .zero)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    overlay.translatesAutoresizingMaskIntoConstraints = &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    overlay.backgroundColor = UIColor.black.withAlphaComponent(&lt;span style=&#34;color:#ae81ff&#34;&gt;0.5&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; coachButton = buildButton(with: buttonText)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    coachButton.translatesAutoresizingMaskIntoConstraints = &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    coachButton.isUserInteractionEnabled = &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; coachLabel = UILabel(frame: .zero)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    coachLabel.translatesAutoresizingMaskIntoConstraints = &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    coachLabel.textColor = .white&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    coachLabel.text = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Get player in the game&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    view.addSubview(button)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    view.addSubview(overlay)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    view.addSubview(coachButton)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    view.addSubview(coachLabel)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    NSLayoutConstraint.activate([&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        button.centerXAnchor.constraint(equalTo: &lt;span style=&#34;color:#66d9ef&#34;&gt;self&lt;/span&gt;.view.centerXAnchor),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        button.centerYAnchor.constraint(equalTo: &lt;span style=&#34;color:#66d9ef&#34;&gt;self&lt;/span&gt;.view.centerYAnchor),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        coachButton.centerXAnchor.constraint(equalTo: button.centerXAnchor),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        coachButton.centerYAnchor.constraint(equalTo: button.centerYAnchor),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        coachLabel.centerXAnchor.constraint(equalTo: coachButton.centerXAnchor),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        coachLabel.centerYAnchor.constraint(equalTo: coachButton.bottomAnchor, constant: &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        overlay.topAnchor.constraint(equalTo: &lt;span style=&#34;color:#66d9ef&#34;&gt;self&lt;/span&gt;.view.topAnchor),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        overlay.bottomAnchor.constraint(equalTo: &lt;span style=&#34;color:#66d9ef&#34;&gt;self&lt;/span&gt;.view.bottomAnchor),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        overlay.leftAnchor.constraint(equalTo: &lt;span style=&#34;color:#66d9ef&#34;&gt;self&lt;/span&gt;.view.leftAnchor),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        overlay.rightAnchor.constraint(equalTo: &lt;span style=&#34;color:#66d9ef&#34;&gt;self&lt;/span&gt;.view.rightAnchor),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ])&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#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;&#xA;&#xA;  &lt;img class=&#34;page-img&#34; src=&#34;https://ivanthinking.net/images/software-dev/coach-marks02.png&#34; alt=&#34;Instructional Overlay Views Exploded&#34;  /&gt;&#xA;&#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;</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>
      <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://littlecountdown.com/&#34;&gt;little countdown&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>
      <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; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;desc &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Get current change notes&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;lane &lt;span style=&#34;color:#e6db74&#34;&gt;:get_change_notes&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#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 style=&#34;display:flex;&#34;&gt;&lt;span&gt;  changelog_from_git_commits(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;pretty&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;- %s&amp;#39;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;tag_match_pattern&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;submission/*&amp;#39;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;merge_commit_filtering&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;exclude_merges&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#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; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pilot(&lt;span style=&#34;color:#e6db74&#34;&gt;changelog&lt;/span&gt;: get_change_notes)&#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; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;crashlytics(&lt;span style=&#34;color:#e6db74&#34;&gt;notes&lt;/span&gt;: get_change_notes)&#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;</description>
    </item>
  </channel>
</rss>
