<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Aryaman Sharda]]></title><description><![CDATA[Deep dives, projects, and tutorials.]]></description><link>https://digitalbunker.dev/</link><image><url>https://digitalbunker.dev/favicon.png</url><title>Aryaman Sharda</title><link>https://digitalbunker.dev/</link></image><generator>Ghost 5.2</generator><lastBuildDate>Fri, 23 Feb 2024 07:45:54 GMT</lastBuildDate><atom:link href="https://digitalbunker.dev/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Week In The Life Of A Staff Software Engineer]]></title><description><![CDATA[Unlike the "Day In The Life" videos all over YouTube, I wanted to offer a transparent account of what it's actually like to work in the field]]></description><link>https://digitalbunker.dev/day-in-the-life-staff-software-engineer/</link><guid isPermaLink="false">65c85735d6cd890c7ba8a2e1</guid><category><![CDATA[articles]]></category><category><![CDATA[ios]]></category><category><![CDATA[programming]]></category><category><![CDATA[software engineering]]></category><category><![CDATA[lifestyle]]></category><dc:creator><![CDATA[Aryaman Sharda]]></dc:creator><pubDate>Mon, 19 Feb 2024 19:10:51 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1534665482403-a909d0d97c67?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDh8fHByb2dyYW1tZXJ8ZW58MHx8fHwxNzA4Mjk4OTg0fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1534665482403-a909d0d97c67?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDh8fHByb2dyYW1tZXJ8ZW58MHx8fHwxNzA4Mjk4OTg0fDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Week In The Life Of A Staff Software Engineer"><p>If you&apos;ve spent any time on YouTube, chances are you&apos;ve seen those &quot;Day In The Life Of A Software Engineer&quot; videos. They&apos;re often staged portrayals of elaborate morning routines and luxury high-rise apartments with cinematography that rivals Hollywood.</p><p>These videos excel at portraying an idyllic day: from sunrise yoga classes to artisanal coffee brewing to rock climbing montages. However, for all their visual appeal, there&apos;s often a glaring oversight: a genuine perspective of what it&apos;s like to <em>work</em> as a software engineer.</p><p>Debugging, problem-solving, technical discussions, collaboration, code reviews, release planning, and other critical aspects of the job receive at most a passing mention. </p><p>So, over this past week, I&apos;ve kept detailed records of my workday in order to provide a realistic view of a typical workweek as a Staff iOS Software Engineer at <a href="https://turo.com/">Turo</a>. </p><p>It&apos;s likely more detailed than most people care to see, but when I was first exploring this career, this is the kind of play-by-play I was looking for.</p><blockquote>I&apos;ve replaced real project names with fake ones (movie names) just to be on the safe side and I&apos;ve intentionally been a bit vague with the details in some areas.<br><br>I like my job and I want to keep it. &#x1F92B;</blockquote><hr><h2 id="monday">Monday</h2><ul><li><strong>8 am: </strong>Rest day from the gym. Personal errands in the morning, apartment cleaning, quick grocery run, coffee, etc. </li><li><strong>9 am: </strong>We still have some Objective-C in our codebase, so last Friday, I had started converting one of those classes to Swift. I picked that work back up and wrote new unit and snapshot tests.</li><li><strong>9:40 am: </strong>Opened up a PR for the conversion and did a <a href="https://github.com/aryamansharda/iOS-Pull-Request-Checklist">quick self-review</a>.</li><li><strong>10 am: </strong>I had another PR ready to go on Friday, but I was waiting for translations that, fortunately, came through over the weekend. So, I pulled in the new translations, addressed the final code review comments, and requested another round of review from the team.</li><li><strong>10:15 am: </strong>[Backlog Grooming] We&apos;ve got a new project - Interstellar - on the horizon and it involves a somewhat sensitive area of the codebase. While the details of the new project aren&apos;t finalized, I know that there&apos;s a couple of components that will need to be more reusable in order to support this new experience. So, I start looking into refactoring them and improving our code coverage for this area of the codebase.</li><li><strong>11 am: </strong>A quick breakfast, more coffee, and a little reading on HackerNews and <a href="https://news.digitalbunker.dev/">iOS News</a>.</li><li><strong>11:20 am: </strong>Continued working on the refactor.</li><li><strong>12:20 pm:</strong> Taking a short break to address minor comments on the PR I opened this morning.</li><li><strong>12:40 pm:</strong> Lunch and some code review.</li><li><strong>1:40 pm:</strong> My main focus right now is Project Matrix. We&apos;re on the home stretch, so just working through some of the final bug fixes, TODOs, and release blockers.</li><li><strong>3 pm:</strong> Came across some potential tech debt in the codebase. So, I started searching through Slack messages and older channels to try to better understand the expected behavior.</li><li><strong>3:30 pm: </strong>This is looking more and more like a bug now rather than just tech debt. I pinged one of my Android co-workers to see if they&apos;re experiencing the same thing - no luck.</li><li><strong>4 pm:</strong> I&apos;m leading the small team spearheading Turo&apos;s move to SwiftUI. Just had a brief call with &#xA0;a few of the other team members to discuss the status of the tooling (like linters and pre-commit hooks) and other helpers we&apos;re in the process of developing. </li></ul><blockquote>I&apos;ll be talking about this transition in more detail at an upcoming iOS conference. &#x1F609;</blockquote><ul><li><strong>4:20 pm:</strong> Returned to investigating the tech debt / bug topic. After discussing with Product and Design team members, we decide that it&apos;s a low priority issue we can address after launching Matrix.</li><li><strong>4:30 pm: </strong>Our release engineer just released the latest version of our iOS app which includes a major project I&apos;ve been working on for the past few weeks. So, I started monitoring our metrics and logs for any new issues following the initial rollout of the feature.</li><li><strong>4:40 pm:</strong> Back to Matrix now. Concentrating on some TODOs and writing documentation. I&apos;m still checking the metrics and logs periodically and will continue to do so throughout the evening.</li><li><strong>5:15 pm:</strong> 1:1 with a teammate I&apos;m mentoring.</li><li><strong>5:40:</strong> Signed off for the day.</li></ul><h2 id="tuesday">Tuesday</h2><ul><li><strong>6:30 am:</strong> Gym.</li><li><strong>8:30 am:</strong> Shower, breakfast, coffee, etc.</li><li><strong>9 am:</strong> Looking at a few code review requests that came in yesterday evening.</li><li><strong>10 am:</strong> [Standup]</li><li><strong>10:15 am:</strong> [Sprint Planning]</li><li><strong>11 am:</strong> Continued working through the final release blockers for Matrix.</li><li><strong>12:30 pm:</strong> Lunch and some code review.</li><li><strong>1 pm:</strong> Still working on Matrix, but focusing on adding tests and addressing code review comments.</li><li><strong>1:30 pm:</strong> Most Matrix release blockers are now resolved. </li><li><strong>1:40 pm:</strong> Found an edge case on mobile clients for Matrix that would require changes to the API response. Set up a meeting with Android and backend engineers to discuss further.</li><li><strong>2:30 pm:</strong> Ran into an offline data storage edge case that effects Matrix&apos;s implementation. Checked in with the Android team to see how they&apos;re navigating this issue. We align on similar solution for both platforms.</li><li><strong>3 pm:</strong> I&apos;m the technical lead for Interstellar. It&apos;s still in it&apos;s early stages, but met with Product and Design teams to provide initial engineering input on designs, project scope, and estimated development timelines.</li><li><strong>4 pm: </strong>Matrix team meeting where everyone aligned their status on release-blocking issues, discussed pending analytics events, and reviewed our phased rollout strategy.</li><li><strong>4:45 pm:</strong> Responded to comments on open PRs and successfully merged in a couple of them.</li><li><strong>5:30 pm:</strong> Signed off for the day.</li></ul><h2 id="wednesday">Wednesday</h2><ul><li><strong>6:30 am:</strong> Gym.</li><li><strong>8:30 am:</strong> Shower, breakfast, coffee, etc.</li><li><strong>9 am:</strong> Code review (re-reviews mostly) and looked at a proposal (request for comments) from another member on the iOS team.</li><li><strong>9:40 am:</strong> Left for the office.</li><li><strong>10 am: </strong>Arrived at the office and continued working on Matrix. Mostly focused on implementing the changes from our launch discussion yesterday (updating analytics events, PR comments, and a bit more testing).</li><li><strong>11 am: </strong>Learned that another coworker (on a different team) was modifying the behavior of an existing feature that Matrix relied on as part of an A/B test. Had a quick call to ensure that the new changes would still be compatible with both of our use cases.</li><li><strong>12 pm:</strong> Catered lunch in the office and spent some time catching up with coworkers.</li><li><strong>1 pm:</strong> &#xA0;Stopped by a Valentine&apos;s Day event organized by the building with a few coworkers.</li><li><strong>1:15 pm:</strong> Back to Matrix. Noted some small differences in how things were behaving across different platforms (Web, Android, and iOS), so I started a call with folks from Product, Design, and Engineering to make sure we were all on the same page.</li><li><strong>2 pm: </strong>Guiding a coworker through a SwiftUI issue they were having and brought them up to speed on the SwiftUI transition team&apos;s recommend conventions and best practices.</li><li><strong>3 pm: </strong>Discovered a bug in a recently launched feature and released a hot fix.</li><li><strong>3:30 pm:</strong> SwiftUI transition team meeting. Talked through some of the development issues the larger iOS team was running into, how we wanted to prioritize the remaining work, and a presentation from one of the members about the newest design system components they had built out.</li><li><strong>4:40 pm:</strong> Heading home.</li><li><strong>5 pm:</strong> Some misc. tasks: updating some analytics events, a couple SwiftUI questions from coworkers, and sending out a new internal build for Matrix testing.</li><li><strong>6 pm:</strong> Signed off for the day.</li></ul><h2 id="thursday">Thursday</h2><ul><li><strong>6:30 am:</strong> Gym.</li><li><strong>8:30 am:</strong> Shower, breakfast, coffee, etc.</li><li><strong>9 am:</strong> [Engineering All-Hands / Tech Update] Company-wide engineering updates and presentations from other engineers about new tooling, initiatives, or other developments they&apos;re working on.</li><li><strong>10 am:</strong> [Standup]</li><li><strong>10:15 am:</strong> Updated portions of the Matrix project&apos;s UI to use the new design system components.</li><li><strong>12 pm:</strong> Discovered a potential release blocker for Matrix on the backend. Meeting with product, designers, and other engineers on the team to triage and align on a solution.</li><li><strong>12:30 pm:</strong> Engineering only meeting to delve deeper into the implementation details of the new solution.</li><li><strong>1 pm:</strong> Group code review session - we split into small groups of 3-4 engineers to collectively review PRs. It&apos;s hands down my favorite meeting of the week and where I feel I learn the most.</li><li><strong>2 pm: </strong>Back to Matrix.</li><li><strong>2:30 pm:</strong> Quick call with a coworker to help them debug a SwiftUI issue.</li><li><strong>2:45 pm:</strong> Back to Matrix.</li><li><strong>3:30 pm:</strong> Starting today, I&apos;m also supporting Project Spectre. We gathered to go over requirements and to start the handoff to my team. (I usually don&apos;t work on these many projects at once.)</li><li><strong>4:30 pm:</strong> Back to Matrix.</li><li><strong>5 pm: </strong>Encountered a bug that affects the entire app, not just Matrix. So, I put together a tentative solution as a starting point for discussion. I don&apos;t love the solution, but I&apos;m hoping - with the team&apos;s help - we can come up with something more refined.</li><li><strong>5:45 pm:</strong> Created a proposal (RFC) and requested feedback from the team.</li><li><strong>6 pm:</strong> Addressed some feedback on an open PR and merged it in.</li><li><strong>6:30 pm:</strong> Signed off for the day.</li></ul><h2 id="friday">Friday</h2><ul><li><strong>6:30 am:</strong> Gym.</li><li><strong>8:30 am:</strong> Shower, breakfast, coffee, etc.</li><li><strong>9 am:</strong> Looking at a few code review requests that came in yesterday evening.</li><li><strong>10 am: </strong>Team &quot;coffee meeting&quot; over Zoom. With many of us working remotely, taking a little time to just chat and hang out helps us feel more connected as a team.</li><li><strong>10:30 am:</strong> Had a meeting with the other engineers on my team to work through the spikes, tasks, and API definitions needed for Spectre now that it&apos;s officially assigned to our team.</li><li><strong>11 am:</strong> Doctor&apos;s appointment.</li><li><strong>12:15 pm:</strong> Over lunch, I brought Matrix&apos;s integration branch up to date with the main line (mostly resolving merge conflicts) and addressed some open PR comments.</li><li><strong>1 pm:</strong> iOS team meeting (discussed improvements to our snapshot testing setup, had a coworker present on DocC, and reviewed a new internal developer tool).</li><li><strong>2 pm:</strong> SwiftUI Office Hours. Myself and other members of the SwiftUI transition team dedicate time every week to assist engineers encountering SwiftUI development challenges as we navigate this transition from UIKit.</li><li><strong>3 pm:</strong> More code review.</li><li><strong>3:30 pm:</strong> A coworker reached out a SwiftUI issue they were having. After some debugging, it seems like a dead end until we can drop iOS 15 support. We find a workaround for now, but it&apos;s something I&apos;ll have to bring up with the full transition team next week.</li><li><strong>4 pm:</strong> Back to Matrix. Addressed the remaining TODOs and opened up the final PRs. &#x1F680;</li><li><strong>5:15 pm: </strong> Helped a coworker plan out their new feature&apos;s implementation in SwiftUI.</li><li><strong>6 pm:</strong> Signed off for the day.</li></ul><p>My evenings usually follow a familiar routine. After work, I&apos;ll usually work on side projects like <a href="https://aryamansharda.gumroad.com/l/tcvck">writing books</a>, <a href="https://digitalbunker.dev/">blog posts</a>, <a href="https://indie.watch/">newsletters</a>, or indie app development. Occasionally, I&apos;ll also enjoy a bar trivia night or happy hour during the week too. &#x1F379;</p><hr><p>Hopefully, this deep dive has provided you with a clearer picture of what a typical work week is like for a full-time software engineer. As you can see, every day is different - new problems, new solutions - and that&apos;s all part of the fun. &#x1F680;</p><p>If you&apos;re interested in more articles about iOS Development &amp; Swift, check out my <a href="https://www.youtube.com/c/AryamanSharda">YouTube channel</a> or <a href="https://twitter.com/aryamansharda">follow me on Twitter</a>.</p><p>And, if you&apos;re an indie iOS developer, make sure to <a href="https://indie.watch/">check out my newsletter</a>! Each issue features a new indie developer, so feel free to <a href="https://indie.watch/submit">submit</a> your iOS apps.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://aryamansharda.gumroad.com/l/tcvck"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Ace The iOS Interview</div><div class="kg-bookmark-description">The best investment for landing your dream iOS jobHey there! My name is Aryaman Sharda and I started making iOS apps way back in 2015. Since then, I&#x2019;ve worked for a variety of companies like Porsche, Turo, and Scoop Technologies just to name a few. Over the years, I&#x2019;ve mentored junior engineers, bui&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://public-files.gumroad.com/g9djd65cu1rd3pssapogotmjzlb0" alt="Week In The Life Of A Staff Software Engineer"><span class="kg-bookmark-author">Gumroad</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://public-files.gumroad.com/w4uim9581d8jj4m8r70o4tux5c4b" alt="Week In The Life Of A Staff Software Engineer"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://indie.watch/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Indie Watch</div><div class="kg-bookmark-description">Indie Watch is an exclusive weekly hand-curated newsletter showcasing the best iOS, macOS, watchOS, and tvOS apps from developers worldwide.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://indie.watch/content/images/size/w256h256/2022/05/Favicon_v2.png" alt="Week In The Life Of A Staff Software Engineer"><span class="kg-bookmark-author">Indie Watch</span><span class="kg-bookmark-publisher">Indie Watch</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://indie.watch/content/images/2022/05/Twitter---Facebook-9.png" alt="Week In The Life Of A Staff Software Engineer"></div></a></figure><hr>]]></content:encoded></item><item><title><![CDATA[Launching news.digitalbunker.dev 🚀]]></title><description><![CDATA[<p>As someone who loves staying current with iOS Development changes, my week is basically a marathon of scrolling through blogs, podcasts, newsletters, and YouTube videos.</p><p>But, let&apos;s be real, I&apos;ll inevitably miss out on some cool content. There&apos;s no easy way of staying on</p>]]></description><link>https://digitalbunker.dev/launching-news-digitalbunker-dev/</link><guid isPermaLink="false">657fd5f3d6cd890c7ba8a232</guid><category><![CDATA[articles]]></category><category><![CDATA[developer tool]]></category><category><![CDATA[ios]]></category><category><![CDATA[programming]]></category><category><![CDATA[project]]></category><category><![CDATA[random]]></category><dc:creator><![CDATA[Aryaman Sharda]]></dc:creator><pubDate>Mon, 18 Dec 2023 05:30:09 GMT</pubDate><media:content url="https://digitalbunker.dev/content/images/2023/12/twitter-image.png" medium="image"/><content:encoded><![CDATA[<img src="https://digitalbunker.dev/content/images/2023/12/twitter-image.png" alt="Launching news.digitalbunker.dev &#x1F680;"><p>As someone who loves staying current with iOS Development changes, my week is basically a marathon of scrolling through blogs, podcasts, newsletters, and YouTube videos.</p><p>But, let&apos;s be real, I&apos;ll inevitably miss out on some cool content. There&apos;s no easy way of staying on top of everything.</p><p>So, to solve the problem for myself and other developers, &#xA0;I&apos;m excited to introduce a space that brings together everything you need for iOS Development&#x2014;all in real-time.</p><p>From insightful blogs to engaging podcasts, curated newsletters, new package releases / updates, and YouTube videos, you&apos;ll find everything you need here:</p><figure class="kg-card kg-bookmark-card kg-card-hascaption"><a class="kg-bookmark-container" href="https://news.digitalbunker.dev/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">iOS Development News</div><div class="kg-bookmark-description">Stay up to date with the latest iOS Developments news</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://news.digitalbunker.dev/favicon.ico" alt="Launching news.digitalbunker.dev &#x1F680;"></div></div><div class="kg-bookmark-thumbnail"><img src="https://news.digitalbunker.dev/opengraph-image.png?155ae59effeca868" alt="Launching news.digitalbunker.dev &#x1F680;"></div></a><figcaption>Join our mailing list for weekly iOS recaps and stay ahead of the curve.</figcaption></figure>]]></content:encoded></item><item><title><![CDATA[Working With XcodeKit <> SwiftLeeds]]></title><description><![CDATA[<p>Just wrapped up Day 2 at SwiftLeeds! Big thanks to SwiftLeeds for having me as a speaker! It was great meeting so many people from the iOS Community.</p><p>Here&apos;s a copy of the slides:</p>
        <div class="kg-card kg-file-card kg-file-card-medium">
            <a class="kg-file-card-container" href="https://digitalbunker.dev/content/files/2023/10/SwiftLeeds-Xcode-Editor-Extension-Day-2.pdf" title="Download" download>
                <div class="kg-file-card-contents">
                    <div class="kg-file-card-title">SwiftLeeds Xcode Editor Extension Day 2</div>
                    
                    <div class="kg-file-card-metadata">
                        <div class="kg-file-card-filename">SwiftLeeds Xcode Editor Extension Day 2.pdf</div>
                        <div class="kg-file-card-filesize">19</div></div></div></a></div>]]></description><link>https://digitalbunker.dev/swift-leeds-working-with-xcodekit/</link><guid isPermaLink="false">65250b519934237c2b9f37c6</guid><category><![CDATA[articles]]></category><category><![CDATA[developer tool]]></category><category><![CDATA[implementation]]></category><category><![CDATA[ios]]></category><category><![CDATA[programming]]></category><category><![CDATA[swift]]></category><category><![CDATA[xcode]]></category><dc:creator><![CDATA[Aryaman Sharda]]></dc:creator><pubDate>Tue, 10 Oct 2023 08:30:07 GMT</pubDate><content:encoded><![CDATA[<p>Just wrapped up Day 2 at SwiftLeeds! Big thanks to SwiftLeeds for having me as a speaker! It was great meeting so many people from the iOS Community.</p><p>Here&apos;s a copy of the slides:</p>
        <div class="kg-card kg-file-card kg-file-card-medium">
            <a class="kg-file-card-container" href="https://digitalbunker.dev/content/files/2023/10/SwiftLeeds-Xcode-Editor-Extension-Day-2.pdf" title="Download" download>
                <div class="kg-file-card-contents">
                    <div class="kg-file-card-title">SwiftLeeds Xcode Editor Extension Day 2</div>
                    
                    <div class="kg-file-card-metadata">
                        <div class="kg-file-card-filename">SwiftLeeds Xcode Editor Extension Day 2.pdf</div>
                        <div class="kg-file-card-filesize">19 MB</div>
                    </div>
                </div>
                <div class="kg-file-card-icon">
                    <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><defs><style>.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}</style></defs><title>download-circle</title><polyline class="a" points="8.25 14.25 12 18 15.75 14.25"/><line class="a" x1="12" y1="6.75" x2="12" y2="18"/><circle class="a" cx="12" cy="12" r="11.25"/></svg>
                </div>
            </a>
        </div>
        <p>And you can find the code samples here:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/aryamansharda/EditKitPro"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - aryamansharda/EditKitPro: A multi-purpose Xcode Editor Extension for iOS and macOS developers</div><div class="kg-bookmark-description">A multi-purpose Xcode Editor Extension for iOS and macOS developers - GitHub - aryamansharda/EditKitPro: A multi-purpose Xcode Editor Extension for iOS and macOS developers</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.com/fluidicon.png" alt><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">aryamansharda</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/acf783580f7c015f7fb326e1732254f97810961002438e00d560d7aa4e4a88da/aryamansharda/EditKitPro" alt></div></a></figure><p>If you have any questions, feel free to <a href="https://twitter.com/aryamansharda">message me on Twitter</a> or at <a href="mailto:aryaman@digitalbunker.dev">aryaman@digitalbunker.dev</a>.</p>]]></content:encoded></item><item><title><![CDATA[Bresenham's Line Algorithm]]></title><description><![CDATA[<p>I took a Computer Graphics course during college, and I&apos;ve been revisiting the subject. During this recent exploration, I came across something interesting: Bresenham&apos;s Line Drawing Algorithm. It&apos;s a straightforward and extremely clever method for drawing lines on a pixel-based display.</p><p>Let&apos;s</p>]]></description><link>https://digitalbunker.dev/bresenhams-line-algorithm/</link><guid isPermaLink="false">650206d39934237c2b9f2edc</guid><category><![CDATA[algorithms]]></category><category><![CDATA[articles]]></category><category><![CDATA[basics]]></category><category><![CDATA[computer graphics]]></category><category><![CDATA[computer science]]></category><category><![CDATA[implementation]]></category><category><![CDATA[programming]]></category><category><![CDATA[random]]></category><category><![CDATA[swift]]></category><category><![CDATA[things to know]]></category><dc:creator><![CDATA[Aryaman Sharda]]></dc:creator><pubDate>Thu, 21 Sep 2023 19:49:01 GMT</pubDate><media:content url="https://digitalbunker.dev/content/images/2023/09/Group-10--2-.png" medium="image"/><content:encoded><![CDATA[<img src="https://digitalbunker.dev/content/images/2023/09/Group-10--2-.png" alt="Bresenham&apos;s Line Algorithm"><p>I took a Computer Graphics course during college, and I&apos;ve been revisiting the subject. During this recent exploration, I came across something interesting: Bresenham&apos;s Line Drawing Algorithm. It&apos;s a straightforward and extremely clever method for drawing lines on a pixel-based display.</p><p>Let&apos;s take a closer look at how the algorithm works and we&apos;ll even implement it from scratch in Swift.</p><h2 id="overview">Overview</h2><p>Bresenham&apos;s algorithm - a fundamental method in Computer Graphics - is a clever way of approximating a continuous straight line with discrete pixels, ensuring that the line appears straight and smooth on a pixel-based display.</p><p>The algorithm calculates which pixels to color in order to create a straight line between two points. Since it works exclusively with integer arithmetic, it avoids the need for costly floating-point calculations and is better suited for hardware-constrained environments. Not to mention, it&apos;s also more accurate than simply rounding coordinates to the nearest pixel.</p><h2 id="algorithm">Algorithm</h2><p>Imagine you have a grid of square pixels, and you want to draw a line between A and B.</p><p>The line intersects pixels at odd angles which makes it a challenge to determine precisely which ones to color for the most accurate straight-line representation.</p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-20-at-3.35.11-PM.png" class="kg-image" alt="Bresenham&apos;s Line Algorithm" loading="lazy" width="1358" height="808" srcset="https://digitalbunker.dev/content/images/size/w600/2023/09/Screenshot-2023-09-20-at-3.35.11-PM.png 600w, https://digitalbunker.dev/content/images/size/w1000/2023/09/Screenshot-2023-09-20-at-3.35.11-PM.png 1000w, https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-20-at-3.35.11-PM.png 1358w" sizes="(min-width: 720px) 720px"></figure><blockquote>A is at X = 1, Y = 2. B is at X = 4, Y = 4.</blockquote><p>At a minimum, it&apos;s safe to assume that we should color our starting pixel. So, let&apos;s proceed and do just that:</p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-20-at-3.35.39-PM.png" class="kg-image" alt="Bresenham&apos;s Line Algorithm" loading="lazy" width="1356" height="810" srcset="https://digitalbunker.dev/content/images/size/w600/2023/09/Screenshot-2023-09-20-at-3.35.39-PM.png 600w, https://digitalbunker.dev/content/images/size/w1000/2023/09/Screenshot-2023-09-20-at-3.35.39-PM.png 1000w, https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-20-at-3.35.39-PM.png 1356w" sizes="(min-width: 720px) 720px"></figure><p>Now, for our next step, we need to decide whether to move only along the X-axis or if we should also move diagonally (horizontally and vertically) towards B.</p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-20-at-3.36.12-PM.png" class="kg-image" alt="Bresenham&apos;s Line Algorithm" loading="lazy" width="1360" height="810" srcset="https://digitalbunker.dev/content/images/size/w600/2023/09/Screenshot-2023-09-20-at-3.36.12-PM.png 600w, https://digitalbunker.dev/content/images/size/w1000/2023/09/Screenshot-2023-09-20-at-3.36.12-PM.png 1000w, https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-20-at-3.36.12-PM.png 1360w" sizes="(min-width: 720px) 720px"></figure><p>Bresenham&apos;s algorithm uses the slope of the original line along with a value called the &quot;decision parameter&quot; (which we&apos;ll explore shortly) in tandem to help us choose the pixels that create the most precise straight-line approximation between these two points.</p><p>We&apos;ll begin by calculating the slope of the line between A and B: </p><pre><code>Slope = Rise / Run = &#x394;Y / &#x394;X = (4 - 2) / (4 - 1)
Slope = 2 / 3 = 0.66</code></pre><blockquote>The key point to note is that <code>&#x394;Y = 2</code> and <code>&#x394;X = 3</code> - we&apos;ll use these values again shortly.<br><br>We&apos;ll use the <code>slope</code> value in the edge cases section.</blockquote><hr><h2 id="decision-parameter">Decision Parameter</h2><p>Our goal is to select pixels in a way that keeps them as close as possible to their positions on the original line. In other words, as we start filling in pixels, we want to minimize the error between the actual line position and the nearest selected pixel position at each step.</p><p>This is exactly the problem the decision parameter - <code>P</code> - helps us solve. It guides the algorithm&apos;s incremental decisions about whether to move horizontally or diagonally and which pixel to color next.</p><p>As the algorithm progresses, we&apos;ll continuously update the decision parameter based on whether <code>P</code> was positive, negative, or zero in the previous iteration. This constant refinement helps us minimize the deviation from the original line. If this seems a little abstract, don&apos;t worry - it&apos;ll make more sense as we work through an example.</p><p>This iterative process ensures the line maintains its correct path and has a smooth appearance on the pixel grid.</p><blockquote>The derivation of the decision parameter is extremely interesting and simple to follow. For those curious, you can find <a href="https://youtu.be/RGB-wlatStc?si=fhm_JWDeHTCcPLtL&amp;t=1020">more info here</a> and <a href="https://www.youtube.com/watch?v=TRbwu17oAYY&amp;ab_channel=TheBootStrappers">here</a>.</blockquote><h3 id="initial-state">Initial State</h3><p>The formula for the initial value of the decision parameter is:</p><blockquote><code>P = 2 * &#x394;Y - &#x394;X</code><br><br>where &#x394;X is the difference in x-coordinates between A and B, and &#x394;Y is the difference in y-coordinates. <strong>These values will stay the same throughout the algorithm&apos;s run.</strong></blockquote><p>We&apos;ll also create <code>X</code> and <code>Y</code> values to denote the current coordinate/pixel we&apos;re at and we&apos;ll initialize them with A&apos;s values (the starting point of the line):</p><blockquote><code>X = 1</code><br><code>Y = 2</code></blockquote><h3 id="for-all-other-states">For All Other States</h3><p>As we move from one selected pixel to the next, we&apos;ll make incremental decisions based on the value of <code>P</code>: if <code>P</code> is positive, we move diagonally, and if <code>P</code> is negative, we move only horizontally.</p><p>The decision parameter&apos;s formula changes slightly for all pixels <em>after</em> the starting one.</p><p>If <code>P &lt; 0</code>, we choose the next pixel horizontally and update <code>P</code> using:</p><blockquote><code>P = P + 2 * &#x394;Y</code><br><code>X = X + 1</code></blockquote><p>If <code>P &#x2265; 0</code>, we choose the next pixel diagonally and update <code>P</code> using:</p><blockquote><code>P = P + 2 * &#x394;Y - 2 * &#x394;X</code><br><code>X = X + 1</code><br><code>Y = Y + 1</code><br><br>If you want to better understand how we get these formulas, I highly recommend checking out the resources linked below.</blockquote><p>As we make these decisions, we update the current position accordingly. For example, if we&apos;re moving horizontally, we increment the X-coordinate, and if we&apos;re moving diagonally, we increment both X and Y.</p><h3 id="example">Example</h3><p>Let&apos;s return to our starting state:</p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-20-at-3.35.11-PM.png" class="kg-image" alt="Bresenham&apos;s Line Algorithm" loading="lazy" width="1358" height="808" srcset="https://digitalbunker.dev/content/images/size/w600/2023/09/Screenshot-2023-09-20-at-3.35.11-PM.png 600w, https://digitalbunker.dev/content/images/size/w1000/2023/09/Screenshot-2023-09-20-at-3.35.11-PM.png 1000w, https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-20-at-3.35.11-PM.png 1358w" sizes="(min-width: 720px) 720px"></figure><p>Let&apos;s initialize our decision variable and other starting variables:</p><blockquote><code>P = 2 * &#x394;Y - &#x394;X = 2 * 2 - 3 = 1</code><br><code>X = 1</code><br><code>Y = 2</code></blockquote><p>We know that we&apos;ll at least be coloring in our starting pixel, so let&apos;s check that off first.</p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-20-at-3.36.12-PM.png" class="kg-image" alt="Bresenham&apos;s Line Algorithm" loading="lazy" width="1360" height="810" srcset="https://digitalbunker.dev/content/images/size/w600/2023/09/Screenshot-2023-09-20-at-3.36.12-PM.png 600w, https://digitalbunker.dev/content/images/size/w1000/2023/09/Screenshot-2023-09-20-at-3.36.12-PM.png 1000w, https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-20-at-3.36.12-PM.png 1360w" sizes="(min-width: 720px) 720px"></figure><p>In our case, since <code>P = 1</code>, according to the rules from earlier:</p><blockquote>If <code>P &#x2265; 0</code>, we choose the next pixel diagonally and update <code>P</code> using <code>P = P + 2 * &#x394;Y - 2 * &#x394;X</code>.</blockquote><p>So, we&apos;ll need to increment both <code>X</code> and <code>Y</code> and we&apos;ll need to update the value of <code>P</code> for the next iteration:</p><blockquote><code>X = 2</code><br><code>Y = 3</code><br><code>P = 1 + 2 * 2 - 2 * 3 = -1</code></blockquote><p>Now, we can incrementally create our pixel-based line by repeating this process:</p><ol><li>Fill in the pixel at (X, Y).</li><li>Calculate the updated value of <code>P</code>.</li><li>Calculate the updated value of (X, Y).</li><li>Repeat until (X, Y) matches the line&apos;s end position.</li></ol><h3 id="iteration-2">Iteration 2</h3><p>Let&apos;s color in the pixel we&apos;re currently visiting (2,3) :</p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-20-at-4.30.49-PM.png" class="kg-image" alt="Bresenham&apos;s Line Algorithm" loading="lazy" width="1360" height="814" srcset="https://digitalbunker.dev/content/images/size/w600/2023/09/Screenshot-2023-09-20-at-4.30.49-PM.png 600w, https://digitalbunker.dev/content/images/size/w1000/2023/09/Screenshot-2023-09-20-at-4.30.49-PM.png 1000w, https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-20-at-4.30.49-PM.png 1360w" sizes="(min-width: 720px) 720px"></figure><p>Now, we just repeat the process with <code>P &lt; 0</code> this time:</p><blockquote><code>P = P + 2 * &#x394;Y</code><br><code>P = -1 + 2 * 2 = 3</code></blockquote><p>And, following the rules we defined earlier since <code>P</code> is less than 0, we <em><strong>only</strong></em> move horizontally:</p><blockquote><code>X = 3</code><br><code>Y = 3</code><br><code>P = 3</code></blockquote><h3 id="iteration-3">Iteration 3</h3><p>We&apos;ll color the pixel at our new (X, Y) position just like before:</p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-20-at-4.36.47-PM.png" class="kg-image" alt="Bresenham&apos;s Line Algorithm" loading="lazy" width="1358" height="806" srcset="https://digitalbunker.dev/content/images/size/w600/2023/09/Screenshot-2023-09-20-at-4.36.47-PM.png 600w, https://digitalbunker.dev/content/images/size/w1000/2023/09/Screenshot-2023-09-20-at-4.36.47-PM.png 1000w, https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-20-at-4.36.47-PM.png 1358w" sizes="(min-width: 720px) 720px"></figure><p>Since <code>P &gt; 0</code>:</p><blockquote><code>P = P + 2 * &#x394;Y - 2 * &#x394;X</code><br><code>P = 3 + 2 * 2 - 2 * 3 = 1</code></blockquote><p>According to the rules from earlier, we know that our &#xA0;next move will be a vertical and horizontal move, so our values leading into our final iteration are:</p><blockquote><code>X = 4</code><br><code>Y = 4</code><br><code>P = 1</code></blockquote><h3 id="final-iteration">Final Iteration</h3><p>After filling in the pixel, we can terminate the algorithm since our <code>X</code> and <code>Y</code> values match the end position of the line.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-20-at-5.10.45-PM.png" class="kg-image" alt="Bresenham&apos;s Line Algorithm" loading="lazy" width="1364" height="812" srcset="https://digitalbunker.dev/content/images/size/w600/2023/09/Screenshot-2023-09-20-at-5.10.45-PM.png 600w, https://digitalbunker.dev/content/images/size/w1000/2023/09/Screenshot-2023-09-20-at-5.10.45-PM.png 1000w, https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-20-at-5.10.45-PM.png 1364w" sizes="(min-width: 720px) 720px"><figcaption>&#x1F60A;</figcaption></figure><p>The line was drawn using only basic mathematical operations like addition, subtraction, and multiplication by 2 (which can be efficiently done with bit shifting) - this is one of the key reasons why this algorithm is so fast and efficient.</p><h3 id="implementation">Implementation</h3><pre><code class="language-swift">func drawLine(x1: Int, y1: Int, x2: Int, y2: Int) {
    let dy = y2 - y1
    let dx = x2 - x1
    var d = 2 * dy - dx
    var x = x1
    var y = y1

    while x &lt;= x2 {
        print(&quot;Draw pixel at: (\(x),\(y))&quot;)

        x += 1

        if d &lt; 0 {
            d += 2 * dy
        } else {
            d += 2 * (dy - dx)
            y += 1
        }
    }
}

// Input
drawLine(x1: 1, y1: 2, x2: 4, y2: 4)

// Output
Draw pixel at: (1,2)
Draw pixel at: (2,3)
Draw pixel at: (3,3)
Draw pixel at: (4,4)</code></pre><h3 id="edge-cases">Edge Cases</h3><p>So far, we&apos;ve only talked about situations where X1 (starting point) &lt; X2 (ending point) and where the slope is between 0 and 1. </p><p>There are several cases we haven&apos;t handled: </p><ul><li>X1 &gt; X2</li><li>Slope &lt; 0</li><li>Slope &gt; 1</li></ul><p><a href="https://medium.com/geekculture/bresenhams-line-drawing-algorithm-2e0e953901b3">Anusha&apos;s post</a> provides a thorough explanation of these edge cases:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://medium.com/geekculture/bresenhams-line-drawing-algorithm-2e0e953901b3"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Bresenham&#x2019;s Line Drawing Algorithm</div><div class="kg-bookmark-description">Explanation of Bresenham&#x2019;s Line Drawing Algorithm with examples</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://cdn-static-1.medium.com/_/fp/icons/Medium-Avatar-500x500.svg" alt="Bresenham&apos;s Line Algorithm"><span class="kg-bookmark-author">Geek Culture</span><span class="kg-bookmark-publisher">Anusha Ihalapathirana</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://miro.medium.com/v2/resize:fit:428/1*cP9OaF3n4mmbAhtguD52rg.png" alt="Bresenham&apos;s Line Algorithm"></div></a></figure><h3 id="conclusion">Conclusion</h3><p>Bresenham&apos;s algorithm excels in both speed and ease of implementation. However, it doesn&apos;t address <a href="https://en.wikipedia.org/wiki/Aliasing">aliasing</a>. </p><p>As a result, many implementations opt for <a href="https://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm">Xiaolin Wu&apos;s algorithm</a>, which offers support for aliasing operations.</p><p>If you&apos;re interested in a deep dive into that, let me know!</p><hr><p>If you&apos;re interested in more articles about iOS Development &amp; Swift, check out my <a href="https://www.youtube.com/c/AryamanSharda">YouTube channel</a> or <a href="https://twitter.com/aryamansharda">follow me on Twitter</a>.</p><p>And, if you&apos;re an indie iOS developer, make sure to <a href="https://indie.watch/">check out my newsletter</a>! Each issue features a new indie developer, so feel free to <a href="https://indie.watch/submit">submit</a> your iOS apps.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://aryamansharda.gumroad.com/l/tcvck"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Ace The iOS Interview</div><div class="kg-bookmark-description">The best investment for landing your dream iOS jobHey there! My name is Aryaman Sharda and I started making iOS apps way back in 2015. Since then, I&#x2019;ve worked for a variety of companies like Porsche, Turo, and Scoop Technologies just to name a few. Over the years, I&#x2019;ve mentored junior engineers, bui&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://public-files.gumroad.com/g9djd65cu1rd3pssapogotmjzlb0" alt="Bresenham&apos;s Line Algorithm"><span class="kg-bookmark-author">Gumroad</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://public-files.gumroad.com/45nojtirapxmcuffy08475597w9u" alt="Bresenham&apos;s Line Algorithm"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://aryamansharda.gumroad.com/l/build-switcher"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Build Switcher: Local Build Caching for Xcode</div><div class="kg-bookmark-description">Introducing BuildSwitcher &#x1F680;&#x26A1; BuildSwitcher intelligently caches the latest builds across your most frequented branches. Now, you can switch between these builds instantly in the Simulator without having to wait for compilation or stashing your working changes when you change branches. Say goodbye t&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://public-files.gumroad.com/g9djd65cu1rd3pssapogotmjzlb0" alt="Bresenham&apos;s Line Algorithm"><span class="kg-bookmark-author">Gumroad</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://public-files.gumroad.com/p5uuxuizp0o8rcwyz2qjpjz5hcwo" alt="Bresenham&apos;s Line Algorithm"></div></a></figure><hr><h3 id="sources">Sources</h3><ul><li><a href="https://en.wikipedia.org/wiki/Bresenham&apos;s_line_algorithm">https://en.wikipedia.org/wiki/Bresenham&apos;s_line_algorithm</a></li><li><a href="https://www.cs.drexel.edu/~popyack/Courses/CSP/Fa18/notes/08.3_MoreGraphics/Bresenham.html?CurrentSlide=2">https://www.cs.drexel.edu/~popyack/Courses/CSP/Fa18/notes/08.3_MoreGraphics/Bresenham.html?CurrentSlide=2</a></li></ul><h3 id="videos">Videos</h3><ul><li><a href="https://www.youtube.com/watch?v=RGB-wlatStc&amp;t=1020s&amp;pp=ygUTYnJlc2VuaGFtIGxpbmUgYWxnbw%3D%3D">https://www.youtube.com/watch?v=RGB-wlatStc</a></li><li><a href="https://youtu.be/h3gDB89h0os?si=W0lPlftL7O-vw0tf">https://youtu.be/h3gDB89h0os?si=W0lPlftL7O-vw0tf</a></li><li><a href="https://www.youtube.com/watch?v=TRbwu17oAYY&amp;t=1s&amp;ab_channel=TheBootStrappers">https://www.youtube.com/watch?v=TRbwu17oAYY&amp;t=1s&amp;ab_channel=TheBootStrappers</a></li></ul>]]></content:encoded></item><item><title><![CDATA[Exploring Animation Curves]]></title><description><![CDATA[<p>Between my <a href="https://apps.apple.com/gb/developer/aryaman-sharda/id651900313">recent SwiftUI projects</a> and <a href="https://www.youtube.com/@AryamanSharda">YouTube content</a>, animations have become a crucial part of my work. So, I&apos;ve been down a bit of an animation rabbit hole lately...</p><p>We&apos;ll start with the basics - from linear animations to easing curves - and then we&apos;</p>]]></description><link>https://digitalbunker.dev/exploring-animation-curves/</link><guid isPermaLink="false">64fba2929934237c2b9f270f</guid><category><![CDATA[algorithms]]></category><category><![CDATA[articles]]></category><category><![CDATA[basics]]></category><category><![CDATA[computer science]]></category><category><![CDATA[computer graphics]]></category><category><![CDATA[grayscale]]></category><category><![CDATA[implementation]]></category><category><![CDATA[ios]]></category><category><![CDATA[math]]></category><category><![CDATA[programming]]></category><category><![CDATA[software engineering]]></category><category><![CDATA[swift]]></category><category><![CDATA[things to know]]></category><dc:creator><![CDATA[Aryaman Sharda]]></dc:creator><pubDate>Wed, 13 Sep 2023 13:30:50 GMT</pubDate><media:content url="https://digitalbunker.dev/content/images/2023/09/Group-7--1-.png" medium="image"/><content:encoded><![CDATA[<img src="https://digitalbunker.dev/content/images/2023/09/Group-7--1-.png" alt="Exploring Animation Curves"><p>Between my <a href="https://apps.apple.com/gb/developer/aryaman-sharda/id651900313">recent SwiftUI projects</a> and <a href="https://www.youtube.com/@AryamanSharda">YouTube content</a>, animations have become a crucial part of my work. So, I&apos;ve been down a bit of an animation rabbit hole lately...</p><p>We&apos;ll start with the basics - from linear animations to easing curves - and then we&apos;ll dive into creating custom animations from scratch. Whether you&apos;re a designer aiming for delightful user experiences or a developer focused on effective animation, this post has you covered.</p><h2 id="linear-animation-linear-interpolation">Linear Animation &amp; Linear Interpolation</h2><p>At its core, linear interpolation (a.k.a lerp) smoothly transitions between two values over a fixed time, creating a consistent, constant-speed motion. This forms the basis of linear animation where objects move uniformly from A to B.</p><p>Let&apos;s see what this looks like in code:</p><ul><li><code>startValue</code> is the starting value of the animation (i.e. <code>alpha</code>, <code>x,</code> <code>width</code>, etc.).</li><li><code>endValue</code> is the ending value of the animation.</li><li><code>progress</code> is a parameter that ranges from [0,1] and represents how much of the animation&apos;s duration has elapsed.</li></ul><pre><code class="language-swift">func lerp(startValue: CGFloat, endValue: CGFloat, progress: CGFloat) -&gt; CGFloat {
    return startValue + (endValue - startValue) * progress
}
</code></pre><ul><li>When <code>progress = 0</code>, <code>lerp</code> returns <code>startValue</code>.</li><li>When <code>progress <code>= 1</code></code>, <code>lerp</code> returns <code>endValue</code>.</li><li>When <code>0 &lt; progress &lt; 1</code>, <code>lerp</code> returns a value between <code>startValue</code> and <code>endValue</code> proportional to the elapsed animation duration.</li></ul><p>You can use this <code>lerp</code> function to interpolate between any numeric values in your Swift code; position, opacity, colors, etc.</p><p>Now, let&apos;s use our <code>lerp</code> function to animate a view linearly across the screen:</p><pre><code class="language-swift">class LinearAnimationViewController: UIViewController {
    lazy var boxView: UIView = {
        // Create a blue box
        let boxView = UIView(frame: CGRect(x: 0, y: 200, width: 100, height: 100))
        boxView.backgroundColor = UIColor.blue
        view.addSubview(boxView)
        return boxView
    }()

    let animationDuration: TimeInterval = 3.0
    let frameRate: TimeInterval = 1.0 / 60.0 // 60 FPS
    var currentTime: TimeInterval = 0

    var animationTimer: Timer?

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        // Start the animation timer
        animationTimer = Timer.scheduledTimer(timeInterval: frameRate, target: self, selector: #selector(animateBox), userInfo: nil, repeats: true)
    }

    @objc func animateBox() {
        let startValue: CGFloat = 0
        let endValue: CGFloat = self.view.frame.width - 200

        // Calculate interpolated position
        let progress = CGFloat(currentTime / animationDuration)
        let newX = lerp(startValue: startValue, endValue: endValue, progress: progress)
        boxView.frame.origin.x = newX

        // Moves to the next frame
        currentTime += frameRate
        if currentTime &gt; animationDuration {
            animationTimer?.invalidate()
        }
    }

    func lerp(startValue: CGFloat, endValue: CGFloat, progress: CGFloat) -&gt; CGFloat {
        return startValue + (endValue - startValue) * progress
    }
}
</code></pre><blockquote>I&apos;ll omit the setup code in future examples as it remains constant.</blockquote><figure class="kg-card kg-video-card"><div class="kg-video-container"><video src="https://digitalbunker.dev/content/media/2023/09/Simulator-Screen-Recording---iPhone-14-Plus---2023-09-08-at-17.33.49.mp4" poster="https://img.spacergif.org/v1/2778x1284/0a/spacer.png" width="2778" height="1284" loop autoplay muted playsinline preload="metadata" style="background: transparent url(&apos;https://digitalbunker.dev/content/images/2023/09/media-thumbnail-ember6434.jpg&apos;) 50% 50% / cover no-repeat;"></video><div class="kg-video-overlay"><button class="kg-video-large-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button></div><div class="kg-video-player-container kg-video-hide"><div class="kg-video-player"><button class="kg-video-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button><button class="kg-video-pause-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg></button><span class="kg-video-current-time">0:00</span><div class="kg-video-time">/<span class="kg-video-duration"></span></div><input type="range" class="kg-video-seek-slider" max="100" value="0"><button class="kg-video-playback-rate">1&#xD7;</button><button class="kg-video-unmute-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg></button><button class="kg-video-mute-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg></button><input type="range" class="kg-video-volume-slider" max="100" value="100"></div></div></div></figure><p>While this linear animation works, it&apos;s missing something. Not only is it a little boring, but very few things in the real world are linear in nature - usually, the rate of change of most things tends to be non-linear. For example, when you floor the accelerator in your car, in the first moment nothing happens and then the movement happens all at once.</p><p>So, in order to make our animations look more natural, we&apos;ll need to rely on non-linear animation curves.</p><h2 id="easing-functions">Easing Functions</h2><p>Animation timing functions, often simply called &quot;timing functions&quot; or &quot;easing functions,&quot; define <strong>how the progress of an animation changes over time. </strong></p><p>Timing functions are often depicted as graphs, with time on the horizontal axis and progress or value changes on the vertical axis. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://digitalbunker.dev/content/images/2023/09/image.png" class="kg-image" alt="Exploring Animation Curves" loading="lazy" width="865" height="625" srcset="https://digitalbunker.dev/content/images/size/w600/2023/09/image.png 600w, https://digitalbunker.dev/content/images/2023/09/image.png 865w" sizes="(min-width: 720px) 720px"><figcaption>Easing functions shape how an animation begins, progresses, and ends. (Source: <a href="http://vitiy.info/easing-functions-for-your-animations/">Victor Laskin&apos;s Blog</a>)</figcaption></figure><p>For a more tangible example, consider a car&apos;s acceleration. Gentle gas pedal pressure leads to a smooth start, much like an ease-in animation. When you let off the gas before stopping, the gradual slowdown is akin to an ease-out animation. </p><h3 id="ease-in">Ease In</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-09-at-9.23.21-AM.png" class="kg-image" alt="Exploring Animation Curves" loading="lazy" width="917" height="724" srcset="https://digitalbunker.dev/content/images/size/w600/2023/09/Screenshot-2023-09-09-at-9.23.21-AM.png 600w, https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-09-at-9.23.21-AM.png 917w" sizes="(min-width: 720px) 720px"><figcaption>Ease In Animation Curve</figcaption></figure><blockquote>Both <code>t</code> and <code>x</code> range from <code>[0, 1]</code>.</blockquote><p>Easing functions frequently use quadratic or cubic equations because the output grows quickly with minor increases in the input value. This lines up perfectly with our aim of modeling a car&apos;s smooth start and then rapid acceleration.</p><figure class="kg-card kg-code-card"><pre><code class="language-swift">// Calculate interpolated position
let progress = CGFloat(currentTime / animationDuration)
let newX = lerp(startValue: startValue, endValue: endValue, progress: easeIn(progress: progress))
boxView.frame.origin.x = newX

func easeIn(progress: CGFloat) -&gt; CGFloat {
    pow(progress, 2)
}</code></pre><figcaption><a href="https://easings.net/#easeInQuad">Easings.net&apos;s</a> recommended ease-in equation</figcaption></figure><p>When you use an easing function like <code>easeIn</code>, it takes a single parameter, <code>progress</code>, which represents the fraction of the animation duration that has elapsed and ranges from 0 to 1. </p><p>This easing function then transforms this input into an &apos;eased value&apos; within the same 0 to 1 range. When we combine this &apos;eased value&apos; with <code>lerp</code>, we can introduce a non-linear element to our interpolation between the start and end values of our animation.</p><figure class="kg-card kg-video-card kg-card-hascaption"><div class="kg-video-container"><video src="https://digitalbunker.dev/content/media/2023/09/Ease-In-Quad.mp4" poster="https://img.spacergif.org/v1/2778x1284/0a/spacer.png" width="2778" height="1284" loop autoplay muted playsinline preload="metadata" style="background: transparent url(&apos;https://digitalbunker.dev/content/images/2023/09/media-thumbnail-ember8147.jpg&apos;) 50% 50% / cover no-repeat;"></video><div class="kg-video-overlay"><button class="kg-video-large-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button></div><div class="kg-video-player-container kg-video-hide"><div class="kg-video-player"><button class="kg-video-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button><button class="kg-video-pause-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg></button><span class="kg-video-current-time">0:00</span><div class="kg-video-time">/<span class="kg-video-duration"></span></div><input type="range" class="kg-video-seek-slider" max="100" value="0"><button class="kg-video-playback-rate">1&#xD7;</button><button class="kg-video-unmute-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg></button><button class="kg-video-mute-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg></button><input type="range" class="kg-video-volume-slider" max="100" value="100"></div></div></div><figcaption>Ease In Demo</figcaption></figure><blockquote>Returning to our car analogy, you can see that gentle start and increased acceleration behavior in this animation.</blockquote><h3 id="ease-out">Ease Out</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-09-at-9.24.31-AM.png" class="kg-image" alt="Exploring Animation Curves" loading="lazy" width="964" height="717" srcset="https://digitalbunker.dev/content/images/size/w600/2023/09/Screenshot-2023-09-09-at-9.24.31-AM.png 600w, https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-09-at-9.24.31-AM.png 964w" sizes="(min-width: 720px) 720px"><figcaption>Ease Out Animation Curve</figcaption></figure><pre><code class="language-swift">// Calculate interpolated position
let progress = CGFloat(currentTime / animationDuration)
let newX = lerp(startValue: startValue, endValue: endValue, progress: easeOutQuad(progress: progress))
boxView.frame.origin.x = newX

func easeOutQuad(progress: CGFloat) -&gt; CGFloat {
    1 - (1 - progress) * (1 - progress)
}</code></pre><figure class="kg-card kg-video-card kg-card-hascaption"><div class="kg-video-container"><video src="https://digitalbunker.dev/content/media/2023/09/Ease-Out-Quad.mp4" poster="https://img.spacergif.org/v1/2778x1284/0a/spacer.png" width="2778" height="1284" loop autoplay muted playsinline preload="metadata" style="background: transparent url(&apos;https://digitalbunker.dev/content/images/2023/09/media-thumbnail-ember8329.jpg&apos;) 50% 50% / cover no-repeat;"></video><div class="kg-video-overlay"><button class="kg-video-large-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button></div><div class="kg-video-player-container kg-video-hide"><div class="kg-video-player"><button class="kg-video-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button><button class="kg-video-pause-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg></button><span class="kg-video-current-time">0:00</span><div class="kg-video-time">/<span class="kg-video-duration"></span></div><input type="range" class="kg-video-seek-slider" max="100" value="0"><button class="kg-video-playback-rate">1&#xD7;</button><button class="kg-video-unmute-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg></button><button class="kg-video-mute-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg></button><input type="range" class="kg-video-volume-slider" max="100" value="100"></div></div></div><figcaption>Ease Out Demo</figcaption></figure><h3 id="ease-in-elastic">Ease In Elastic</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-08-at-8.34.42-PM.png" class="kg-image" alt="Exploring Animation Curves" loading="lazy" width="960" height="801" srcset="https://digitalbunker.dev/content/images/size/w600/2023/09/Screenshot-2023-09-08-at-8.34.42-PM.png 600w, https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-08-at-8.34.42-PM.png 960w" sizes="(min-width: 720px) 720px"><figcaption>Ease In Elastic Animation Curve</figcaption></figure><pre><code class="language-swift">// Calculate interpolated position
let progress = CGFloat(currentTime / animationDuration)
let newX = lerp(startValue: startValue, endValue: endValue, progress: easeInElastic(progress: progress))
boxView.frame.origin.x = newX

func easeInElastic(progress: CGFloat) -&gt; CGFloat {
    guard progress != 0 || progress != 1 else {
        return progress
    }

    return -pow(2, 10 * progress - 10) * sin((progress * 10 - 10.75) * ((2 * CGFloat.pi) / 3))
}

</code></pre><figure class="kg-card kg-video-card kg-card-hascaption"><div class="kg-video-container"><video src="https://digitalbunker.dev/content/media/2023/09/Ease-In-Elastic.mp4" poster="https://img.spacergif.org/v1/2778x1284/0a/spacer.png" width="2778" height="1284" loop autoplay muted playsinline preload="metadata" style="background: transparent url(&apos;https://digitalbunker.dev/content/images/2023/09/media-thumbnail-ember6906.jpg&apos;) 50% 50% / cover no-repeat;"></video><div class="kg-video-overlay"><button class="kg-video-large-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button></div><div class="kg-video-player-container kg-video-hide"><div class="kg-video-player"><button class="kg-video-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button><button class="kg-video-pause-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg></button><span class="kg-video-current-time">0:00</span><div class="kg-video-time">/<span class="kg-video-duration"></span></div><input type="range" class="kg-video-seek-slider" max="100" value="0"><button class="kg-video-playback-rate">1&#xD7;</button><button class="kg-video-unmute-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg></button><button class="kg-video-mute-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg></button><input type="range" class="kg-video-volume-slider" max="100" value="100"></div></div></div><figcaption>Ease In Elastic Demo</figcaption></figure><h3 id="ease-in-out-quint">Ease In Out Quint</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-08-at-8.33.33-PM.png" class="kg-image" alt="Exploring Animation Curves" loading="lazy" width="982" height="695" srcset="https://digitalbunker.dev/content/images/size/w600/2023/09/Screenshot-2023-09-08-at-8.33.33-PM.png 600w, https://digitalbunker.dev/content/images/2023/09/Screenshot-2023-09-08-at-8.33.33-PM.png 982w" sizes="(min-width: 720px) 720px"><figcaption>Ease In Out Quint Animation Curve</figcaption></figure><p>Check out the <a href="https://www.mathsisfun.com/sets/functions-piecewise.html">piecewise approach</a> in the implementation below. </p><p>You&apos;re not confined to using a single expression for the entire animation. Instead, you have the freedom to use different formulas for specific time segments, giving you creative control over each part of the animation.</p><pre><code class="language-swift">// Calculate interpolated position
let progress = CGFloat(currentTime / animationDuration)
let newX = lerp(startValue: startValue, endValue: endValue, progress: easeInOutQuint(progress: progress))
boxView.frame.origin.x = newX

func easeInOutQuint(progress: CGFloat) -&gt; CGFloat {
    if progress &lt; 0.5 {
        return 16 * pow(progress, 5)
    } else {
        return 1 - pow(-2 * progress + 2, 5) / 2
    }
}
</code></pre><figure class="kg-card kg-video-card kg-card-hascaption"><div class="kg-video-container"><video src="https://digitalbunker.dev/content/media/2023/09/Ease-In-Out-Quint.mp4" poster="https://img.spacergif.org/v1/2778x1284/0a/spacer.png" width="2778" height="1284" loop autoplay muted playsinline preload="metadata" style="background: transparent url(&apos;https://digitalbunker.dev/content/images/2023/09/media-thumbnail-ember6957.jpg&apos;) 50% 50% / cover no-repeat;"></video><div class="kg-video-overlay"><button class="kg-video-large-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button></div><div class="kg-video-player-container kg-video-hide"><div class="kg-video-player"><button class="kg-video-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button><button class="kg-video-pause-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg></button><span class="kg-video-current-time">0:00</span><div class="kg-video-time">/<span class="kg-video-duration"></span></div><input type="range" class="kg-video-seek-slider" max="100" value="0"><button class="kg-video-playback-rate">1&#xD7;</button><button class="kg-video-unmute-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg></button><button class="kg-video-mute-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg></button><input type="range" class="kg-video-volume-slider" max="100" value="100"></div></div></div><figcaption>Ease In-Out Quint Position Demo</figcaption></figure><p>These easing functions aren&apos;t limited to just animating positions. We could apply the same approach for scaling, rotation, or opacity animations. </p><p>Here&apos;s the implementation for an opacity animation using the same easing function:</p><pre><code class="language-swift">let startValue: CGFloat = 0
let endValue: CGFloat = 1

// Calculate interpolated position
let progress = CGFloat(currentTime / animationDuration)
let newAlpha = lerp(startValue: startValue, endValue: endValue, progress: easeInOutQuint(progress: progress))
boxView.alpha = newAlpha</code></pre><figure class="kg-card kg-video-card kg-card-hascaption"><div class="kg-video-container"><video src="https://digitalbunker.dev/content/media/2023/09/Ease-in-Out-Quint-Opacity.mp4" poster="https://img.spacergif.org/v1/2778x1284/0a/spacer.png" width="2778" height="1284" loop autoplay muted playsinline preload="metadata" style="background: transparent url(&apos;https://digitalbunker.dev/content/images/2023/09/media-thumbnail-ember7007.jpg&apos;) 50% 50% / cover no-repeat;"></video><div class="kg-video-overlay"><button class="kg-video-large-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button></div><div class="kg-video-player-container kg-video-hide"><div class="kg-video-player"><button class="kg-video-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button><button class="kg-video-pause-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg></button><span class="kg-video-current-time">0:00</span><div class="kg-video-time">/<span class="kg-video-duration"></span></div><input type="range" class="kg-video-seek-slider" max="100" value="0"><button class="kg-video-playback-rate">1&#xD7;</button><button class="kg-video-unmute-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg></button><button class="kg-video-mute-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg></button><input type="range" class="kg-video-volume-slider" max="100" value="100"></div></div></div><figcaption>Ease In Out Quint Opacity Demo</figcaption></figure><h2 id="no-right-answer">No Right Answer</h2><p>Finding the exact formula for specific easing curves can be challenging since there&apos;s no universal equation or standard for all easing functions. Easing functions are often created by designers and developers to achieve specific visual effects or animations, and the exact formula may vary depending on the software or library used. </p><p>Animations are a mix of math and artistry. For example, take a look at Lucas Garron&apos;s video below, where he builds his own &quot;perfect&quot; easing function from first principles.</p><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/ely4VswZJOA?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="Lucas Garron - The Perfect Easing Function - G4G13 Apr 2018"></iframe></figure><p>To discover the exact formula for a particular easing curve, search for documentation related to the animation library or framework you&apos;re using. Some libraries provide mathematical equations for their built-in easing functions.</p><p>Additionally, some tools and websites allow you to visualize and <a href="https://tools.webdevpuneet.com/css-easing-generator/">generate custom easing curves graphically</a>. While they may not provide the exact formula, they allow you to experiment with different easing curves and export them for use in your animations.</p><p>While understanding the math behind easing functions can be valuable for custom animations, predefined easing functions from libraries are often sufficient for achieving the desired visual effects in most projects. This deep dive was driven purely by curiosity.</p><p>The implementations above drew significant inspiration from <a href="https://easings.net/">Easings.net</a>. If you&apos;re interested in learning more about easing functions, it&apos;s a fantastic resource to check out.</p><h2 id="conclusion">Conclusion</h2><p>From linear ease-ins to quirky custom curves, animations add that extra flair to your user experience. So, the next time you&apos;re designing an app or website, don&apos;t forget to sprinkle in some animations. With the right timing, creativity, and a dash of experimentation, you&apos;ll transform your static screens into an immersive experience.</p><hr><p>If you&apos;re interested in more articles about iOS Development &amp; Swift, check out my <a href="https://www.youtube.com/c/AryamanSharda">YouTube channel</a> or <a href="https://twitter.com/aryamansharda">follow me on Twitter</a>.</p><p>And, if you&apos;re an indie iOS developer, make sure to <a href="https://indie.watch/">check out my newsletter</a>! Each issue features a new indie developer, so feel free to <a href="https://indie.watch/submit">submit</a> your iOS apps.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://aryamansharda.gumroad.com/l/tcvck"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Ace The iOS Interview</div><div class="kg-bookmark-description">The best investment for landing your dream iOS jobHey there! My name is Aryaman Sharda and I started making iOS apps way back in 2015. Since then, I&#x2019;ve worked for a variety of companies like Porsche, Turo, and Scoop Technologies just to name a few. Over the years, I&#x2019;ve mentored junior engineers, bui&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://public-files.gumroad.com/g9djd65cu1rd3pssapogotmjzlb0" alt="Exploring Animation Curves"><span class="kg-bookmark-author">Gumroad</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://public-files.gumroad.com/45nojtirapxmcuffy08475597w9u" alt="Exploring Animation Curves"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://aryamansharda.gumroad.com/l/build-switcher"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Build Switcher: Local Build Caching for Xcode</div><div class="kg-bookmark-description">Introducing BuildSwitcher &#x1F680;&#x26A1; BuildSwitcher intelligently caches the latest builds across your most frequented branches. Now, you can switch between these builds instantly in the Simulator without having to wait for compilation or stashing your working changes when you change branches. Say goodbye t&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://public-files.gumroad.com/g9djd65cu1rd3pssapogotmjzlb0" alt="Exploring Animation Curves"><span class="kg-bookmark-author">Gumroad</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://public-files.gumroad.com/p5uuxuizp0o8rcwyz2qjpjz5hcwo" alt="Exploring Animation Curves"></div></a></figure><h3 id="sources-additional-reading">Sources &amp; Additional Reading</h3><ul><li><a href="https://easings.net/">https://easings.net/</a></li><li><a href="https://www.youtube.com/watch?v=CDDfXOeEJl0&amp;t=3s">https://www.youtube.com/watch?v=CDDfXOeEJl0</a></li><li><a href="https://www.youtube.com/watch?v=YJB1QnEmlTs&amp;list=WL&amp;index=2&amp;ab_channel=SimonDev">https://www.youtube.com/watch?v=YJB1QnEmlTs</a></li></ul>]]></content:encoded></item><item><title><![CDATA[Introducing BuildSwitcher: Local Build Caching in Xcode 🚀]]></title><description><![CDATA[<p>&#x26A1; <a href="https://aryamansharda.gumroad.com/l/build-switcher" rel="noopener ugc nofollow">Build Switcher</a> intelligently caches the latest builds across your most frequented branches. Now, you can <strong><strong>switch between these builds instantly in the Simulator without having to wait for compilation</strong></strong> or stashing your working changes when you change branches.</p><p>Say goodbye to wasted time on resolving dependency versions, battling caching</p>]]></description><link>https://digitalbunker.dev/buildswitcher-local-build-caching-in-xcode/</link><guid isPermaLink="false">64f247aeeb0d020c18913e88</guid><category><![CDATA[ios]]></category><category><![CDATA[project]]></category><category><![CDATA[programming]]></category><category><![CDATA[software engineering]]></category><category><![CDATA[swift]]></category><category><![CDATA[xcode]]></category><dc:creator><![CDATA[Aryaman Sharda]]></dc:creator><pubDate>Fri, 01 Sep 2023 20:24:15 GMT</pubDate><media:content url="https://digitalbunker.dev/content/images/2023/09/Group-13--4--2.png" medium="image"/><content:encoded><![CDATA[<img src="https://digitalbunker.dev/content/images/2023/09/Group-13--4--2.png" alt="Introducing BuildSwitcher: Local Build Caching in Xcode &#x1F680;"><p>&#x26A1; <a href="https://aryamansharda.gumroad.com/l/build-switcher" rel="noopener ugc nofollow">Build Switcher</a> intelligently caches the latest builds across your most frequented branches. Now, you can <strong><strong>switch between these builds instantly in the Simulator without having to wait for compilation</strong></strong> or stashing your working changes when you change branches.</p><p>Say goodbye to wasted time on resolving dependency versions, battling caching / DerivedData issues, and waiting for compilation during branch changes and demos.</p><figure class="kg-card kg-embed-card kg-card-hascaption"><iframe width="200" height="113" src="https://www.youtube.com/embed/7ty6eUM-3uM?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="Build Switcher: Local Build Caching for Xcode"></iframe><figcaption>Check out Build Switcher in action!</figcaption></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://aryamansharda.gumroad.com/l/build-switcher?source=post_page-----c7a43f8612b4--------------------------------"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Build Switcher: Local Build Caching for Xcode</div><div class="kg-bookmark-description">Introducing BuildSwitcher &#x1F680;&#x26A1; BuildSwitcher intelligently caches the latest builds across your most frequented branches. Now, you can switch between these builds instantly in the Simulator without having to wait for compilation or stashing your working changes when you change branches. Say goodbye t&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://public-files.gumroad.com/g9djd65cu1rd3pssapogotmjzlb0" alt="Introducing BuildSwitcher: Local Build Caching in Xcode &#x1F680;"><span class="kg-bookmark-author">Gumroad</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://public-files.gumroad.com/p5uuxuizp0o8rcwyz2qjpjz5hcwo" alt="Introducing BuildSwitcher: Local Build Caching in Xcode &#x1F680;"></div></a></figure><p>&#x1F50D; <strong><strong>Swift, Smooth Comparison:</strong></strong> With <a href="https://aryamansharda.gumroad.com/l/build-switcher" rel="noopener ugc nofollow">Build Switcher</a>, comparing your feature or bug-fix branches with <code>main</code> is a breeze. Safeguard against regressions and ensure that pesky bugs are truly squashed, all without the hassle of switching branches or recompiling.</p><p>&#x1F504; <strong><strong>Simpler Refactoring:</strong></strong> Transitioning from Swift/ObjC to SwiftUI? Or, in the middle of a big refactor? <a href="https://aryamansharda.gumroad.com/l/build-switcher" rel="noopener ugc nofollow">Build Switcher&#x2019;s</a> got your back! Easily compare your current implementation against the original allowing you to ensure the expected behavior remains intact.</p><p><strong><strong>&#x1F4CA; Dazzling Demos, Zero Delay:</strong></strong> Picture this &#x2014; it&#x2019;s the end of the week and you&#x2019;re getting ready to demo your bug fixes and new features to your team. As you move between branches and wait for your project to recompile, your high-energy demo quickly becomes a time-sucking nightmare.</p><p><strong><strong>With </strong></strong><a href="https://aryamansharda.gumroad.com/l/build-switcher" rel="noopener ugc nofollow"><strong><strong>Build Switcher</strong></strong></a><strong><strong> automatically caching your builds across your recent branches, you can now easily restore builds in the Simulator without compiling, changing branches, or stashing any changes.</strong></strong> Your demos just went from clunky to seamless. &#x1F389;</p><p>&#x1F6E0;&#xFE0F; <strong><strong>Easy Setup:</strong></strong> Seamless integration into your workflow. A few taps, and you&#x2019;re good to go! No rocket science, I promise. Plus, it&#x2019;s compatible with all iOS and iPadOS projects. &#x1F680;</p><p>&#x1F468;&#x200D;&#x1F4BB; <strong><strong>Developer-Tested, Developer-Friendly:</strong></strong> I&#x2019;ve put <a href="https://aryamansharda.gumroad.com/l/build-switcher" rel="noopener ugc nofollow">Build Switcher</a> through its paces over a few on-call shifts and I&#x2019;ve found it indispensable in my development workflow.</p><!--kg-card-begin: html--><script src="https://gumroad.com/js/gumroad-embed.js"></script>
<div class="gumroad-product-embed"><a href="https://aryamansharda.gumroad.com/l/build-switcher">Loading...</a></div><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Introducing Pickleball Score Tracker]]></title><description><![CDATA[Serve. Score. Share. Elevate Your Play.]]></description><link>https://digitalbunker.dev/pickleball-score-tracker/</link><guid isPermaLink="false">64e962352e8ca6104f4e68f0</guid><category><![CDATA[app store]]></category><category><![CDATA[ios]]></category><category><![CDATA[project]]></category><category><![CDATA[random]]></category><dc:creator><![CDATA[Aryaman Sharda]]></dc:creator><pubDate>Sat, 26 Aug 2023 02:42:38 GMT</pubDate><media:content url="https://digitalbunker.dev/content/images/2023/08/Slice-1--3--3.png" medium="image"/><content:encoded><![CDATA[<img src="https://digitalbunker.dev/content/images/2023/08/Slice-1--3--3.png" alt="Introducing Pickleball Score Tracker"><p>Calling all pickleball enthusiasts! It&apos;s time to celebrate because the Pickleball Score Tracker app has arrived. Crafted by a fellow pickleball aficionado, this game-changing app syncs seamlessly with your Apple Watch, transforming how you keep score and experience the game. Say goodbye to scorekeeping headaches and hello to a whole new level of gameplay.</p><h2 id="product-features-highlights">Product Features &amp; Highlights</h2><p>Hey there, fellow pickleball enthusiast! Ever had those moments on the court when the score gets all tangled up? I&apos;ve been there too, and that&apos;s why I created the Pickleball Score Tracker watchOS app.</p><h3 id="%F0%9F%93%B1effortless-scoring">&#x1F4F1;Effortless Scoring</h3><p>With the app&apos;s integration with your Apple Watch, tracking scores becomes as intuitive as your best shots. A quick twist of the crown, and you&apos;re all set. Your focus stays on the game, where it should be.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://digitalbunker.dev/content/images/2023/08/Slice-2.png" class="kg-image" alt="Introducing Pickleball Score Tracker" loading="lazy" width="1270" height="760" srcset="https://digitalbunker.dev/content/images/size/w600/2023/08/Slice-2.png 600w, https://digitalbunker.dev/content/images/size/w1000/2023/08/Slice-2.png 1000w, https://digitalbunker.dev/content/images/2023/08/Slice-2.png 1270w" sizes="(min-width: 720px) 720px"><figcaption>When your friends join your game, they&apos;ll see the score update on their watch in realtime.</figcaption></figure><h3 id="%F0%9F%8C%9Flive-score-streaming">&#x1F31F;Live Score Streaming</h3><p>But the app doesn&apos;t stop there. I know the camaraderie pickleball brings. That&apos;s why I&apos;ve added live score streaming. Now, you can share the excitement with your fellow players in real-time, enhancing the experience for everyone involved. </p><h3 id="%F0%9F%8F%8B%EF%B8%8F%E2%80%8D%E2%99%82%EF%B8%8F-workout-tracking">&#x1F3CB;&#xFE0F;&#x200D;&#x2642;&#xFE0F; Workout Tracking</h3><p> And for those fitness goals? Consider them covered. The app tracks your workout while you focus on your game. It&apos;s a win-win for your health and your pickleball skills. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://digitalbunker.dev/content/images/2023/08/Slice-1--4-.png" class="kg-image" alt="Introducing Pickleball Score Tracker" loading="lazy" width="1270" height="760" srcset="https://digitalbunker.dev/content/images/size/w600/2023/08/Slice-1--4-.png 600w, https://digitalbunker.dev/content/images/size/w1000/2023/08/Slice-1--4-.png 1000w, https://digitalbunker.dev/content/images/2023/08/Slice-1--4-.png 1270w" sizes="(min-width: 720px) 720px"><figcaption>Pick your preferred match settings and game type.</figcaption></figure><h3 id="%F0%9F%9B%A0%EF%B8%8F-personalize-your-gameplay">&#x1F6E0;&#xFE0F; Personalize Your Gameplay</h3><p>The best part? All these features are yours, for free. As an avid player myself, I want everyone to have access to tools that enhance the game.</p><figure class="kg-card kg-image-card"><img src="https://api.producthunt.com/widgets/embed-image/v1/follow.svg?product_id=548646&amp;theme=light" class="kg-image" alt="Introducing Pickleball Score Tracker" loading="lazy"></figure><h2 id="media">Media</h2>
        <div class="kg-card kg-file-card ">
            <a class="kg-file-card-container" href="https://digitalbunker.dev/content/files/2023/08/Pickleball-Press-Kit.zip" title="Download" download>
                <div class="kg-file-card-contents">
                    <div class="kg-file-card-title">Pickleball Press Kit</div>
                    <div class="kg-file-card-caption">App Store screenshots, App Icon, and other marketing material.</div>
                    <div class="kg-file-card-metadata">
                        <div class="kg-file-card-filename">Pickleball Press Kit.zip</div>
                        <div class="kg-file-card-filesize">551 KB</div>
                    </div>
                </div>
                <div class="kg-file-card-icon">
                    <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><defs><style>.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}</style></defs><title>download-circle</title><polyline class="a" points="8.25 14.25 12 18 15.75 14.25"/><line class="a" x1="12" y1="6.75" x2="12" y2="18"/><circle class="a" cx="12" cy="12" r="11.25"/></svg>
                </div>
            </a>
        </div>
        <h2 id="download">Download</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://apps.apple.com/us/app/pickleball-score-tracker/id6449199491"><div class="kg-bookmark-content"><div class="kg-bookmark-title">&#x200E;Pickleball Score Tracker</div><div class="kg-bookmark-description">&#x200E;Introducing the Ultimate Pickleball Companion: Pickleball Score Tracker Never lose track of the score again! Elevate your pickleball experience with the Pickleball Score Tracker app. Designed with passion for players, by players, this app seamlessly integrates with your Apple Watch to enhance your&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://apps.apple.com/favicon.ico" alt="Introducing Pickleball Score Tracker"><span class="kg-bookmark-author">App Store</span><span class="kg-bookmark-publisher">Aryaman Sharda</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://is1-ssl.mzstatic.com/image/thumb/Purple116/v4/29/19/91/29199123-5f3f-f818-6967-bd49387fc8ad/AppIcon-1x_U007ewatch-85-220.png/1200x630wa.png" alt="Introducing Pickleball Score Tracker"></div></a></figure><h2 id="localization">Localization</h2><ul><li>English (Default)</li></ul><h2 id="contact-information">Contact Information</h2><p>For more info or media inquiries, please message me at <a href="mailto:aryaman@digitalbunker.dev">aryaman@digitalbunker.dev</a>.</p>]]></content:encoded></item><item><title><![CDATA[Resistor Color Band Calculator: Your Go-To App for Effortless Resistor Calculations]]></title><description><![CDATA[<p>Today, I&apos;m excited to announce the launch of my latest app, <a href="https://apps.apple.com/us/app/resistor-bands-calculator/id1658435931">Resistor Color Band Calculator</a>, now <a href="https://apps.apple.com/us/app/resistor-bands-calculator/id1658435931?platform=iphone">available on the App Store</a>. </p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/06/Screenshot-2023-06-06-at-7.35.25-PM.png" class="kg-image" alt loading="lazy" width="683" height="267" srcset="https://digitalbunker.dev/content/images/size/w600/2023/06/Screenshot-2023-06-06-at-7.35.25-PM.png 600w, https://digitalbunker.dev/content/images/2023/06/Screenshot-2023-06-06-at-7.35.25-PM.png 683w"></figure><p>Designed with a sleek and user-friendly interface - the <a href="https://apps.apple.com/us/app/resistor-bands-calculator/id1658435931?platform=iphone">Resistor Color Band Calculator</a> app - is a must-have for professionals, educators, and DIY enthusiasts who work with</p>]]></description><link>https://digitalbunker.dev/resistor-color-bands-calculator-app/</link><guid isPermaLink="false">647fead22e8ca6104f4e6862</guid><category><![CDATA[developer tool]]></category><category><![CDATA[project]]></category><dc:creator><![CDATA[Aryaman Sharda]]></dc:creator><pubDate>Wed, 07 Jun 2023 02:44:52 GMT</pubDate><content:encoded><![CDATA[<p>Today, I&apos;m excited to announce the launch of my latest app, <a href="https://apps.apple.com/us/app/resistor-bands-calculator/id1658435931">Resistor Color Band Calculator</a>, now <a href="https://apps.apple.com/us/app/resistor-bands-calculator/id1658435931?platform=iphone">available on the App Store</a>. </p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/06/Screenshot-2023-06-06-at-7.35.25-PM.png" class="kg-image" alt loading="lazy" width="683" height="267" srcset="https://digitalbunker.dev/content/images/size/w600/2023/06/Screenshot-2023-06-06-at-7.35.25-PM.png 600w, https://digitalbunker.dev/content/images/2023/06/Screenshot-2023-06-06-at-7.35.25-PM.png 683w"></figure><p>Designed with a sleek and user-friendly interface - the <a href="https://apps.apple.com/us/app/resistor-bands-calculator/id1658435931?platform=iphone">Resistor Color Band Calculator</a> app - is a must-have for professionals, educators, and DIY enthusiasts who work with resistors. </p><p>Whether you&apos;re designing a new circuit, repairing an old one, or just learning about electronics, this app will help you save time and avoid errors in resistance calculations.</p><figure class="kg-card kg-image-card kg-card-hascaption"><a href="https://apps.apple.com/us/app/resistor-bands-calculator/id1658435931?platform=iphone"><img src="https://digitalbunker.dev/content/images/2023/06/Screenshot-2023-06-06-at-7.36.41-PM.png" class="kg-image" alt loading="lazy" width="1481" height="758" srcset="https://digitalbunker.dev/content/images/size/w600/2023/06/Screenshot-2023-06-06-at-7.36.41-PM.png 600w, https://digitalbunker.dev/content/images/size/w1000/2023/06/Screenshot-2023-06-06-at-7.36.41-PM.png 1000w, https://digitalbunker.dev/content/images/2023/06/Screenshot-2023-06-06-at-7.36.41-PM.png 1481w" sizes="(min-width: 720px) 720px"></a><figcaption>Calculate resistance for 4,5, and 6-band resistors on iOS and iPadOS.</figcaption></figure><p>It offers comprehensive support for a wide range of resistors, including those with 4, 5, and 6 color bands. Simply enter the colors you see on the resistor and Resistor Color Band Calculator will take it from there!</p><p>Whether you&apos;re using an iPhone or an iPad, <a href="https://apps.apple.com/us/app/resistor-bands-calculator/id1658435931">Resistor Color Band Calculator</a> has you covered - it&apos;s available for iOS and iPadOS ensuring you can access it from any of your Apple devices with ease.</p><p><a href="https://apps.apple.com/us/app/resistor-bands-calculator/id1658435931">Resistor Color Band Calculator</a> is a practical tool for enthusiasts and professionals alike. With its friendly user interface, wide range of resistor support, accurate calculations, and cross-device compatibility, this app is a must-have for anyone working with resistors.</p><p>Head over to the App Store now and grab your copy of the <a href="https://apps.apple.com/us/app/resistor-bands-calculator/id1658435931">Resistor Color Band Calculator</a>. </p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://apps.apple.com/us/app/resistor-bands-calculator/id1658435931?platform=iphone"><div class="kg-bookmark-content"><div class="kg-bookmark-title">&#x200E;Resistor Color Band Calculator</div><div class="kg-bookmark-description">&#x200E;Introducing the Resistor Color Band Calculator - the ultimate tool for engineers, hobbyists, and students of electronics! With this app, you can quickly and easily determine the resistance of a resistor just by selecting its color bands using the intuitive interface. You can also calculate the to&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://apps.apple.com/favicon.ico" alt><span class="kg-bookmark-author">App Store</span><span class="kg-bookmark-publisher">Aryaman Sharda</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://is4-ssl.mzstatic.com/image/thumb/Purple126/v4/19/f5/32/19f532ab-c3a1-abc6-a97d-4497be26d6dc/AppIcon-1x_U007epad-85-220.png/1200x630wa.png" alt></div></a></figure><h2 id="supported-languages">Supported Languages</h2><ul><li>English</li><li>French</li><li>German</li><li>Japanese</li><li>Portuguese</li><li>Russian</li><li>Simplified Chinese</li><li>Spanish</li></ul>]]></content:encoded></item><item><title><![CDATA[Manage Simulator Screenshots with Screen Purge]]></title><description><![CDATA[<figure class="kg-card kg-image-card"><a href="https://aryamansharda.gumroad.com/l/ios-simulator-screenshots-screen-purge"><img src="https://digitalbunker.dev/content/images/2023/05/Group-8--4--2.png" class="kg-image" alt loading="lazy" width="1400" height="720" srcset="https://digitalbunker.dev/content/images/size/w600/2023/05/Group-8--4--2.png 600w, https://digitalbunker.dev/content/images/size/w1000/2023/05/Group-8--4--2.png 1000w, https://digitalbunker.dev/content/images/2023/05/Group-8--4--2.png 1400w" sizes="(min-width: 720px) 720px"></a></figure><p>Hey iOS Developers!</p><p>We all know the struggle of having a cluttered desktop filled with stale iOS Simulator screenshots. That&apos;s why I created <a href="https://aryamansharda.gumroad.com/l/ios-simulator-screenshots-screen-purge">Screen Purge</a> - an iOS Simulator Screenshot cleaner.</p><p><a href="https://aryamansharda.gumroad.com/l/ios-simulator-screenshots-screen-purge">Screen Purge</a> is specifically designed to simplify the process of managing and deleting iOS Simulator screenshots and</p>]]></description><link>https://digitalbunker.dev/managing-simulator-screenshots/</link><guid isPermaLink="false">647540722e8ca6104f4e6819</guid><category><![CDATA[software engineering]]></category><category><![CDATA[developer tool]]></category><dc:creator><![CDATA[Aryaman Sharda]]></dc:creator><pubDate>Tue, 30 May 2023 00:23:07 GMT</pubDate><content:encoded><![CDATA[<figure class="kg-card kg-image-card"><a href="https://aryamansharda.gumroad.com/l/ios-simulator-screenshots-screen-purge"><img src="https://digitalbunker.dev/content/images/2023/05/Group-8--4--2.png" class="kg-image" alt loading="lazy" width="1400" height="720" srcset="https://digitalbunker.dev/content/images/size/w600/2023/05/Group-8--4--2.png 600w, https://digitalbunker.dev/content/images/size/w1000/2023/05/Group-8--4--2.png 1000w, https://digitalbunker.dev/content/images/2023/05/Group-8--4--2.png 1400w" sizes="(min-width: 720px) 720px"></a></figure><p>Hey iOS Developers!</p><p>We all know the struggle of having a cluttered desktop filled with stale iOS Simulator screenshots. That&apos;s why I created <a href="https://aryamansharda.gumroad.com/l/ios-simulator-screenshots-screen-purge">Screen Purge</a> - an iOS Simulator Screenshot cleaner.</p><p><a href="https://aryamansharda.gumroad.com/l/ios-simulator-screenshots-screen-purge">Screen Purge</a> is specifically designed to simplify the process of managing and deleting iOS Simulator screenshots and recordings. With just a few clicks, you can say goodbye to unnecessary clutter and hello to a clean and focused desktop.</p><p><a href="https://aryamansharda.gumroad.com/l/ios-simulator-screenshots-screen-purge">Screen Purge</a> will automatically find all iOS, iPadOS, watchOS, etc. screenshots on your Desktop making it that much easier to get rid of unwanted screenshots and recordings. </p><figure class="kg-card kg-image-card kg-card-hascaption"><a href="https://aryamansharda.gumroad.com/l/ios-simulator-screenshots-screen-purge"><img src="https://digitalbunker.dev/content/images/2023/05/Screenshot-2023-05-29-at-5.09.21-PM.png" class="kg-image" alt loading="lazy" width="772" height="1906" srcset="https://digitalbunker.dev/content/images/size/w600/2023/05/Screenshot-2023-05-29-at-5.09.21-PM.png 600w, https://digitalbunker.dev/content/images/2023/05/Screenshot-2023-05-29-at-5.09.21-PM.png 772w" sizes="(min-width: 720px) 720px"></a><figcaption>Screen Purge in action</figcaption></figure><p>You can <a href="https://aryamansharda.gumroad.com/l/ios-simulator-screenshots-screen-purge">download it for free here</a>!</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://aryamansharda.gumroad.com/l/ios-simulator-screenshots-screen-purge"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Screen Purge: Manage Your iOS Simulator Screenshots</div><div class="kg-bookmark-description">Introducing Screen Purge!The ultimate tool for iOS developers to efficiently manage and organize their iOS Simulator Screenshots and recordings. Designed specifically for iOS engineers, this powerful utility simplifies the process of clearing away stale and cluttered screenshots, ensuring a streamli&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://public-files.gumroad.com/g9djd65cu1rd3pssapogotmjzlb0" alt><span class="kg-bookmark-author">Gumroad</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://public-files.gumroad.com/x9e1n3szfzcjpbx6crmqwsry2547" alt></div></a></figure>]]></content:encoded></item><item><title><![CDATA[StickerSpot Shutdown Announcement]]></title><description><![CDATA[<p>After careful consideration, we have come to the difficult decision of closing down StickerSpot. We understand that this news might come as a disappointment, and I want to take a moment to express our gratitude for your interest and creativity.</p><p>While running StickerSpot has been an enjoyable experience, but unfortunately,</p>]]></description><link>https://digitalbunker.dev/stickerspot-shutdown-announcement/</link><guid isPermaLink="false">646596582e8ca6104f4e67ea</guid><dc:creator><![CDATA[Aryaman Sharda]]></dc:creator><pubDate>Thu, 18 May 2023 03:11:46 GMT</pubDate><content:encoded><![CDATA[<p>After careful consideration, we have come to the difficult decision of closing down StickerSpot. We understand that this news might come as a disappointment, and I want to take a moment to express our gratitude for your interest and creativity.</p><p>While running StickerSpot has been an enjoyable experience, but unfortunately, it hasn&apos;t been financially sustainable. As much as I tried to find a way to make it work, the reality is that the costs of operating and maintaining the platform have outweighed the revenue generated.</p><p>All customers who have pending orders will be refunded.</p><p>If you have any questions or concerns about your order or refund, please reach out to me at <a>aryaman@digitalbunker.dev</a>, and I&apos;ll be happy to assist you.</p>]]></content:encoded></item><item><title><![CDATA[Recreating The iOS Timer App In SwiftUI]]></title><description><![CDATA[Today's tutorial will focus on using SwiftUI to recreate the UI and functionality of the iOS Timer app.]]></description><link>https://digitalbunker.dev/recreating-the-ios-timer-in-swiftui/</link><guid isPermaLink="false">63f3ca472e8ca6104f4e64ea</guid><category><![CDATA[articles]]></category><category><![CDATA[ios]]></category><category><![CDATA[programming]]></category><category><![CDATA[swift]]></category><dc:creator><![CDATA[Aryaman Sharda]]></dc:creator><pubDate>Tue, 21 Feb 2023 03:44:07 GMT</pubDate><content:encoded><![CDATA[<p>Today&apos;s tutorial will focus on using SwiftUI to recreate the UI and functionality of the iOS Timer. </p><p>We&apos;ll construct each of the UI components individually, build out the <code>ViewModel</code>, and then we&apos;ll bring everything together.</p><p>The source code for this project is available <a href="https://github.com/aryamansharda/SwiftUITimerApp">here</a>.</p><h3 id="breaking-down-the-ui">Breaking Down The UI</h3><p>When we look at the UI, we can break it down into 4 discrete sections:</p><ol><li><code>TimePickerView</code></li><li><code>StartButton</code> &#xA0;&amp; <code>StopButton</code> controls</li><li><code>CircularProgressView</code> (visible only when the timer is active)</li><li><code>PauseButton</code> control (visible only when the timer is active)</li></ol><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://digitalbunker.dev/content/images/2023/02/Group-6--1-.png" class="kg-image" alt loading="lazy" width="2000" height="1616" srcset="https://digitalbunker.dev/content/images/size/w600/2023/02/Group-6--1-.png 600w, https://digitalbunker.dev/content/images/size/w1000/2023/02/Group-6--1-.png 1000w, https://digitalbunker.dev/content/images/size/w1600/2023/02/Group-6--1-.png 1600w, https://digitalbunker.dev/content/images/size/w2400/2023/02/Group-6--1-.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>We&apos;ll ignore the TabBar and sound effect selection for now. We&apos;ll revisit these in future blog posts.</figcaption></figure><p>If we look at the screenshot above, we&apos;ll notice that the text in the <code>Picker</code> appears to be right-aligned while its accompanying label is left-aligned. Moreover, each of the <code>Pickers</code> for hours, minutes, and seconds occupies 1/3rd of the screen respectively, but the selected row effect is combined across all of them!</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">The key point to note here is that the spacing, sizing, and selection behavior are all fully custom and do not represent the default behavior of these components.</div></div><p>So, we&apos;ll need to create a custom multi-column <code>Picker</code> component.</p><h3 id="1-timepickerview">1. TimePickerView</h3><p>Since we know we&apos;ll eventually need multiple <code>Picker</code> columns, let&apos;s create a view to represent a single one.</p><p>Our <code>TimePickerView</code> will accept a <code>title</code> for the label on the right-hand side, a range of values to display, and a binding value that will update with the user&apos;s selection.</p><pre><code class="language-swift">struct TimePickerView: View {
    let title: String
    let range: ClosedRange&lt;Int&gt;
    let binding: Binding&lt;Int&gt;

    var body: some View {
        ...
    }
}
</code></pre><p>While Apple&apos;s implementation here is fairly custom, after playing around with the implementation for a while, I was able to come up with a reasonably close approximation.</p><pre><code class="language-swift">struct TimePickerView: View {
    // This is used to tighten up the spacing between the Picker and its
    // respective label
    //
    // This allows us to avoid having to use custom
    private let pickerViewTitlePadding: CGFloat = 4.0

    let title: String
    let range: ClosedRange&lt;Int&gt;
    let binding: Binding&lt;Int&gt;

    var body: some View {
        HStack(spacing: -pickerViewTitlePadding) {
            Picker(title, selection: binding) {
                ForEach(range, id: \.self) { timeIncrement in
                    HStack {
                        // Forces the text in the Picker to be
                        // right-aligned
                        Spacer()
                        Text(&quot;\(timeIncrement)&quot;)
                            .foregroundColor(.white)
                            .multilineTextAlignment(.trailing)
                    }
                }
            }
            .pickerStyle(InlinePickerStyle())
            .labelsHidden()

            Text(title)
                .fontWeight(.bold)
        }
    }
}</code></pre><p>Now, we can add this custom component to our main <code>View</code>:</p><pre><code class="language-swift">class TimerViewModel: ObservableObject {
    @Published var selectedHoursAmount = 10
    @Published var selectedMinutesAmount = 10
    @Published var selectedSecondsAmount = 10

    let hoursRange = 0...23
    let minutesRange = 0...59
    let secondsRange = 0...59
}

struct TimerView: View {
    @StateObject private var model = TimerViewModel()

    var body: some View {
        HStack() {
            TimePickerView(title: &quot;hours&quot;, 
            	range: model.hoursRange, 
                binding: $model.selectedHoursAmount)
            TimePickerView(title: &quot;min&quot;, 
                range: model.minutesRange, 
                binding: $model.selectedMinutesAmount)
            TimePickerView(title: &quot;sec&quot;, 
            	range: model.secondsRange, 
                binding: $model.selectedSecondsAmount)
        }
        .padding(.all, 32)
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(.black)
        .foregroundColor(.white)
    }
}</code></pre><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/02/Screenshot-2023-02-20-at-3.05.10-PM.png" class="kg-image" alt loading="lazy" width="431" height="804"></figure><hr><blockquote>An alternative implementation is presented <a href="https://stackoverflow.com/questions/66601955/is-there-support-for-something-like-timepicker-hours-mins-secs-in-swiftui">here</a> for the multi-column <code>Picker</code> view, but it&apos;s pretty involved...<br><br>It requires overlaying a <code>HStack</code> with two <code>Text</code> views onto the <code>Picker</code> view using a <code>ZStack</code>. Then, it sets clear text in the first <code>Text</code> view to offset the position of the second <code>Text</code> view and uses custom <code>alignmentGuides</code> to bring everything into alignment. <br><br>I was able to achieve the desired effect by simply adding a minor offset of -5 to the <code>spacing</code> property on the containing <code>HStack</code>.</blockquote><hr><h3 id="2-start-stop-button-controls">2. Start / Stop Button Controls</h3><p>To create our custom buttons, we can define a new <code>ButtonStyle</code> for each of the 3 variants (start, resume, pause):</p><pre><code class="language-swift">struct StartButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -&gt; some View {
        configuration.label
            .frame(width: 70, height: 70)
            .foregroundColor(Color(&quot;TimerButtonStart&quot;))
            .background(Color(&quot;TimerButtonStart&quot;).opacity(0.3))
            .clipShape(Circle())
            .padding(.all, 3)
            .overlay(
                Circle()
                    .stroke(Color(&quot;TimerButtonStart&quot;)
                    	.opacity(0.3), lineWidth: 2)
            )
    }
}
</code></pre><p>I&apos;ve also created <code>CancelButtonStyle</code> and <code>PauseButtonStyle</code> that share the same implementation, but with different <code>foregroundColor</code> and <code>background</code> properties.</p><p>Now, we can add them to our <code>TimerView</code>: </p><pre><code class="language-swift">HStack {
    Button(&quot;Cancel&quot;) {}
    .buttonStyle(CancelButtonStyle())

    Spacer()

    Button(&quot;Start&quot;) {}
    .buttonStyle(StartButtonStyle())
}
.padding(.horizontal, 32)</code></pre><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/02/Screenshot-2023-02-20-at-3.38.50-PM.png" class="kg-image" alt loading="lazy" width="387" height="760"></figure><p>Now we&apos;re getting somewhere!</p><h3 id="3-circular-progress-view">3. Circular Progress View</h3><p>When we take another look at the iOS Timer app&apos;s implementation, we can see that the <code>ProgressView</code> consists of two components; one of them is a gray circle which becomes visible only when the orange circle fades away to reveal it.</p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/02/IMG_9F8F29D2830C-1-1.png" class="kg-image" alt loading="lazy" width="1170" height="1474" srcset="https://digitalbunker.dev/content/images/size/w600/2023/02/IMG_9F8F29D2830C-1-1.png 600w, https://digitalbunker.dev/content/images/size/w1000/2023/02/IMG_9F8F29D2830C-1-1.png 1000w, https://digitalbunker.dev/content/images/2023/02/IMG_9F8F29D2830C-1-1.png 1170w" sizes="(min-width: 720px) 720px"></figure><p>Our custom <code>CircularProgressView</code> is also modeled after this behavior:</p><pre><code class="language-swift">struct CircularProgressView: View {
    @Binding var progress: Float

    var body: some View {
        ZStack {
            // Gray circle
            Circle()
                .stroke(lineWidth: 8.0)
                .opacity(0.3)
                .foregroundColor(Color(&quot;TimerButtonCancel&quot;))

             // Orange circle
            Circle()
                .trim(from: 0.0, to: CGFloat(min(progress, 1.0)))
                .stroke(style: StrokeStyle(lineWidth: 8.0, 
                	lineCap: .round, lineJoin: .round))
                .foregroundColor(Color(&quot;TimerButtonPause&quot;))
                // Ensures the animation starts from 12 o&apos;clock
                .rotationEffect(Angle(degrees: 270))
        }
        // The progress animation will animate over 1 second which
        // allows for a continuous smooth update of the ProgressView
        .animation(.linear(duration: 1.0), value: progress)
    }
}</code></pre><p>Now, in our <code>TimerViewModel</code>, I&apos;ve added the following properties and helper functions:</p><pre><code class="language-swift">// Represents the different states the timer can be in
enum TimerState {
    case active
    case paused
    case resumed
    case cancelled
}


// MARK: Private Properties
private var timer = Timer()
private var totalTimeForCurrentSelection: Int {
    (selectedHoursAmount * 3600) + (selectedMinutesAmount * 60) + selectedSecondsAmount
}

// MARK: Public Properties
@Published var state: TimerState = .cancelled {
    didSet {
    	// Modeled as a state machine for easier testing and
        // a cleaner / more readable implementation
        switch state {
        case .cancelled:
        	// Cancel the timer and reset all progress properties
            timer.invalidate()
            secondsToCompletion = 0
            progress = 0

        case .active:
        	// Starts the timer and sets all progress properties
            // to their initial values
            startTimer()

            secondsToCompletion = totalTimeForCurrentSelection
            progress = 1.0

            updateCompletionDate()

        case .paused:
        	// We want to pause the timer, but we
            // don&apos;t want to change the state of our progress
            // properties (secondsToCompletion and progress)
            timer.invalidate()

        case .resumed:
        	// Resumes the timer
            startTimer()
            
            // We don&apos;t know how long we&apos;ve been paused for, so
            // we need to update our ETA
            updateCompletionDate()
        }
    }
}

// Powers the ProgressView
@Published var secondsToCompletion = 0
@Published var progress: Float = 0.0
@Published var completionDate = Date.now

private func startTimer() {
    timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { [weak self] _ in
        guard let self else { return }

        self.secondsToCompletion -= 1
        self.progress = Float(self.secondsToCompletion) / Float(self.totalTimeForCurrentSelection)

        // We can&apos;t do &lt;= here because we need to ensure the animation
        // has time to finish running (see .linear(duration: 1.0))
        if self.secondsToCompletion &lt; 0 {
            self.state = .cancelled
        }
    })
}

private func updateCompletionDate() {
    completionDate = Date.now.addingTimeInterval(Double(secondsToCompletion))
}</code></pre><p>I&apos;ve also added a small extension on <code>Int</code> to more conveniently convert some number of seconds into a convenient timestamp format (ex. 12:23:45):</p><pre><code>extension Int {
    var asTimestamp: String {
        let hour = self / 3600
        let minute = self / 60 % 60
        let second = self % 60

        return String(format: &quot;%02i:%02i:%02i&quot;, hour, minute, second)
    }
}</code></pre><p>With all of this logic in place, we can turn our attention to cleaning up the implementation of our main <code>View</code>.</p><h3 id="4-adding-pause-timer-controls">4. Adding Pause Timer Controls</h3><p>We&apos;ll start by moving the timing controls - start, pause, and resume - into their own computed property. This property will refer to <code>model.state</code> to determine the appropriate buttons to show.</p><pre><code class="language-swift">var timerControls: some View {
    HStack {
        Button(&quot;Cancel&quot;) {
            model.state = .cancelled
        }
        .buttonStyle(CancelButtonStyle())

        Spacer()

        switch model.state {
        case .cancelled:
            Button(&quot;Start&quot;) {
                model.state = .active
            }
            .buttonStyle(StartButtonStyle())
        case .paused:
            Button(&quot;Resume&quot;) {
                model.state = .resumed
            }
            .buttonStyle(PauseButtonStyle())
        case .active, .resumed:
            Button(&quot;Pause&quot;) {
                model.state = .paused
            }
            .buttonStyle(PauseButtonStyle())
        }
    }
    .padding(.horizontal, 32)
}</code></pre><p>We can break out our multi-column <code>Picker</code> and <code>CircularProgressView</code> in the same way:</p><pre><code class="language-swift">var timePickerControl: some View {
    HStack() {
        TimePickerView(title: &quot;hours&quot;, range: model.hoursRange, binding: $model.selectedHoursAmount)
        TimePickerView(title: &quot;min&quot;, range: model.minutesRange, binding: $model.selectedMinutesAmount)
        TimePickerView(title: &quot;sec&quot;, range: model.secondsRange, binding: $model.selectedSecondsAmount)
    }
    .frame(width: 360, height: 255)
    .padding(.all, 32)
}

var progressView: some View {
    ZStack {
        withAnimation {
            CircularProgressView(progress: $model.progress)
        }

        VStack {
            Text(model.secondsToCompletion.asTimestamp)
                .font(.largeTitle)
            HStack {
                Image(systemName: &quot;bell.fill&quot;)
                Text(model.completionDate, format: .dateTime.hour().minute())
            }
        }
    }
    .frame(width: 360, height: 255)
    .padding(.all, 32)
}</code></pre><p>Now, our <code>body</code> is much easier to read and understand.</p><pre><code class="language-swift">var body: some View {
    VStack {
        if model.state == .cancelled {
            timePickerControl
        } else {
            progressView
        }

        timerControls
        Spacer()
    }
    .frame(maxWidth: .infinity, maxHeight: .infinity)
    .background(.black)
    .foregroundColor(.white)
}</code></pre><h2 id="demo">Demo</h2><p>And finally, we have a working replica of &#xA0;the Timer app along with a clean SwiftUI implementation:</p><figure class="kg-card kg-video-card"><div class="kg-video-container"><video src="https://digitalbunker.dev/content/media/2023/02/Timer-Demo.mp4" poster="https://img.spacergif.org/v1/886x1920/0a/spacer.png" width="886" height="1920" playsinline preload="metadata" style="background: transparent url(&apos;https://digitalbunker.dev/content/images/2023/02/media-thumbnail-ember746.jpg&apos;) 50% 50% / cover no-repeat;"></video><div class="kg-video-overlay"><button class="kg-video-large-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button></div><div class="kg-video-player-container"><div class="kg-video-player"><button class="kg-video-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button><button class="kg-video-pause-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg></button><span class="kg-video-current-time">0:00</span><div class="kg-video-time">/<span class="kg-video-duration"></span></div><input type="range" class="kg-video-seek-slider" max="100" value="0"><button class="kg-video-playback-rate">1&#xD7;</button><button class="kg-video-unmute-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg></button><button class="kg-video-mute-icon kg-video-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg></button><input type="range" class="kg-video-volume-slider" max="100" value="100"></div></div></div></figure><p>You can find the source code <a href="https://github.com/aryamansharda/SwiftUITimerApp">here</a>!</p><hr><p>If you&apos;re interested in more articles about iOS Development &amp; Swift, check out my <a href="https://www.youtube.com/c/AryamanSharda">YouTube channel</a> or <a href="https://twitter.com/aryamansharda">follow me on Twitter</a>. </p><p>And, if you&apos;re an indie iOS developer, make sure to <a href="https://indie.watch/">check out my newsletter</a>! Each issue features a new indie developer, so feel free to <a href="https://indie.watch/submit">submit</a> your iOS apps.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://aryamansharda.gumroad.com/l/tcvck"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Ace The iOS Interview</div><div class="kg-bookmark-description">The best investment for landing your dream iOS jobHey there! My name is Aryaman Sharda and I started making iOS apps way back in 2015. Since then, I&#x2019;ve worked for a variety of companies like Porsche, Turo, and Scoop Technologies just to name a few. Over the years, I&#x2019;ve mentored junior engineers, bui&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://public-files.gumroad.com/variants/th3wj5f20j7m2ebtdwnwln5b2w6s/4ec519eb32080d4ff1ef08cba157dc2ac7dab092fa26aeca54e8e2b8f31f9a63" alt><span class="kg-bookmark-author">Gumroad</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://public-files.gumroad.com/variants/cv3bdpxesm5j3j4bupe9ntfh2abn/3298c3eb001bbed90f1d616da66708480096a0a1b6e81bd4f8a2d6e9b831d301" alt></div></a></figure>]]></content:encoded></item><item><title><![CDATA[How To Create a PDF In A WKWebView]]></title><description><![CDATA[In this article, we'll see how we can save the contents of a WKWebView as a PDF using Swift 5.]]></description><link>https://digitalbunker.dev/how-to-create-pdf-from-wkwebview/</link><guid isPermaLink="false">63f18658fd7aed537923eb85</guid><category><![CDATA[articles]]></category><category><![CDATA[basics]]></category><category><![CDATA[ios]]></category><category><![CDATA[swift]]></category><category><![CDATA[things to know]]></category><dc:creator><![CDATA[Aryaman Sharda]]></dc:creator><pubDate>Sun, 19 Feb 2023 20:32:48 GMT</pubDate><content:encoded><![CDATA[<p>In this article, we&apos;ll take a look at how to save the contents of a <code>WKWebView</code> as a PDF.</p><h3 id="setting-up-the-wkwebview">Setting up the WKWebView</h3><p>To get things started, let&apos;s create our <code>WKWebView</code>:</p><pre><code class="language-swift">import WebKit

private lazy var webView: WKWebView = {
    // We&apos;ll go ahead and use the default configuration here. 
    // There&apos;s some cool configuration options here, but not in scope.
    let webView = WKWebView(frame: .zero, configuration: WKWebViewConfiguration())

    // We&apos;ll need this to determine when the web page has fully loaded.
    webView.navigationDelegate = self

    view.addSubview(webView)
    webView.pinToSuperview()
    return webView
}()</code></pre><p>Next, we&apos;ll have it show the webpage that we&apos;ll eventually convert into a PDF:</p><pre><code class="language-swift">override func viewDidLoad() {
    super.viewDidLoad()
    loadURL(from: &quot;https://www.digitalbunker.dev&quot;)
}

private func loadURL(from string: String) {
    guard let url = URL(string: string) else {
        return
    }

    webView.load(URLRequest(url: url))
}</code></pre><p>We can&apos;t generate the PDF until we know the webpage has finished loading, otherwise, we&apos;d just end up with an empty file as output. </p><p>In order to detect when loading is finished, we&apos;ll need to implement <code>func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)</code> from <code>WKNavigationDelegate</code>.</p><pre><code class="language-swift">extension ViewController: WKNavigationDelegate {
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        // The web view has finished loading the resource, we can now create a PDF
        saveAsPDF(title: webView.title)
    }
}</code></pre><h3 id="creating-the-pdf">Creating the PDF</h3><p>Now, we&apos;re finally in a position where we can take the loaded webpage and save it as a PDF in our <code>.downloadsDirectory</code>, <code>.documentsDirectory</code>, etc.</p><p>We&apos;ll start off by creating a <code>WKPDFConfiguration</code>. This object only exposes one property - <code>rect</code> - that allows us to specify the area of the <code>WKWebView</code> we want to capture. &#xA0;</p><pre><code class="language-swift">let pdfConfiguration = WKPDFConfiguration()

/// Using `webView.scrollView.frame` allows us to capture the 
// entire page, not just the visible portion
pdfConfiguration.rect = CGRect(x: 0, y: 0, width: webView.scrollView.contentSize.width, height: webView.scrollView.contentSize.height)</code></pre><p>If you just wanted to capture the currently visible area instead, you could do something like this:</p><pre><code class="language-swift">pdfConfiguration.rect = CGRect(x: 0, y: 0, width: webView.frame.width, height: webView.frame.height)</code></pre><p>In iOS 14.0+, <code>WKWebView</code> provides a <code>createPDF()</code> that takes in the <code>WKPDFConfigruation</code> we made earlier:</p><pre><code class="language-swift">webView.createPDF(configuration: pdfConfiguration) { result in
    switch result {
    case .success(let data):
        // Creates a path to the downloads directory
        guard let downloadsDirectory = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first else {
            return
        }

        do {
            // Attempts to save PDF and logs an error otherwise
            let savePath = downloadsDirectory.appendingPathComponent(title ?? &quot;PDF&quot;).appendingPathExtension(&quot;pdf&quot;)
            try data.write(to: savePath)

            print(&quot;Successfully created and saved PDF at \(savePath)&quot;)
        } catch let error {
            print(&quot;Could not save pdf due to \(error.localizedDescription)&quot;)
        }

    case .failure(let failure):
        print(failure.localizedDescription)
    }
}</code></pre><p>And, we&apos;ll now have our exported PDF available in our device&apos;s downloads directory:</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">&#x1F4A1;</div><div class="kg-callout-text">Since we used <code>webView.scrollView.contentSize</code>, we get the full contents of the webpage, not just the currently visible portion.</div></div><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/02/Screenshot-2023-02-19-at-12.31.25-PM.png" class="kg-image" alt loading="lazy" width="1373" height="1268" srcset="https://digitalbunker.dev/content/images/size/w600/2023/02/Screenshot-2023-02-19-at-12.31.25-PM.png 600w, https://digitalbunker.dev/content/images/size/w1000/2023/02/Screenshot-2023-02-19-at-12.31.25-PM.png 1000w, https://digitalbunker.dev/content/images/2023/02/Screenshot-2023-02-19-at-12.31.25-PM.png 1373w" sizes="(min-width: 720px) 720px"></figure><p>You can find the source code for this article <a href="https://gist.github.com/aryamansharda/6e4088072b870b7367d65db6b6a592d0">here</a>. </p><hr><p>If you&apos;re interested in more articles about iOS Development &amp; Swift, check out my <a href="https://www.youtube.com/c/AryamanSharda">YouTube channel</a> or <a href="https://twitter.com/aryamansharda">follow me on Twitter</a>. </p><p>And, if you&apos;re an indie iOS developer, make sure to <a href="https://indie.watch/">check out my newsletter</a>! Each issue features a new indie developer, so feel free to <a href="https://indie.watch/submit">submit</a> your iOS apps.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://aryamansharda.gumroad.com/l/tcvck"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Ace The iOS Interview</div><div class="kg-bookmark-description">The best investment for landing your dream iOS jobHey there! My name is Aryaman Sharda and I started making iOS apps way back in 2015. Since then, I&#x2019;ve worked for a variety of companies like Porsche, Turo, and Scoop Technologies just to name a few. Over the years, I&#x2019;ve mentored junior engineers, bui&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://public-files.gumroad.com/variants/th3wj5f20j7m2ebtdwnwln5b2w6s/4ec519eb32080d4ff1ef08cba157dc2ac7dab092fa26aeca54e8e2b8f31f9a63" alt><span class="kg-bookmark-author">Gumroad</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://public-files.gumroad.com/variants/cv3bdpxesm5j3j4bupe9ntfh2abn/3298c3eb001bbed90f1d616da66708480096a0a1b6e81bd4f8a2d6e9b831d301" alt></div></a></figure>]]></content:encoded></item><item><title><![CDATA[Code Replay for Xcode]]></title><description><![CDATA[<figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/02/AstraStrong.png" class="kg-image" alt loading="lazy" width="1920" height="1080" srcset="https://digitalbunker.dev/content/images/size/w600/2023/02/AstraStrong.png 600w, https://digitalbunker.dev/content/images/size/w1000/2023/02/AstraStrong.png 1000w, https://digitalbunker.dev/content/images/size/w1600/2023/02/AstraStrong.png 1600w, https://digitalbunker.dev/content/images/2023/02/AstraStrong.png 1920w" sizes="(min-width: 720px) 720px"></figure><p>When creating YouTube videos or <a href="https://app.gumroad.com/products/tcvck">writing about iOS Development</a>, I&apos;ll typically try out the code in Xcode first and then convert it into either a blog post or YouTube video once the implementation is complete.</p><p>When it comes time to make the video tutorial, I&apos;ll often</p>]]></description><link>https://digitalbunker.dev/code-repaly/</link><guid isPermaLink="false">63e01e6dfd7aed537923ea41</guid><dc:creator><![CDATA[Aryaman Sharda]]></dc:creator><pubDate>Mon, 06 Feb 2023 02:04:40 GMT</pubDate><content:encoded><![CDATA[<figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/02/AstraStrong.png" class="kg-image" alt loading="lazy" width="1920" height="1080" srcset="https://digitalbunker.dev/content/images/size/w600/2023/02/AstraStrong.png 600w, https://digitalbunker.dev/content/images/size/w1000/2023/02/AstraStrong.png 1000w, https://digitalbunker.dev/content/images/size/w1600/2023/02/AstraStrong.png 1600w, https://digitalbunker.dev/content/images/2023/02/AstraStrong.png 1920w" sizes="(min-width: 720px) 720px"></figure><p>When creating YouTube videos or <a href="https://app.gumroad.com/products/tcvck">writing about iOS Development</a>, I&apos;ll typically try out the code in Xcode first and then convert it into either a blog post or YouTube video once the implementation is complete.</p><p>When it comes time to make the video tutorial, I&apos;ll often edit out the parts that show me typing as I feel it&apos;s not an efficient use of the viewer&apos;s time. I&apos;d much rather have the code appear immediately and spend the time walking through it instead.</p><p>Let&apos;s say I was creating a tutorial on how to implement this function:</p><pre><code class="language-swift">func isPrime(num: Int) -&gt; Bool {
    guard num &gt; 1 else { 
    	return false 
    }

    for index in 2..&lt;num {
        if num % index == 0 {
            return false
        }
    }

    return true
}</code></pre><p>In the case of this function, for example, I may want to start off by introducing the method signature, a discussion of some edge cases, the main implementation, and then conclude with some additional optimizations or alternative implementations. </p><p>I may even want to turn it into a blog post afterward &#x1F937;.</p><p>I wanted to share a tool that I created for myself to help me streamline both of these workflows.</p><p>Code Replay is an Xcode Editor Extension designed to easily capture the incremental stages of implementation, streamlining the creation of documentation, a YouTube video, or a blog post.</p><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/xYngwS1OVU0?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="Code Replay Demo Video"></iframe></figure><p>I&apos;ll kick things off by creating a &quot;New Session.&quot; Now, as I work through the implementation, I can establish &quot;Checkpoints&quot; at important intervals. </p><p>These Checkpoints will not only be saved to a Markdown file to simplify the process of writing the blog post, but I can also &quot;Replay&quot; them to make the video more concise and streamlined.</p><p>This is a tool I developed primarily for personal use to streamline the blogging and tutorial recording process and I understand the target audience is limited. Regardless, once I&apos;m no longer embarrassed by the code, I&apos;ll release it as an open-source project &#x1F60A;.</p>]]></content:encoded></item><item><title><![CDATA[Introducing EditKit Pro]]></title><description><![CDATA[<p>Hi all, </p><p>I&apos;ve been working on a new Xcode Editor Extension and I&apos;d love to get your feedback on it.</p><p>My goal was to create something that would provide developers with a wide range of conveniences but to go one step further and address issues that</p>]]></description><link>https://digitalbunker.dev/editkit-pro/</link><guid isPermaLink="false">63ca2f30fd7aed537923e923</guid><category><![CDATA[app store]]></category><category><![CDATA[software engineering]]></category><category><![CDATA[xcode]]></category><dc:creator><![CDATA[Aryaman Sharda]]></dc:creator><pubDate>Sat, 21 Jan 2023 10:00:32 GMT</pubDate><content:encoded><![CDATA[<p>Hi all, </p><p>I&apos;ve been working on a new Xcode Editor Extension and I&apos;d love to get your feedback on it.</p><p>My goal was to create something that would provide developers with a wide range of conveniences but to go one step further and address issues that code snippets couldn&apos;t solve on their own.</p><p>-Aryaman S.</p><p>Download link: <a href="https://apps.apple.com/us/app/editkit-pro/id1659984546">https://apps.apple.com/us/app/editkit-pro/id1659984546</a></p><p>If you have any feature requests or bug reports, please let me know at <a href="mailto:aryaman@digitalbunker.dev">aryaman@digitalbunker.dev</a> &#x1F60A;</p><hr><h2 id="installation">Installation</h2><p>Once you&apos;ve downloaded it from the App Store, open System Preferences -&gt; Extensions -&gt; Enable EditKit Pro.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://digitalbunker.dev/content/images/2023/02/Screenshot-2023-02-17-at-10.04.42-AM.png" class="kg-image" alt loading="lazy" width="827" height="737" srcset="https://digitalbunker.dev/content/images/size/w600/2023/02/Screenshot-2023-02-17-at-10.04.42-AM.png 600w, https://digitalbunker.dev/content/images/2023/02/Screenshot-2023-02-17-at-10.04.42-AM.png 827w" sizes="(min-width: 720px) 720px"><figcaption>While you&apos;re in the App Store, <a href="https://t.co/867MwUUCDZ">RemafoX</a> is worth checking out as well by <a href="https://twitter.com/Jeehut">Cihat G&#xFC;nd&#xFC;z</a></figcaption></figure><p>You may need to restart Xcode. </p><p>The extension should be available in the Editor menu.</p><p>You can set up custom shortcuts for your favorite actions with Xcode&apos;s Key Bindings:</p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/02/Screenshot-2023-02-18-at-12.01.56-PM.png" class="kg-image" alt loading="lazy" width="942" height="848" srcset="https://digitalbunker.dev/content/images/size/w600/2023/02/Screenshot-2023-02-18-at-12.01.56-PM.png 600w, https://digitalbunker.dev/content/images/2023/02/Screenshot-2023-02-18-at-12.01.56-PM.png 942w" sizes="(min-width: 720px) 720px"></figure><hr><h3 id="align-around-equals">Align Around Equals</h3><p>Aligns statements are the <code>=</code> operator. It&apos;s important to use a monospaced font, otherwise, you&apos;ll see minor discrepancies like in the video below. </p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/01/Align-Around-Equals.gif" class="kg-image" alt loading="lazy" width="2000" height="1125" srcset="https://digitalbunker.dev/content/images/size/w600/2023/01/Align-Around-Equals.gif 600w, https://digitalbunker.dev/content/images/size/w1000/2023/01/Align-Around-Equals.gif 1000w, https://digitalbunker.dev/content/images/size/w1600/2023/01/Align-Around-Equals.gif 1600w, https://digitalbunker.dev/content/images/size/w2400/2023/01/Align-Around-Equals.gif 2400w" sizes="(min-width: 720px) 720px"></figure><h3 id="auto-mark">Auto Mark</h3><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/01/AutoMark.gif" class="kg-image" alt loading="lazy" width="2000" height="1293" srcset="https://digitalbunker.dev/content/images/size/w600/2023/01/AutoMark.gif 600w, https://digitalbunker.dev/content/images/size/w1000/2023/01/AutoMark.gif 1000w, https://digitalbunker.dev/content/images/size/w1600/2023/01/AutoMark.gif 1600w, https://digitalbunker.dev/content/images/size/w2400/2023/01/AutoMark.gif 2400w" sizes="(min-width: 720px) 720px"></figure><h3 id="beautify-json">Beautify JSON</h3><p>Fixes issues with poorly formatted JSON. Useful if you use JSON to represent mock data in your apps.</p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/01/BeautifyJSON.gif" class="kg-image" alt loading="lazy" width="2000" height="1125" srcset="https://digitalbunker.dev/content/images/size/w600/2023/01/BeautifyJSON.gif 600w, https://digitalbunker.dev/content/images/size/w1000/2023/01/BeautifyJSON.gif 1000w, https://digitalbunker.dev/content/images/size/w1600/2023/01/BeautifyJSON.gif 1600w, https://digitalbunker.dev/content/images/size/w2400/2023/01/BeautifyJSON.gif 2400w" sizes="(min-width: 720px) 720px"></figure><h3 id="convert-json-to-codable">Convert JSON To Codable</h3><p>Creates Codable models that match the provided JSON in the clipboard.</p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/01/JSON-to-Codable.gif" class="kg-image" alt loading="lazy" width="2000" height="1125" srcset="https://digitalbunker.dev/content/images/size/w600/2023/01/JSON-to-Codable.gif 600w, https://digitalbunker.dev/content/images/size/w1000/2023/01/JSON-to-Codable.gif 1000w, https://digitalbunker.dev/content/images/size/w1600/2023/01/JSON-to-Codable.gif 1600w, https://digitalbunker.dev/content/images/size/w2400/2023/01/JSON-to-Codable.gif 2400w" sizes="(min-width: 720px) 720px"></figure><h3 id="create-type-definition">Create Type Definition</h3><p>This utility considers the suffix of the filename and creates the initial class definition matching that name and type.</p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/01/Create-Type-Definition.gif" class="kg-image" alt loading="lazy" width="2000" height="1125" srcset="https://digitalbunker.dev/content/images/size/w600/2023/01/Create-Type-Definition.gif 600w, https://digitalbunker.dev/content/images/size/w1000/2023/01/Create-Type-Definition.gif 1000w, https://digitalbunker.dev/content/images/size/w1600/2023/01/Create-Type-Definition.gif 1600w, https://digitalbunker.dev/content/images/size/w2400/2023/01/Create-Type-Definition.gif 2400w" sizes="(min-width: 720px) 720px"></figure><h3 id="format-as-multiline">Format As Multiline</h3><p>Breaks an array or a long line of code into multiple lines.</p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/01/Multiline.gif" class="kg-image" alt loading="lazy" width="2000" height="1125" srcset="https://digitalbunker.dev/content/images/size/w600/2023/01/Multiline.gif 600w, https://digitalbunker.dev/content/images/size/w1000/2023/01/Multiline.gif 1000w, https://digitalbunker.dev/content/images/size/w1600/2023/01/Multiline.gif 1600w, https://digitalbunker.dev/content/images/size/w2400/2023/01/Multiline.gif 2400w" sizes="(min-width: 720px) 720px"></figure><h3 id="copy-as-markdown">Copy As Markdown</h3><p>Automatically adds the ``` characters to the selected text to make sharing on JIRA, Confluence, GitHub, etc. much easier.</p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/01/Copy-As-Markdown.gif" class="kg-image" alt loading="lazy" width="2000" height="1125" srcset="https://digitalbunker.dev/content/images/size/w600/2023/01/Copy-As-Markdown.gif 600w, https://digitalbunker.dev/content/images/size/w1000/2023/01/Copy-As-Markdown.gif 1000w, https://digitalbunker.dev/content/images/size/w1600/2023/01/Copy-As-Markdown.gif 1600w, https://digitalbunker.dev/content/images/size/w2400/2023/01/Copy-As-Markdown.gif 2400w" sizes="(min-width: 720px) 720px"></figure><h3 id="search-on-google-github-stackoverflow">Search on Google / GitHub / StackOverflow</h3><p>A convenient utility to search for the selection on a variety of platforms. It&apos;s useful for finding out more information about a class or line of code you&apos;re unfamiliar with and/or confused about.</p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/01/Search-On.gif" class="kg-image" alt loading="lazy" width="2000" height="1125" srcset="https://digitalbunker.dev/content/images/size/w600/2023/01/Search-On.gif 600w, https://digitalbunker.dev/content/images/size/w1000/2023/01/Search-On.gif 1000w, https://digitalbunker.dev/content/images/size/w1600/2023/01/Search-On.gif 1600w, https://digitalbunker.dev/content/images/size/w2400/2023/01/Search-On.gif 2400w" sizes="(min-width: 720px) 720px"></figure><h3 id="sort-imports">Sort Imports</h3><p>No selection is required. The utility will automatically identify the <code>import</code> statements and order them alphabetically.</p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/01/Sort-Imports.gif" class="kg-image" alt loading="lazy" width="2000" height="1125" srcset="https://digitalbunker.dev/content/images/size/w600/2023/01/Sort-Imports.gif 600w, https://digitalbunker.dev/content/images/size/w1000/2023/01/Sort-Imports.gif 1000w, https://digitalbunker.dev/content/images/size/w1600/2023/01/Sort-Imports.gif 1600w, https://digitalbunker.dev/content/images/size/w2400/2023/01/Sort-Imports.gif 2400w" sizes="(min-width: 720px) 720px"></figure><h3 id="sort-lines-alphabetically">Sort Lines Alphabetically</h3><p>Sorts the selection alphabetically.</p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/01/Sort-Lines-Alphabetically.gif" class="kg-image" alt loading="lazy" width="2000" height="1125" srcset="https://digitalbunker.dev/content/images/size/w600/2023/01/Sort-Lines-Alphabetically.gif 600w, https://digitalbunker.dev/content/images/size/w1000/2023/01/Sort-Lines-Alphabetically.gif 1000w, https://digitalbunker.dev/content/images/size/w1600/2023/01/Sort-Lines-Alphabetically.gif 1600w, https://digitalbunker.dev/content/images/size/w2400/2023/01/Sort-Lines-Alphabetically.gif 2400w" sizes="(min-width: 720px) 720px"></figure><h3 id="sort-lines-by-length">Sort Lines By Length</h3><p>Sorts the selection by line length.</p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/01/Sort-Lines-By-Length.gif" class="kg-image" alt loading="lazy" width="2000" height="1125" srcset="https://digitalbunker.dev/content/images/size/w600/2023/01/Sort-Lines-By-Length.gif 600w, https://digitalbunker.dev/content/images/size/w1000/2023/01/Sort-Lines-By-Length.gif 1000w, https://digitalbunker.dev/content/images/size/w1600/2023/01/Sort-Lines-By-Length.gif 1600w, https://digitalbunker.dev/content/images/size/w2400/2023/01/Sort-Lines-By-Length.gif 2400w" sizes="(min-width: 720px) 720px"></figure><h3 id="wrap-in-ifdef">Wrap in #ifdef</h3><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/01/WrapInIfDef.gif" class="kg-image" alt loading="lazy" width="2000" height="1293" srcset="https://digitalbunker.dev/content/images/size/w600/2023/01/WrapInIfDef.gif 600w, https://digitalbunker.dev/content/images/size/w1000/2023/01/WrapInIfDef.gif 1000w, https://digitalbunker.dev/content/images/size/w1600/2023/01/WrapInIfDef.gif 1600w, https://digitalbunker.dev/content/images/size/w2400/2023/01/WrapInIfDef.gif 2400w" sizes="(min-width: 720px) 720px"></figure><h3 id="auto-localize-string">Auto Localize String</h3><p>Identifies any text in quotes and wraps it in a <code>NSLocalizedString</code> call.</p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/01/Wrap-In-Localized-String.gif" class="kg-image" alt loading="lazy" width="2000" height="1125" srcset="https://digitalbunker.dev/content/images/size/w600/2023/01/Wrap-In-Localized-String.gif 600w, https://digitalbunker.dev/content/images/size/w1000/2023/01/Wrap-In-Localized-String.gif 1000w, https://digitalbunker.dev/content/images/size/w1600/2023/01/Wrap-In-Localized-String.gif 1600w, https://digitalbunker.dev/content/images/size/w2400/2023/01/Wrap-In-Localized-String.gif 2400w" sizes="(min-width: 720px) 720px"></figure><hr><h3 id="swiftui-disable-outer-view">[SwiftUI] Disable Outer View</h3><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/01/Disable-Outer-View.gif" class="kg-image" alt loading="lazy" width="2000" height="1293" srcset="https://digitalbunker.dev/content/images/size/w600/2023/01/Disable-Outer-View.gif 600w, https://digitalbunker.dev/content/images/size/w1000/2023/01/Disable-Outer-View.gif 1000w, https://digitalbunker.dev/content/images/size/w1600/2023/01/Disable-Outer-View.gif 1600w, https://digitalbunker.dev/content/images/size/w2400/2023/01/Disable-Outer-View.gif 2400w" sizes="(min-width: 720px) 720px"></figure><h3 id="swiftui-delete-outer-view">[SwiftUI] Delete Outer View</h3><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/01/DeleteOuterView.gif" class="kg-image" alt loading="lazy" width="2000" height="1293" srcset="https://digitalbunker.dev/content/images/size/w600/2023/01/DeleteOuterView.gif 600w, https://digitalbunker.dev/content/images/size/w1000/2023/01/DeleteOuterView.gif 1000w, https://digitalbunker.dev/content/images/size/w1600/2023/01/DeleteOuterView.gif 1600w, https://digitalbunker.dev/content/images/size/w2400/2023/01/DeleteOuterView.gif 2400w" sizes="(min-width: 720px) 720px"></figure><h3 id="swiftui-disable-view">[SwiftUI] Disable View</h3><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/01/DisableView.gif" class="kg-image" alt loading="lazy" width="2000" height="1293" srcset="https://digitalbunker.dev/content/images/size/w600/2023/01/DisableView.gif 600w, https://digitalbunker.dev/content/images/size/w1000/2023/01/DisableView.gif 1000w, https://digitalbunker.dev/content/images/size/w1600/2023/01/DisableView.gif 1600w, https://digitalbunker.dev/content/images/size/w2400/2023/01/DisableView.gif 2400w" sizes="(min-width: 720px) 720px"></figure><h3 id="swiftui-delete-view">[SwiftUI] Delete View</h3><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2023/01/Delete-View.gif" class="kg-image" alt loading="lazy" width="2000" height="1293" srcset="https://digitalbunker.dev/content/images/size/w600/2023/01/Delete-View.gif 600w, https://digitalbunker.dev/content/images/size/w1000/2023/01/Delete-View.gif 1000w, https://digitalbunker.dev/content/images/size/w1600/2023/01/Delete-View.gif 1600w, https://digitalbunker.dev/content/images/size/w2400/2023/01/Delete-View.gif 2400w" sizes="(min-width: 720px) 720px"></figure>]]></content:encoded></item><item><title><![CDATA[Using The Proxy Pattern In Swift 5]]></title><description><![CDATA[<p>Today, we&apos;ll take a closer look at the proxy design pattern. We&apos;ll get the formal definition out of the way quickly and then we&apos;ll jump into some practical examples.</p><h2 id="what-is-the-proxy-pattern">What Is The Proxy Pattern?</h2><p>The proxy pattern is a <a href="https://en.wikipedia.org/wiki/Structural_pattern">structural pattern</a> that helps you</p>]]></description><link>https://digitalbunker.dev/using-the-proxy-pattern-in-swift-5/</link><guid isPermaLink="false">633a5af84fcf3f0e28176504</guid><category><![CDATA[algorithms]]></category><category><![CDATA[articles]]></category><category><![CDATA[basics]]></category><category><![CDATA[computer science]]></category><category><![CDATA[implementation]]></category><category><![CDATA[programming]]></category><category><![CDATA[software engineering]]></category><category><![CDATA[things to know]]></category><category><![CDATA[swift]]></category><dc:creator><![CDATA[Aryaman Sharda]]></dc:creator><pubDate>Tue, 04 Oct 2022 04:53:53 GMT</pubDate><content:encoded><![CDATA[<p>Today, we&apos;ll take a closer look at the proxy design pattern. We&apos;ll get the formal definition out of the way quickly and then we&apos;ll jump into some practical examples.</p><h2 id="what-is-the-proxy-pattern">What Is The Proxy Pattern?</h2><p>The proxy pattern is a <a href="https://en.wikipedia.org/wiki/Structural_pattern">structural pattern</a> that helps you limit access to another class. </p><p>In practice, the proxy is typically implemented as a wrapper class that implements the same protocol and/or exposes the same interface as the class it&apos;s wrapping (a.k.a. a one-to-one wrapper). </p><figure class="kg-card kg-image-card"><img src="https://digitalbunker.dev/content/images/2022/10/icons.png" class="kg-image" alt loading="lazy" width="2000" height="652" srcset="https://digitalbunker.dev/content/images/size/w600/2022/10/icons.png 600w, https://digitalbunker.dev/content/images/size/w1000/2022/10/icons.png 1000w, https://digitalbunker.dev/content/images/size/w1600/2022/10/icons.png 1600w, https://digitalbunker.dev/content/images/size/w2400/2022/10/icons.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>This makes it easy for the proxy to add additional functionality before/after the request makes it to the wrapped object (i.e. middleware) thereby allowing you to introduce new capabilities without modifying the wrapped object&apos;s implementation (see <a href="https://blog.knoldus.com/what-is-liskov-substitution-principle-lsp-with-real-world-examples/#:~:text=Simply%20put%2C%20the%20Liskov%20Substitution,the%20objects%20of%20our%20superclass.">Liskov Substitution Principal</a> and <a href="https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle">Open&#x2013;closed principle</a>).</p><p>Take your credit card as an example. It&apos;s effectively a proxy for your bank account; it lets you accomplish the same things you could do with a bank account, but lets you wrap it in an extra layer of security. </p><h2 id="adding-additional-functionality">Adding Additional Functionality</h2><h3 id="ex-1-adding-timing-logging">Ex. 1: Adding Timing &amp; Logging</h3><p>As previously mentioned, one of the most common use cases for the proxy pattern is to perform an action before and/or after the request reaches the wrapped object. </p><p>Since the proxy implements the same interface as the wrapped object, it can be used as a drop-in replacement for the original object. In this case, we can easily use the proxy pattern to enable us to log execution time. </p><figure class="kg-card kg-code-card"><pre><code class="language-swift">protocol ExampleProtocol {
    func performAction()
}

struct ExampleService: ExampleProtocol {

    func performAction() {
        // Perform a long-running complex task
    }
}

struct ProfilingExampleService: ExampleProtocol {
    private let exampleService = ExampleService()

    func performAction() {
        let startingTime = Date()
        exampleService.performAction()

        let endingTime = Date()
        print(&quot;Time Elapsed: \(endingTime.timeIntervalSince(startingTime))&quot;)
    }
}

// Usage (1)
let service = ExampleService()

// Usage (2)
let service = ProfilingExampleService()</code></pre><figcaption>The proxy must provide the same interface as the resource it wraps thereby allowing the call site to use either <code>ExampleService</code> or <code>ProfilingExampleService</code>.</figcaption></figure><p>This also serves as a great demonstration of the <a href="https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle">Open&#x2013;closed principle</a> and <a href="https://blog.knoldus.com/what-is-liskov-substitution-principle-lsp-with-real-world-examples/#:~:text=Simply%20put%2C%20the%20Liskov%20Substitution,the%20objects%20of%20our%20superclass.">Liskov Substitution Principal</a> as we&apos;re able to introduce new behavior without changing the implementation of the original service.</p><h3 id="ex-2-cache">Ex. 2: Cache</h3><p>Imagine we have a service that allows us to download videos from a URL.</p><pre><code class="language-swift">protocol VideoDownloadService {
    func getVideo(url: String)
}

class MainVideoDownloadService: VideoDownloadService {
    func getVideo(url: String) {
        // Download video from URL
        URLSession.shared.downloadTask(with: URLRequest(url: URL(string: url)!)).resume()
    }
}

let videoDownloader = MainVideoDownloadService()
videoDownloader.getVideo(url: &quot;www.youtube.com/video/1&quot;)
videoDownloader.getVideo(url: &quot;www.youtube.com/video/2&quot;)
videoDownloader.getVideo(url: &quot;www.youtube.com/video/3&quot;)
videoDownloader.getVideo(url: &quot;www.youtube.com/video/2&quot;)
videoDownloader.getVideo(url: &quot;www.youtube.com/video/3&quot;)</code></pre><p>While this code certainly works, we would clearly benefit from having a cache of previously downloaded videos. With the use of the proxy pattern, adding this new behavior is trivial:</p><pre><code class="language-swift">class SmartVideoDownloadService: VideoDownloadService {
    private var cache = [String: URLSessionDownloadTask]()
    private let downloader = MainVideoDownloadService()

    func getVideo(url: String) {
        if cache[url] == nil {

            // Download video from URL
            let downloadTask = URLSession.shared.downloadTask(with: URLRequest(url: URL(string: url)!))
            cache[url] = downloadTask
            downloadTask.resume()
        } else {
            print(&quot;We&apos;ve already started downloading that video&quot;)
            print(&quot;Time Remaining: \(cache[url]?.progress.estimatedTimeRemaining)&quot;)
        }
    }
}

let videoDownloader = SmartVideoDownloadService()
videoDownloader.getVideo(url: &quot;www.youtube.com/video/1&quot;)
videoDownloader.getVideo(url: &quot;www.youtube.com/video/2&quot;)
videoDownloader.getVideo(url: &quot;www.youtube.com/video/3&quot;)
videoDownloader.getVideo(url: &quot;www.youtube.com/video/2&quot;)
videoDownloader.getVideo(url: &quot;www.youtube.com/video/3&quot;)
</code></pre><p>Since the proxy object and the wrapped object implement the same interface, any class using the proxy will continue to think it&apos;s interacting with the wrapped object directly while the proxy is able to orchestrate everything &quot;behind the scenes&quot;.</p><h2 id="granting-conditional-access">Granting Conditional Access</h2><p>Since the proxy pattern allows us to &quot;intercept&quot; the request before it makes it to the original object, it allows us to grant conditional access to the wrapped resource.</p><h3 id="ex-1-sensitive-operations">Ex. 1: Sensitive Operations</h3><pre><code class="language-swift">protocol ExampleProtocol {
    func performDesctructiveAction()
}

struct DatabaseService: ExampleProtocol {
    func performDestructiveAction() {
        // Delete all tables &#x1F608;
    }
}

struct DatabaseServiceProxy: ExampleProtocol {
    let isAdmin: Bool
    private let service = DatabaseService()
	
    func performDestructiveAction() {
        guard isAdmin else { return }
        service.performDestructiveAction()
    }
}</code></pre><h3 id="ex-2-on-demand-initialization">Ex. 2: On-Demand Initialization</h3><p>If we imagine for a moment that we didn&apos;t have access to the <code>lazy</code> keyword in Swift, we can use proxies to implement the same behavior:</p><figure class="kg-card kg-code-card"><pre><code class="language-swift">protocol SuperIntenseOperation {
    func performCalculation()
}

class DefaultSuperIntenseOperation: SuperIntenseOperation {
    func performCalculation() {
        // Starting long-runnning operation
    }
}

class LazyDefaultSuperIntenseOperation: SuperIntenseOperation {
    private var operation: DefaultSuperIntenseOperation?

    func performCalculation() {
        if operation == nil {
            operation = DefaultSuperIntenseOperation()
        }

        operation?.performCalculation()
    }
}</code></pre><figcaption>The call site can now use either <code>LazyDefaultSuperIntenseOperation</code> or <code>DefaultSuperIntenseOperation</code> interchangeably.</figcaption></figure><h3 id="ex-3-parental-controls">Ex. 3: Parental Controls</h3><p>Lastly, we could use the proxy pattern and its capacity to restrict access to a resource to implement parental controls on a web browser.</p><pre><code class="language-swift">protocol WebBrowser {
    func goToSite(url: String)
}

struct DefaultWebBrowser: WebBrowser {
    func goToSite(url: String) {
        // Navigate to url
    }
}

struct ParentalControlWebBrowser: WebBrowser {
    let blockedSites = [
        &quot;www.youtube.com&quot;,
        &quot;www.twitter.com&quot;,
        &quot;www.facebook.com&quot;
    ]

    private let browser = DefaultWebBrowser()

    func goToSite(url: String) {
        guard !blockedSites.contains(url) else {
            print(&quot;You are not allowed to visit this site. Find cooler parents.&quot;)
            return
        }

        browser.goToSite(url: url)
    }
}</code></pre><hr><h2 id="adapter-vs-proxy-vs-decorator">Adapter vs Proxy vs Decorator</h2><p>Before we wrap up, the proxy pattern tends to get confused with other structural design patterns, so let&apos;s take a moment to make sure we understand the differences:</p><ul><li><em>Adapter</em>: provides a <strong>different</strong> interface to the wrapped object.</li><li><em>Proxy: </em>provides the <strong>same</strong> interface to the wrapped object.</li><li><em>Decorator</em>: provides an <strong>extended</strong> interface to the wrapped object. It focuses on adding responsibilities whereas a proxy focus on controlling access to an object.</li></ul><hr><figure class="kg-card kg-image-card kg-card-hascaption"><a href="https://links.swapstack.co/aml"><img src="https://digitalbunker.dev/content/images/2022/10/Group-11--2-.png" class="kg-image" alt loading="lazy" width="875" height="234" srcset="https://digitalbunker.dev/content/images/size/w600/2022/10/Group-11--2-.png 600w, https://digitalbunker.dev/content/images/2022/10/Group-11--2-.png 875w" sizes="(min-width: 720px) 720px"></a><figcaption>A quick thank you to &quot;The Sample&quot; for sponsoring this issue of Indie Watch!</figcaption></figure><p>Each morning, <a href="https://links.swapstack.co/aml">The Sample</a> sends you one article from a random blog or newsletter that matches up with your interests. When you get one you like, you can subscribe to the writer with just a click.</p><hr><p>Hopefully, this short article has helped demonstrate the proxy design pattern and some of the typical use cases. If you have any article requests, feel free to <a href="https://digitalbunker.dev/">send me a message</a>.</p><p>If you&apos;re interested in more articles about iOS Development &amp; Swift, check out my <a href="https://www.youtube.com/c/AryamanSharda">YouTube channel</a> or <a href="https://twitter.com/aryamansharda">follow me on Twitter</a>.</p><hr><p>If you&apos;re an indie iOS developer, make sure to check out my newsletter! Each issue features a new indie developer, so feel free to <a href="https://indie.watch/submit">submit</a> your iOS apps. </p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="http://indie.watch/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Indie Watch</div><div class="kg-bookmark-description">Indie Watch is an exclusive weekly hand-curated newsletter showcasing the best iOS, macOS, watchOS, and tvOS apps from developers worldwide.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://indie.watch/content/images/size/w256h256/2022/05/Favicon_v2.png" alt><span class="kg-bookmark-author">Indie Watch</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://indie.watch/content/images/2022/05/Twitter---Facebook-9.png" alt></div></a></figure><hr><h3 id="do-you-have-an-ios-interview-coming-up">Do you have an iOS Interview coming up? </h3><p>Check out my book <a href="https://aryamansharda.gumroad.com/l/tcvck">Ace The iOS Interview</a>!</p><!--kg-card-begin: html--><script src="https://gumroad.com/js/gumroad-embed.js"></script>
<div class="gumroad-product-embed" data-outbound-embed="true"><a href="https://aryamansharda.gumroad.com/l/tcvck">Loading...</a></div><!--kg-card-end: html-->]]></content:encoded></item></channel></rss>