<?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:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Aryaman Sharda on Medium]]></title>
        <description><![CDATA[Stories by Aryaman Sharda on Medium]]></description>
        <link>https://medium.com/@aryamansharda?source=rss-174dbe86cc41------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*ufUXji7MJ-N2jSch7vaFWw.png</url>
            <title>Stories by Aryaman Sharda on Medium</title>
            <link>https://medium.com/@aryamansharda?source=rss-174dbe86cc41------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 22:53:35 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@aryamansharda/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[How to Auto-Reload Pages in Safari (Mac, iPhone, and iPad)]]></title>
            <link>https://aryamansharda.medium.com/how-to-auto-reload-pages-in-safari-mac-iphone-and-ipad-2abae480c242?source=rss-174dbe86cc41------2</link>
            <guid isPermaLink="false">https://medium.com/p/2abae480c242</guid>
            <category><![CDATA[productivity]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[browser-extension]]></category>
            <category><![CDATA[safari]]></category>
            <dc:creator><![CDATA[Aryaman Sharda]]></dc:creator>
            <pubDate>Fri, 27 Feb 2026 01:19:24 GMT</pubDate>
            <atom:updated>2026-03-13T18:56:10.033Z</atom:updated>
            <content:encoded><![CDATA[<p>Safari doesn’t have a built-in auto refresh feature, which is surprising given how often you end up hitting Command + R over and over again.</p><ul><li>Watching stock or crypto price dashboards</li><li>Testing local development changes</li><li>Waiting on sneaker or ticket drops</li><li>Refreshing sports scores during a game</li></ul><p>Eventually you ask yourself why you’re manually doing this.</p><p>Since Safari doesn’t offer a native way to reload a page every X seconds, automatic refresh on macOS, iOS, or iPadOS requires a Safari extension — and this post walks through how to set that up and get pages auto-reloading properly.</p><h3>The Cleanest Way to Auto-Reload Safari Pages</h3><p>I built a small Safari extension called <strong>Auto Reloader — Page Refresh</strong> that does exactly this.</p><p><a href="https://apps.apple.com/us/app/auto-reloader-page-refresher/id6446180574">Auto Reloader: Page Refresher App - App Store</a></p><p>It works across:</p><ul><li>macOS</li><li>iOS</li><li>iPadOS</li></ul><p>Same extension. Same behavior. Just installed once per device.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*YjNcrkWQR_Fj9UVa.png" /></figure><h3>How to Auto-Reload a Page in Safari</h3><ol><li>Install the app from the App Store.</li><li>Open Safari.</li><li>Go to Settings → Extensions and enable <a href="https://apps.apple.com/us/app/auto-reloader-page-refresh/id6446180574?ref=digitalbunker.dev">Auto Reloader</a>.</li><li>Click the extension in the toolbar.</li><li>Set a refresh interval.</li></ol><p>That’s it.</p><p>The current tab will reload automatically at your chosen interval.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8TCw2Ep_d0GtQxTWm4vOmA.png" /><figcaption>Auto Reloader running — hands-free Safari refresh.</figcaption></figure><h3>Who Actually Needs Safari Auto Refresh?</h3><p>This isn’t a novelty feature. It’s for moments where you’re stuck manually refreshing the same page over and over.</p><h3>Traders Watching Live Dashboards</h3><p>Some market pages don’t push updates in real time.<br>Order books lag. Portfolio pages cache. Charts stall.</p><p>So what do you do? You sit there hitting refresh.</p><p><a href="https://apps.apple.com/us/app/auto-reloader-page-refresh/id6446180574?ref=digitalbunker.dev">Auto Reload</a> keeps the page current without you babysitting it.</p><h3>Developers Testing Local Changes</h3><p>If you’re working on:</p><ul><li>localhost</li><li>staging builds</li><li>CSS/layout tweaks</li><li>dashboards that poll instead of push</li></ul><h3>Sneaker and Ticket Drop Monitoring</h3><p>Limited drops usually come down to timing.</p><p>If a product page does not automatically update inventory or release status, refreshing every few seconds can matter.</p><p>Automating that refresh removes one more manual step.</p><h3>Sports Score Refresh During Games</h3><p>Some sports sites do not live update without interaction. If you’re watching a score page during a game, automatic refresh keeps it current without touching the screen.</p><h3>Why Use Safari Instead of Switching Browsers?</h3><p>You could install a Chrome extension, but if you prefer Safari for:</p><ul><li>Native performance</li><li>Lower battery usage</li><li>iCloud tab sync</li><li>Apple ecosystem integration</li></ul><p>It makes more sense to add auto refresh directly to Safari rather than switching browsers just for one feature.</p><h3>Common Questions</h3><h3>Can I set different refresh intervals?</h3><p>Yes. You can choose a custom interval for the current tab. For example, you might refresh every 5 seconds for a live dashboard and every 60 seconds for something less time-sensitive.</p><h3>Does it work per tab?</h3><p>Yes. The refresh setting applies to the specific tab where you enable it. Other tabs remain unaffected unless you turn it on there as well.</p><h3>Does it stop when I close the tab?</h3><p>Yes. Once the tab is closed, refresh stops automatically.</p><h3>Is my data tracked?</h3><p>No. The extension does not require accounts, logins, or analytics. It runs locally within Safari.</p><h3>Does it work on any website?</h3><p>It works on standard web pages loaded inside Safari. Most dashboards, storefronts, news pages, sports pages, and development environments work as expected.</p><h3>Will this drain battery?</h3><p>Because it runs as a lightweight Safari extension and only reloads at the interval you choose, usage depends on how aggressive your refresh timing is. Longer intervals naturally use less battery and network activity.</p><p>If you find yourself refreshing the same page more than a few times in a row, that’s usually a sign.</p><p>Safari won’t automate it for you.<br><a href="https://apps.apple.com/us/app/auto-reloader-page-refresh/id6446180574?ref=digitalbunker.dev">Auto Reloader</a> will.</p><p>Set the interval once. Let it run. Move on.</p><p>👉 <a href="https://apps.apple.com/us/app/auto-reloader-page-refresh/id6446180574?ref=digitalbunker.dev">Download Auto Reloader on the App Store</a></p><p><em>Originally published at </em><a href="https://digitalbunker.dev/how-to-auto-reload-pages-in-safari-mac-iphone-and-ipad/"><em>https://digitalbunker.dev</em></a><em> on February 27, 2026.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2abae480c242" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[SimTag: Context for your iOS Simulators]]></title>
            <link>https://aryamansharda.medium.com/simtag-context-for-your-ios-simulators-4971fb1a7be4?source=rss-174dbe86cc41------2</link>
            <guid isPermaLink="false">https://medium.com/p/4971fb1a7be4</guid>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[apple]]></category>
            <category><![CDATA[xcode]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Aryaman Sharda]]></dc:creator>
            <pubDate>Wed, 11 Feb 2026 05:09:36 GMT</pubDate>
            <atom:updated>2026-02-16T22:42:29.342Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ct5buuRFFAmTmfuJeiyWLg.png" /></figure><h3>See which branch your simulator is actually running.</h3><p>Even before AI coding, I’d often have multiple copies of the same project open-using git worktrees or separate clones-to work on different branches in parallel.</p><p>Now, with multiple Claude Code sessions running at once, each doing work on a different branch, that setup is even more common.</p><p>The result is multiple simulators running at the same time, all looking identical, with no clear way to tell which branch any of them is actually running. <a href="https://aryamansharda.gumroad.com/l/sim-tag?ref=digitalbunker.dev">SimTag</a> fixes that.</p><h3>What SimTag Does</h3><p><a href="https://aryamansharda.gumroad.com/l/sim-tag?ref=digitalbunker.dev">SimTag</a> adds a small, unobtrusive overlay to each iOS Simulator window showing the branch that build came from.</p><p>That’s it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*TVH2C613kA-Af11M.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/612/0*cNYhB-fCO0TLuWw_.png" /></figure><p>When you glance at a simulator, you immediately know what you’re looking at. No more wondering:</p><ul><li>Did I rebuild after switching branches?</li><li>Which Simulator has the auth changes?</li><li>Am I debugging my own work or a coworker’s branch?</li></ul><p>If you do any kind of parallel development — worktrees, PR reviews, or AI-assisted coding — this removes a constant source of confusion.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/692/0*Vy3uh9zCqYLnEGtR.png" /></figure><p><a href="https://aryamansharda.gumroad.com/l/sim-tag?ref=digitalbunker.dev">SimTag</a> is especially useful for power users running parallel workflows. If you use git worktrees, multiple clones, or have several AI coding sessions (e.g. Claude, Codex) building to different simulators, you’ll always know which Simulator is running which branch.</p><p>But even if your workflow is simpler, <a href="https://aryamansharda.gumroad.com/l/sim-tag?ref=digitalbunker.dev">SimTag</a> still helps. A quick glance confirms the Simulator matches the branch you think you’re on. The “Pending Build” indicator catches “forgot to rebuild” moments by warning you when commits exist since the last build. During PR reviews, it acts as a sanity check-you’ll know for sure you’re testing your colleague’s code, not your own.</p><p>The overlay is unobtrusive and easy to ignore-until you need it.</p><h3>Features</h3><p><strong>Branch overlay</strong><br>See the git branch for every simulator window at a glance.</p><p><strong>Pending build indicator</strong><br><a href="https://aryamansharda.gumroad.com/l/sim-tag?ref=digitalbunker.dev">SimTag</a> detects when you’ve made commits since the last build. A small warning dot tells you the running app might be stale-no more debugging code that isn’t even in the build.</p><p><strong>Custom labels</strong><br>Add your own text to the overlay. Useful for marking simulators as “PR Review”, “Testing”, or whatever helps you stay oriented.</p><h3>Details</h3><ul><li>macOS 13+</li><li>Requires Screen Recording permission (used only to track window positions-not to record anything; setup is guided on first launch)</li><li>Runs in the menu bar</li><li>Optional launch at login</li><li>Overlay position is configurable (any corner, top/bottom center)</li></ul><p>I use <a href="https://aryamansharda.gumroad.com/l/sim-tag?ref=digitalbunker.dev">SimTag</a> every day now. It’s one of those tools you don’t think about-until it’s missing.</p><p>Download:</p><p><a href="https://aryamansharda.gumroad.com/l/sim-tag?ref=digitalbunker.dev">https://aryamansharda.gumroad.com/l/sim-tag</a></p><h3>Getting Started</h3><ol><li>Download <a href="https://aryamansharda.gumroad.com/l/sim-tag?ref=digitalbunker.dev">SimTag</a></li><li>Move the application to the Applications folder.</li><li>Launch and allow Screen &amp; System Audio Recording permissions (System Settings → Privacy &amp; Security → Screen &amp; System Audio Recording).</li><li>Grant file access permissions when prompted.</li></ol><h3>FAQ</h3><p><strong>Multiple Xcode projects open?</strong><br>SimTag figures out which project produced each specific simulator build.</p><p><strong>React Native / Flutter?</strong><br>Works fine. SimTag detects the git branch of whatever Xcode project built the app.</p><p><strong>Git worktrees?</strong><br>Fully supported. Each worktree shows its own branch correctly.</p><p><em>Originally published at </em><a href="https://digitalbunker.dev/simtag-context-for-your-ios-simulators/"><em>https://digitalbunker.dev</em></a><em> on February 11, 2026.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4971fb1a7be4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Chaikin’s Algorithm In SwiftUI]]></title>
            <link>https://aryamansharda.medium.com/chaikins-algorithm-in-swiftui-acdc6be5c26b?source=rss-174dbe86cc41------2</link>
            <guid isPermaLink="false">https://medium.com/p/acdc6be5c26b</guid>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[curves]]></category>
            <category><![CDATA[computer-graphics]]></category>
            <category><![CDATA[bezier-curves]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Aryaman Sharda]]></dc:creator>
            <pubDate>Thu, 20 Jun 2024 16:58:27 GMT</pubDate>
            <atom:updated>2024-06-26T04:10:13.046Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*xRAkY2qfsFIdrv39.png" /></figure><p>Many of you are probably familiar with Bézier curves and how they create smooth paths and designs, but did you know there’s a simpler way to a similar results?</p><p>I came across <a href="https://www.amygoodchild.com/blog/cursive-handwriting-in-javascript?ref=digitalbunker.dev">this blog post</a> yesterday that introduced me to <a href="https://www.cs.unc.edu/~dm/UNC/COMP258/LECTURES/Chaikins-Algorithm.pdf?ref=digitalbunker.dev">Chaikin’s Algorithm</a>. After spending an afternoon playing around with it, I knew I had to write about it.</p><h3>What is Chaikin’s Algorithm?</h3><p>Chaikin’s Algorithm is a simple method to smooth out a rough polygonal line into a nice, smooth curve. It works by repeatedly adding new points along each line segment, at 25% and 75% of the way between the original points, and then connecting these new points. Every time you do this, the line gets smoother and smoother.</p><p>Let’s apply the algorithm to this starting set of points:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/573/0*NIctoHXTsi8NinQ0.png" /></figure><p>Next, we’ll add new points at 25% and 75% of the way along each line segment. We’ll look at how we calculate these points in a minute, for now we’ll just estimate.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/573/0*dGSciR_-BtKqSwjH.png" /></figure><p>Now, let’s combine <em>just</em> the new points together:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/573/0*3eBx4xHGM-ANm7GJ.png" /></figure><p>You’ll see that by effectively cutting off the corners, we’ve achieved a more curved appearance.</p><p>Let’s do it one more time.</p><p>We’ll start again by adding new points along each of the new line segments:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/460/0*eFYryW8oNNb8kJyO.png" /></figure><p>And then connect them:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/460/0*d2KCGiAsejpm6ZQY.png" /></figure><p>We can see that in just 2 iterations we’ve transformed our initial jagged line into a much more pleasant curve.</p><h3>Calculating Control Points</h3><p>To calculate the control points at 25% and 75% along a line segment, we need to use linear interpolation (a.k.a. lerp). It sounds fancy, but it’s simply a way of finding a point that is some percentage of the way between two other points.</p><p>If you’re already familiar with this concept, feel free to skip to the next section.</p><p>Or, if you’re interested in exploring the relationship between different interpolation approaches and how they relate to animations in iOS (UIKit &amp; SwiftUI), check out my earlier blog post:</p><p><a href="https://digitalbunker.dev/exploring-animation-curves/">https://digitalbunker.dev/exploring-animation-curves/</a></p><p>We’ll start by looking at the linear interpolation formula and then we’ll look at an example:</p><p>L(t) = (1 − t) ⋅ P0​ + t ⋅ P1​</p><ul><li>L(t) is the interpolated point.</li><li>t is the interpolation parameter which ranges from 0 to 1.</li><li>When t = 0, L(t) = P0.</li><li>When t = 1, L(t) = P1.</li><li>P0 is the starting point.</li><li>P1 is the ending point.</li></ul><p>So, if we wanted to figure out a point that was 25% of the way between P0 and P1, we&#39;d just have to evaluate this expression:</p><p>L(t) = (1 - 0.25) * P0 + 0.25 * P1</p><p>L(t) = (0.75 * P0) + (0.25 * P1)</p><p>In practice, we’ll use this formula to calculate a new control point’s X and Y coordinates individually.</p><h3>Implementing Chaikin’s Algorithm</h3><p>Now that we know how the algorithm and linear interpolation work, let’s create an interactive version of it in SwiftUI.</p><p>Here’s the main algorithm’s implementation:</p><pre>func chaikinAlgorithm(points: [CGPoint], iterations: Int) -&gt; [CGPoint] {<br>    // We want to preserve the starting and ending points, so let&#39;s save them.<br>    guard points.count &gt; 2, let startingPoint = points.first, let endingPoint = points.last else { return points }<br><br>    var currentPoints = points<br><br>    // We know the more iterations we do, the smoother the curve.<br>    for _ in 0..&lt;iterations {<br>        var newPoints: [CGPoint] = [startingPoint]<br><br>        for i in 0..&lt;currentPoints.count - 1 {<br>            // Gets adjacent points<br>            let p0 = currentPoints[i]<br>            let p1 = currentPoints[i + 1]<br><br>            // Calculates the position of the new control points<br>            // via linear interpolation<br>            let q = CGPoint(x: 0.75 * p0.x + 0.25 * p1.x, y: 0.75 * p0.y + 0.25 * p1.y)<br>            let r = CGPoint(x: 0.25 * p0.x + 0.75 * p1.x, y: 0.25 * p0.y + 0.75 * p1.y)<br><br>            newPoints.append(q)<br>            newPoints.append(r)<br>        }<br><br>        newPoints.append(endingPoint)<br>        currentPoints = newPoints<br>    }<br><br>    return currentPoints<br>}   var currentPoints = points</pre><p>To render the points and curve in SwiftUI, we can use Path:</p><pre>var body: some View {<br>    ZStack {<br>        // Draw the points where the user taps<br>        ForEach(points, id: \.self) { point in<br>            Circle()<br>                .fill(Color.blue)<br>                .frame(width: 10, height: 10)<br>                .position(point)<br>        }<br><br>        // Draw the smoothed curve<br>        Path { path in<br>            guard !smoothedPoints.isEmpty else { return }<br>            path.move(to: smoothedPoints.first!)<br>            for point in smoothedPoints.dropFirst() {<br>                path.addLine(to: point)<br>            }<br>        }<br>        .stroke(Color.red, lineWidth: 2)<br>    }<br>    .background(Color.white)<br>    .onTapGesture(coordinateSpace: .global) { location in<br>        points.append(location)<br>        <br>        // Perform Chaikin&#39;s Algo every time we add a new point<br>        smoothedPoints = chaikinAlgorithm(points: points, iterations: 3)<br>    }<br>    .edgesIgnoringSafeArea(.all)<br>}</pre><p>Putting everything together, we can now define any line and generate a curve that matches it’s shape.</p><h3>1 Iteration</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ct2uOzdUbXLb6dvahwKiHQ.gif" /></figure><h3>3 Iterations</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8y9WA0kCllETuY1FFe7koA.gif" /></figure><h3>5 Iterations</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VCYQREMJwfH7UlIj-oZ2SQ.gif" /></figure><p>You can find the <a href="https://github.com/aryamansharda/Chaikin?ref=digitalbunker.dev">full source code here</a>.</p><h3>Chaikin’s Algorithm vs. Bézier Curves</h3><p>Both Chaikin’s Algorithm and Bézier curves are essential tools for creating smooth curves from a set of control points. They are widely used in graphic design, animation, and UI/UX design to generate appealing shapes and paths. One key similarity is that both methods can be applied iteratively to increase the smoothness of the resulting curve. With Chaikin’s Algorithm, each iteration adds new points and refines the curve, while Bézier curves can be subdivided further to achieve a similar effect.</p><p>However, there are significant differences between the two. Bézier curves are mathematically more complex, requiring polynomial equations and manipulation of control points to define the curve. In contrast, Chaikin’s Algorithm is straightforward, using simple linear interpolation between points. Bézier curves are defined by a specific set of control points that directly influence the shape of the curve. Moving a control point will change the entire curve’s shape. Chaikin’s Algorithm, however, generates new points along the line segments, gradually smoothing the path without direct manipulation of the original control points.</p><p>In terms of applications, Bézier curves are widely used in vector graphic design, font design, and path animation due to their precision and control. On the other hand, Chaikin’s Algorithm is more commonly used for quick and easy curve smoothing, particularly in applications where simplicity and efficiency are more critical than precise control over the curve’s shape. In summary, while both Chaikin’s Algorithm and Bézier curves are powerful tools for creating smooth curves, they cater to different needs and levels of complexity. Chaikin’s Algorithm is ideal for quick, easy smoothing, while Bézier curves offer greater control and precision for more complex designs.</p><p>If you’re interested in more articles about iOS Development &amp; Swift, check out my <a href="https://www.youtube.com/c/AryamanSharda?ref=digitalbunker.dev">YouTube channel</a> or <a href="https://twitter.com/aryamansharda?ref=digitalbunker.dev">follow me on Twitter</a>.</p><p>And, if you’re an indie iOS developer, make sure to <a href="https://indie.watch/?ref=digitalbunker.dev">check out my newsletter</a>! Each issue features a new indie developer, so feel free to <a href="https://indie.watch/submit?ref=digitalbunker.dev">submit</a> your iOS apps.</p><p><a href="https://getuniversal.link/?ref=digitalbunker.dev">Universal Link Checker &amp; Apple App Site Association Validator</a></p><p><em>Originally published at </em><a href="https://digitalbunker.dev/chaikins-algorithm-in-swiftui/"><em>https://digitalbunker.dev</em></a><em> on June 20, 2024.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=acdc6be5c26b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Launching getuniversal.link ]]></title>
            <link>https://aryamansharda.medium.com/launching-getuniversal-link-1cca71fdc875?source=rss-174dbe86cc41------2</link>
            <guid isPermaLink="false">https://medium.com/p/1cca71fdc875</guid>
            <category><![CDATA[deep-linking]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[universal-links]]></category>
            <category><![CDATA[developer-tools]]></category>
            <category><![CDATA[ios-development]]></category>
            <dc:creator><![CDATA[Aryaman Sharda]]></dc:creator>
            <pubDate>Wed, 08 May 2024 21:27:27 GMT</pubDate>
            <atom:updated>2024-10-27T01:31:24.181Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*mNznlaE48BJvvw4j.png" /></figure><p>After exploring universal links and the Apple App Site Association file in a <a href="https://digitalbunker.dev/apple-app-site-association/">previous blog post</a>, it was clear that developers needed a straightforward solution for testing an app’s universal links.</p><p><a href="https://getuniversal.link/">Easy AASA Management</a></p><h3>What is getuniversal.link?</h3><p>It’s a developer utility that simplifies the process of testing universal links-those special URLs that link to specific content within your app.</p><p>With just the path to your domain, <a href="https://getuniversal.link/?ref=digitalbunker.dev">getuniversal.link</a> allows you to test all the universal links your app supports. Whether it’s a link that opens your app or redirects to your website, ensuring these work correctly is crucial for providing a seamless user experience.</p><h3>How Does It Work?</h3><p>Using <a href="https://getuniversal.link/?ref=digitalbunker.dev">getuniversal.link</a> is simple!</p><p>Start by entering the path to your domain where your AASA (Apple App Site Association) file is hosted (e.g. youtube.com, yelp.com).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*vrDRqRRS-OKd2ljj.png" /></figure><p>The platform will fetch the latest copy of your AASA file from Apple’s servers and will give you an easy-to-use dashboard to let you test them. You can see exactly how each link behaves, verify the setup, and identify regressions.</p><p>You can either navigate to those URLs directly or grab the Terminal command to open the link in the booted Simulator.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*vINVg0OxTCZ2J7Xh.png" /></figure><p>No matter how many apps your AASA file supports, you’ll be be able to test them all.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*N7rdUP6kvOFHYIdW.png" /></figure><p>Even if you use the component-based AASA file, that’s supported too!</p><p>You can easily create custom links based off the specified Universal Link pattern for easy testing.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*WgmV3BkTwye66fsK.png" /></figure><p>These links are stored in local storage, so you can always come back to them in the future.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/788/0*RZhxA9KrV4Jg3LAf.png" /></figure><p>The platform will also tell you when Apple will also let you know when Apple’s cached version of your AASA file will refresh.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*WlNSeguEym0O7oNK.png" /></figure><blockquote><em>We’re also planning on adding AASA file monitoring, so you’ll be informed whenever there’s a syntax error in your configuration or occasions where the file is unreachable.</em></blockquote><blockquote><em>For other feature requests, let me know at </em><a href="/service/mailto:aryaman@digitalbunker.dev"><em>aryaman@digitalbunker.dev</em></a><em>.</em></blockquote><p><a href="https://getuniversal.link/?ref=digitalbunker.dev">getuniversal.link</a> simplifies testing by allowing you to save a static link to the configured dashboard directly on your home screen, whether on the Simulator or a physical devices (e.g. <a href="https://getuniversal.link/turo.com?ref=digitalbunker.dev">getuniversal.link/turo.com</a>).</p><p>This enables easy testing of universal link behavior and lets you share the link with your team for convenient access to the dashboard.</p><h3>Why Choose getuniversal.link?</h3><ul><li><strong>Efficiency:</strong> Save time with automated fetch and test features that cut down the manual steps required in testing each link.</li><li><strong>Accuracy:</strong> With precise testing tools, you can ensure that all your universal links work exactly as intended.</li><li><strong>Convenience:</strong> The platform’s integration with the Terminal and iOS ecosystem makes it easy to test the links via iOS Simulator or device.</li></ul><h3>Ideal for Developers and Teams</h3><p>Whether you’re a solo developer or part of a larger team, <a href="https://getuniversal.link/?ref=digitalbunker.dev">getuniversal.link</a> is designed to support your development process at every stage. <strong>Did I mention it’s all free?</strong></p><p>Ready to take your app’s universal link testing to the next level? Streamline your testing process today.</p><p><a href="https://getuniversal.link/">https://getuniversal.link/</a></p><p><em>Originally published at </em><a href="https://digitalbunker.dev/launching-getuniversal-link/"><em>https://digitalbunker.dev</em></a><em> on May 8, 2024.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1cca71fdc875" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Converting Codable Models To CSV]]></title>
            <link>https://medium.com/hackernoon/converting-codable-models-to-csv-1232deab984f?source=rss-174dbe86cc41------2</link>
            <guid isPermaLink="false">https://medium.com/p/1232deab984f</guid>
            <category><![CDATA[codable]]></category>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[swift-programming]]></category>
            <dc:creator><![CDATA[Aryaman Sharda]]></dc:creator>
            <pubDate>Mon, 22 Apr 2024 03:14:10 GMT</pubDate>
            <atom:updated>2024-06-26T04:02:35.466Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*fFXMO09kWKrxj4gv" /></figure><p>Recently, while working on a side project, I needed to convert an array of Codable models into a CSV file. While there are several 3rd-party solutions available, I was reluctant to introduce yet another dependency into my project for what seemed like a fairly straightforward task. Instead, I’d like to share an implementation you can use to perform this task.</p><p>Both of these implementations work well when you can make some assumptions about the variability of your data. If you have highly dynamic and variable input data, then by all means use a more robust 3rd-party solution.</p><p>This is all about creating a simple solution for simple use cases.</p><h3>Basic Implementation</h3><p>Let’s start by creating a model and adding some fake data.</p><pre>struct Employee: Codable {<br>    var id: Int<br>    var name: String<br>    var department: String<br>}<br><br>let employees = [<br>    Employee(id: 1, name: &quot;Alice&quot;, department: &quot;HR&quot;),<br>    Employee(id: 2, name: &quot;Bob&quot;, department: &quot;IT&quot;),<br>    Employee(id: 3, name: &quot;Charlie&quot;, department: &quot;Finance&quot;)<br>]</pre><p>Our first step in creating the CSV file is to set up the header row that specifies and outlines the columns.</p><p>To do this, we can use the Mirror API in Swift, which gives us a way to list out all the properties of an object as key and value pairs. If you want to learn more about the Mirror API, I recommend checking out <a href="https://www.avanderlee.com/swift/reflection-how-mirror-works/?ref=digitalbunker.dev">this article</a>.</p><blockquote><em>According to Apple, the Mirror API “provides a representation of the substructure and display style of an instance of any type.”</em></blockquote><p>We’ll use the first item in our input as the template for the header row:</p><pre>func convertToCSV&lt;T: Codable&gt;(_ items: [T]) -&gt; String {<br>    var csvString = &quot;&quot;<br><br>    // Reflection to get property names for the header row<br>    if let firstItem = items.first {<br>        let mirror = Mirror(reflecting: firstItem)<br><br>        // The label is the name of the property in the object<br>        // being reflected (i.e. id, name, department)<br>        let headers = mirror.children.compactMap { $0.label }<br>        csvString += headers.joined(separator: &quot;,&quot;) + &quot;\n&quot;<br>    }<br>}</pre><p>Now, to build the rows, we’ll use Mirror again, but we’ll extract the value this time. We&#39;ll also include some basic escaping and validation logic to ensure everything runs smoothly.</p><pre>func convertToCSV&lt;T: Codable&gt;(_ items: [T]) -&gt; String {<br>    var csvString = &quot;&quot;<br><br>    // Reflection to get property names for the header row<br>    if let firstItem = items.first {<br>        let mirror = Mirror(reflecting: firstItem)<br>        let headers = mirror.children.compactMap { $0.label }<br>        csvString += headers.joined(separator: &quot;,&quot;) + &quot;\n&quot;<br>    }<br><br>    // Convert each instance to a CSV row<br>    for item in items {<br>        let mirror = Mirror(reflecting: item)<br>        let values = mirror.children.map { child -&gt; String in<br>            let value = &quot;\(child.value)&quot;<br><br>            // Basic escaping; handles potential commas and quotes in values<br>            if value.contains(&quot;,&quot;) || value.contains(&quot;\&quot;&quot;) {<br>                return &quot;\&quot;\(value.replacingOccurrences(of: &quot;\&quot;&quot;, with: &quot;\&quot;\&quot;&quot;))\&quot;&quot;<br>            }<br>            return value<br>        }<br><br>        // This trailing newline is required for a valid .csv file<br>        csvString += values.joined(separator: &quot;,&quot;) + &quot;\n&quot;<br>    }<br><br>    return csvString<br>}<br><br>// Usage<br>// convertToCSV(employees)<br>//<br>// Output<br>// id,name,department<br>// 1,Alice,HR<br>// 2,Bob,IT<br>// 3,Charlie,Finance</pre><h3>Advanced Implementation</h3><p>Great! We have a working implementation, but there’s a few things I’d like to address:</p><ul><li>How can I make the column names capitalized? How can I create more readable column names?</li><li>How might we pre-format values, like dates, before exporting them?</li><li>Can we selectively choose which properties to include in the output file?</li></ul><p>Let’s start by creating a struct to encapsulate the types of customizations we want to make.</p><pre>struct CSVCustomization {<br>    var headerTransform: ((String) -&gt; String)?<br>    var valueTransform: ((String, Any) -&gt; String)?<br>    <br>    // Set of property names to exclude<br>    var excludedProperties: Set&lt;String&gt; = []  <br>}</pre><p>Next, we’ll pass that into our convertToCSV function:</p><pre>func convertToCSV&lt;T: Codable&gt;(<br>        _ items: [T],<br>        customization: CSVCustomization? = nil<br>    ) -&gt; String { ... }</pre><p>Now, when constructing the header, we’ll first verify that the current property isn’t listed under excludedProperties and then proceed to apply any necessary transformations using headerTransform:</p><pre>var csvString = &quot;&quot;<br><br>// Reflection to get property names for headers<br>if let firstItem = items.first {<br>    let mirror = Mirror(reflecting: firstItem)<br>    var headers = mirror.children.compactMap { $0.label }<br><br>    // Exclude headers<br>    if let excluded = customization?.excludedProperties {<br>        headers = headers.filter { !excluded.contains($0) }<br>    }<br><br>    // Apply header transformations if provided<br>    if let headerTransform = customization?.headerTransform {<br>        headers = headers.map(headerTransform)<br>    }<br><br>    csvString += headers.joined(separator: &quot;,&quot;) + &quot;\n&quot;<br>}</pre><p>We’ll make a similar adjustment to our row-building logic. Note the shift from using map to compactMap.</p><pre>// Convert each instance to a CSV row<br>for item in items {<br>    let mirror = Mirror(reflecting: item)<br>    let values = mirror.children.compactMap { child -&gt; String? in<br>        guard let label = child.label, !(customization?.excludedProperties.contains(label) ?? false) else {<br>            return nil<br>        }<br>        var value = &quot;\(child.value)&quot;<br><br>        // Apply value transformations if provided<br>        if let valueTransform = customization?.valueTransform {<br>            value = valueTransform(label, child.value)<br>        } else {<br>            // Handle potential commas and quotes in values<br>            if value.contains(&quot;,&quot;) || value.contains(&quot;\&quot;&quot;) {<br>                value = &quot;\&quot;\(value.replacingOccurrences(of: &quot;\&quot;&quot;, with: &quot;\&quot;\&quot;&quot;))\&quot;&quot;<br>            }<br>        }<br>        return value<br>    }<br>    csvString += values.joined(separator: &quot;,&quot;) + &quot;\n&quot;<br>}</pre><p>Here’s the final implementation with a tweaked Employee model:</p><pre>struct Employee: Codable {<br>    var id: Int<br>    var firstName: String<br>    var lastName: String<br>    var department: String<br>    var ssn: String<br>}<br><br>struct CSVCustomization {<br>    var headerTransform: ((String) -&gt; String)?<br>    var valueTransform: ((String, Any) -&gt; String)?<br><br>    // Set of property names to exclude<br>    var excludedProperties: Set&lt;String&gt; = []<br>}<br><br>func convertToCSV&lt;T: Codable&gt;(<br>    _ items: [T],<br>    customization: CSVCustomization? = nil<br>) -&gt; String {<br><br>    var csvString = &quot;&quot;<br><br>    // Reflection to get property names for headers<br>    if let firstItem = items.first {<br>        let mirror = Mirror(reflecting: firstItem)<br>        var headers = mirror.children.compactMap { $0.label }<br><br>        // Exclude headers<br>        if let excluded = customization?.excludedProperties {<br>            headers = headers.filter { !excluded.contains($0) }<br>        }<br><br>        // Apply header transformations if provided<br>        if let headerTransform = customization?.headerTransform {<br>            headers = headers.map(headerTransform)<br>        }<br><br>        csvString += headers.joined(separator: &quot;,&quot;) + &quot;\n&quot;<br>    }<br><br>    // Convert each instance to a CSV row<br>    for item in items {<br>        let mirror = Mirror(reflecting: item)<br>        let values = mirror.children.compactMap { child -&gt; String? in<br>            guard let label = child.label, !(customization?.excludedProperties.contains(label) ?? false) else {<br>                return nil<br>            }<br>            var value = &quot;\(child.value)&quot;<br><br>            // Apply value transformations if provided<br>            if let valueTransform = customization?.valueTransform {<br>                value = valueTransform(label, child.value)<br>            } else {<br>                // Handle potential commas and quotes in values<br>                if value.contains(&quot;,&quot;) || value.contains(&quot;\&quot;&quot;) {<br>                    value = &quot;\&quot;\(value.replacingOccurrences(of: &quot;\&quot;&quot;, with: &quot;\&quot;\&quot;&quot;))\&quot;&quot;<br>                }<br>            }<br>            return value<br>        }<br>        csvString += values.joined(separator: &quot;,&quot;) + &quot;\n&quot;<br>    }<br><br>    return csvString<br>}<br><br>// Usage<br>let employees = [<br>    Employee(id: 1, firstName: &quot;Brad&quot;, lastName: &quot;Pitt&quot;, department: &quot;HR&quot;, ssn: &quot;123-45-1234&quot;),<br>    Employee(id: 2, firstName: &quot;Bruce&quot;, lastName: &quot;Willis&quot;, department: &quot;IT&quot;, ssn: &quot;987-45-1234&quot;),<br>    Employee(id: 3, firstName: &quot;Tom&quot;, lastName: &quot;Cruise&quot;, department: &quot;Finance&quot;, ssn: &quot;123-46-1234&quot;),<br>    Employee(id: 4, firstName: &quot;Jackie&quot;, lastName: &quot;Chan&quot;, department: &quot;Legal&quot;, ssn: &quot;315-45-1234&quot;)<br>]<br><br>let customizations = CSVCustomization(<br>    headerTransform: {<br>        if $0 == &quot;firstName&quot; {<br>            return &quot;First Name&quot;<br>        } else if $0 == &quot;lastName&quot; {<br>            return &quot;Last Name&quot;<br>        }<br><br>        return $0.capitalized<br>    },<br>    valueTransform: nil,<br>    excludedProperties: [&quot;ssn&quot;]<br>)<br><br>convertToCSV(employees, customization: customizations)<br><br>// Output<br>// Id,First Name,Last Name,Department<br>// 1,Brad,Pitt,HR<br>// 2,Bruce,Willis,IT<br>// 3,Tom,Cruise,Finance<br>// 4,Jackie,Chan,Legal</pre><p>If you’re interested in more articles about iOS Development &amp; Swift, check out my <a href="https://www.youtube.com/c/AryamanSharda?ref=digitalbunker.dev">YouTube channel</a> or <a href="https://twitter.com/aryamansharda?ref=digitalbunker.dev">follow me on Twitter</a>.</p><p>And, if you’re an indie iOS developer, make sure to <a href="https://indie.watch/?ref=digitalbunker.dev">check out my newsletter</a>! Each issue features a new indie developer, so feel free to <a href="https://indie.watch/submit?ref=digitalbunker.dev">submit</a> your iOS apps.</p><p><em>Originally published at </em><a href="https://digitalbunker.dev/converting-codable-models-to-csv/"><em>https://digitalbunker.dev</em></a><em> on April 22, 2024.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1232deab984f" width="1" height="1" alt=""><hr><p><a href="https://medium.com/hackernoon/converting-codable-models-to-csv-1232deab984f">Converting Codable Models To CSV</a> was originally published in <a href="https://medium.com/hackernoon">HackerNoon.com</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Everything You Need To Know About The Apple App Site Association File]]></title>
            <link>https://aryamansharda.medium.com/everything-you-need-to-know-about-the-apple-app-site-association-file-22acac807eb6?source=rss-174dbe86cc41------2</link>
            <guid isPermaLink="false">https://medium.com/p/22acac807eb6</guid>
            <category><![CDATA[xcode]]></category>
            <category><![CDATA[deep-linking]]></category>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[swift-programming]]></category>
            <dc:creator><![CDATA[Aryaman Sharda]]></dc:creator>
            <pubDate>Sat, 13 Apr 2024 03:45:03 GMT</pubDate>
            <atom:updated>2024-05-05T12:17:05.635Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*2Vp21jy6TXAnbn_g" /><figcaption>Photo by <a href="https://unsplash.com/@davidmartinjr?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">David Martin</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></figcaption></figure><p>The <a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/AppSearch/UniversalLinks.html?ref=digitalbunker.dev">Apple App Site Association (AASA) file</a> enables iOS features like Universal Links, Shared Web Credentials, Handoff, and App Clips by establishing a secure and verified link between your iOS app(s) and a web domain.</p><p>If you’ve noticed that certain links on the mobile web versions of Instagram or YouTube keep you in the browser, while others redirect you into the app, this behavior is all being driven by the AASA file.</p><h3>Creating The AASA File</h3><p>The Apple App Site Association file is a configuration file that defines how URLs are handled and specifies whether they open in a web browser or link directly to content within an app.</p><p>Here’s a basic example:</p><pre>{<br>  &quot;applinks&quot;: {<br>    &quot;apps&quot;: [],<br>    &quot;details&quot;: [<br>      {<br>        &quot;appID&quot;: &quot;ABCDE12345.com.example.app&quot;,<br>        &quot;paths&quot;: [<br>          &quot;/&quot;,<br>          &quot;/about-us/&quot;,<br>          &quot;/products/*&quot;,<br>          &quot;/services/detail/?id=*&quot;,<br>          &quot;/news/article/*&quot;,<br>          &quot;/promo/*&quot;,<br>          &quot;NOT /private/&quot;,<br>          &quot;NOT /settings/*&quot;,<br>          &quot;*.html&quot;<br>        ]<br>      }<br>    ]<br>  },<br>  &quot;webcredentials&quot;: {<br>    &quot;apps&quot;: [&quot;ABCDE12345.com.example.app&quot;]<br>  },<br>  &quot;appclips&quot;: {<br>    &quot;apps&quot;: [&quot;ABCDE12345.com.example.app&quot;]<br>  }<br>}{ &quot;applinks&quot;: { &quot;apps&quot;: [], &quot;details&quot;: [ { &quot;appID&quot;: &quot;ABCDE12345.com.example.app&quot;, &quot;paths&quot;: [ &quot;/&quot;, &quot;/about-us/&quot;, &quot;/products/*&quot;, &quot;/services/detail/?id=*&quot;, &quot;/news/article/*&quot;, &quot;/promo/*&quot;, &quot;NOT /private/&quot;, &quot;NOT /settings/*&quot;, &quot;*.html&quot; ] } ] }, &quot;webcredentials&quot;: { &quot;apps&quot;: [&quot;ABCDE12345.com.example.app&quot;] }, &quot;appclips&quot;: { &quot;apps&quot;: [&quot;ABCDE12345.com.example.app&quot;] } }</pre><p>Attempting to visit any of these paths would route the user to the app:</p><pre>&quot;/&quot;,<br>&quot;/about-us/&quot;,<br>&quot;/products/*&quot;,<br>&quot;/services/detail/?id=*&quot;,<br>&quot;/news/article/*&quot;,<br>...</pre><p>And for paths prefixed with NOT, the system will not reroute the user, allowing the experience to continue in the browser:</p><pre>&quot;NOT /private/&quot;,<br>&quot;NOT /settings/*&quot;,<br>...</pre><h3>Deploying The AASA File</h3><p>When you’re ready to deploy your AASA file, you’ll need to host it at your website’s root or .well-known directory:</p><ul><li>/apple-app-site-association</li><li>/.well-known/apple-app-site-association</li></ul><blockquote><strong><em>Note</em></strong><em>: there should be no file extension on the AASA file.</em></blockquote><p>For the CDNs to successfully cache this file, it must be hosted on a domain that is available to all IP addresses and ranges, serves the file over HTTPS, does not redirect, and is not blocked by access policies.</p><p>You should also make sure your server serves the file with Content-Type: application/json and that the file doesn&#39;t exceed 128KB in size.</p><p>When you first host your AASA file, Apple’s CDNs are set up to retrieve it within 24 hours, meaning that they will request and cache a copy from your server within the first day of its publication.</p><p>This 24 hour window relates <em>only</em> to getting the file into Apple’s CDNs for the first time. Subsequent updates happen at a different interval which we’ll look at shortly.</p><h3>Testing &amp; Validation</h3><p>As your app evolves, you’ll need to revise the AASA file to incorporate universal links for new user flows.</p><p>Errors in the AASA files are common, so validation of both the behavior and syntax of this file become an essential part of the process. Any mistakes in this file, once acknowledged by Apple’s CDNs, can effect universal linking behavior for all users — new or old.</p><p>Fortunately, there are several ways of verifying the behavior of the AASA file before the changes make it to Apple.</p><h3>Bypassing The CDN</h3><p><a href="https://developer.apple.com/documentation/xcode/supporting-associated-domains?ref=digitalbunker.dev">Associated Domains</a> support an <a href="https://developer.apple.com/documentation/xcode/configuring-an-associated-domain?ref=digitalbunker.dev#Enable-alternate-mode-for-unreachable-servers">alternate mode</a> that allows developers to verify the behavior of the modified AASA file by bypassing the CDNs and retrieving the file from your server directly.</p><ol><li>Go to Signing &amp; Capabilities -&gt; Associated Domains</li><li>Add ?mode=developer to the end of the domain entry (e.g. applinks:yourdomain.com?mode=developer)</li></ol><p>To activate this mode in your Xcode project:</p><p>Now, when you build and run your app, it will retrieve the updated AASA file directly from your server.</p><blockquote><em>You can only use alternate modes during development and you must remove the query string from the Associated Domains before you submit your app to the App Store.</em></blockquote><h3>Verifying AASA File Configuration</h3><p>You can use the following tools to verify the syntax and configuration of your AASA file:</p><ul><li><a href="https://branch.io/resources/aasa-validator/?ref=digitalbunker.dev">AASA Validator | Branch</a></li><li><a href="https://stackoverflow.com/questions/74866698/how-do-i-access-apples-aasa-validator-app-search-api-validation-tool?ref=digitalbunker.dev">How do I access Apple&#39;s AASA validator (App Search API Validation Tool)?</a></li><li><a href="https://yurl.chayev.com/?ref=digitalbunker.dev">yURL: Universal Links / App Links Validator</a></li></ul><p>If I need to update the AASA file at work, I’ll start by making changes to the version in our staging environment, then use the links above to validate the configuration, and will then enable developer mode for local testing.</p><p>After confirming everything works as expected, I’ll bring those modifications over to the production environment. But now, the question is, how long will it take for all clients to receive the updated version of this file?</p><h3>Rolling Out Changes</h3><p>You can check if Apple’s CDNs have picked up the latest version of the file by visiting:</p><ul><li><a href="https://app-site-association.cdn-apple.com/a/v1/%7BYOUR_BUNDLE_ID_HERE%7D?ref=digitalbunker.dev">https://app-site-association.cdn-apple.com/a/v1/{YOUR_DOMAIN_HERE}</a></li><li><a href="https://app-site-association.cdn-apple.com/a/v1/facebook.com?ref=digitalbunker.dev">https://app-site-association.cdn-apple.com/a/v1/facebook.com</a></li></ul><p>If the CDN does in fact have the most recent version of the file, then any new app installations will also receive this latest version. For existing users, however, their devices will only check for an updated copy once per week.</p><blockquote><em>Reinstalls of the app will fetch the latest version from the CDN.</em></blockquote><p>In effect, the rollout period for any change to the AASA file is both the time it takes for the CDN to refresh its cache combined with this week long check-in period for existing users.</p><h3>Still seeing the old version?</h3><p>While you can’t clear the CDNs cache, you can at least verify that the AASA file will be correct when the cached version does update.</p><p>Adding any query parameter to the link above will force the CDNs to re-request the resource, so you’ll be able to verify that the future version of the file will be correct once the cache updates.</p><p><a href="https://app-site-association.cdn-apple.com/a/v1/%3Cdomain%3E?digitalbunker=&amp;ref=digitalbunker.dev">https://app-site-association.cdn-apple.com/a/v1/&lt;domain&gt;?digitalbunker</a></p><h3>When will it update?</h3><p>To better understand the remaining time before the cached version is updated, we can look at the Cache-Control field in the response headers from the CDN.</p><blockquote><em>The </em><em>Cache-Control header is used by web servers to dictate how, and for how long, both browser and intermediary caches (like CDNs) should cache the provided version of a file.</em></blockquote><p>Let’s use Facebook and Yelp as examples:</p><ul><li><a href="https://app-site-association.cdn-apple.com/a/v1/facebook.com">https://app-site-association.cdn-apple.com/a/v1/facebook.com</a></li><li><a href="https://app-site-association.cdn-apple.com/a/v1/yelp.com">https://app-site-association.cdn-apple.com/a/v1/yelp.com</a></li></ul><p>Now, we can determine when the cache will update by subtracting age from max-age:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/767/0*ZLBIqaQ5CZYv08VJ.png" /><figcaption>Max Age: 6 hours, cache refreshes in 17,760 seconds / ~5 hours.</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/741/0*pMykUtyBE8ld4IjK.png" /><figcaption>Max Age: 1 hour, cache refreshes in 3,112 seconds / ~50 min.</figcaption></figure><p>In playing around with this approach, it seems like the refresh rates are fairly variable with the shortest max-age being 3,600 seconds, or 1 hour, and the longest being 21,600 seconds, or 6 hours.</p><p>It also may be useful to know that Apple’s CDNs override the Cache-Control settings specified by the original website. For example, accessing the AASA file from Yelp directly has a max-age: 1200, but when retrieving it from Apple&#39;s CDNs, it&#39;s max-age: 3600.</p><p>By caching the file longer, Apple is able to minimize the frequency of requests to the original servers, reducing traffic, and load on both their network and the origin server.</p><p>Hope you enjoyed this article! If you did, <strong>please consider sharing</strong> 😊🙏.</p><p>If you’re interested in more articles about iOS Development &amp; Swift, check out my <a href="https://www.youtube.com/c/AryamanSharda?ref=digitalbunker.dev">YouTube channel</a> or <a href="https://twitter.com/aryamansharda?ref=digitalbunker.dev">follow me on Twitter</a>.</p><p>And, if you’re an indie iOS developer, make sure to <a href="https://indie.watch/?ref=digitalbunker.dev">check out my newsletter</a>! Each issue features a new indie developer, so feel free to <a href="https://indie.watch/submit?ref=digitalbunker.dev">submit</a> your iOS apps.</p><p><em>Originally published at </em><a href="https://digitalbunker.dev/apple-app-site-association/"><em>https://digitalbunker.dev</em></a><em> on April 13, 2024.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=22acac807eb6" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Recreating The DVD Screensaver In SwiftUI]]></title>
            <link>https://medium.com/hackernoon/recreating-the-dvd-screensaver-in-swiftui-6284f525ec2f?source=rss-174dbe86cc41------2</link>
            <guid isPermaLink="false">https://medium.com/p/6284f525ec2f</guid>
            <category><![CDATA[xcode]]></category>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[swiftui]]></category>
            <dc:creator><![CDATA[Aryaman Sharda]]></dc:creator>
            <pubDate>Tue, 26 Mar 2024 02:58:41 GMT</pubDate>
            <atom:updated>2024-03-30T01:37:56.606Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/839/0*3EA6ZGrWmJMzsMnN.png" /></figure><p>Do you remember that DVD screensaver from the early 2000s? Not only was it a practical tool for preventing screen burn-in on CRT displays, but it’s also become <a href="https://speechify.com/blog/dvd-screensaver/">a cultural icon</a>.</p><p>Like many of you, I can remember seeing this screensaver as a kid, but I’d completely forgotten about it until I rewatched this scene from “The Office” recently:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FQOtuX0jL85Y%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DQOtuX0jL85Y&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FQOtuX0jL85Y%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/ede3dff3d7ba2df78c204d5cb2cd7553/href">https://medium.com/media/ede3dff3d7ba2df78c204d5cb2cd7553/href</a></iframe><p>I thought it’d be fun to recreate this screensaver using SwiftUI. Admittedly, there’s no real reason for it other than the fact that I’ve got a long flight ahead and some time to kill.</p><p>Let’s get started!</p><h3>Working With The Canvas API</h3><p>First things first, let’s get all the UI elements set up. We can tackle the animation later.</p><pre>struct ContentView: View {<br>    @State private var position: CGPoint = .zero<br><br>    private let canvasSize: CGSize = UIScreen.main.bounds.size<br>    private let imageSize: CGSize = CGSize(width: 128, height: 76)<br>    private let image = Image(&quot;dvd_logo&quot;)<br><br>    var body: some View {<br>        Canvas { [position] context, size in<br>            // Set the background color to .black<br>            context.fill(Path(CGRect(origin: .zero, size: size)), with: .color(.black))<br><br>            // Draw image at current position<br>            var image = context.resolve(image)<br>            image.shading = .color(.red)<br>            context.draw(<br>                image,<br>                in: CGRect(x: position.x, y: position.y, width: imageSize.width, height: imageSize.height)<br>            )<br>        }<br>        .onAppear {<br>            // Set initial position to the center of the canvas after the view appears<br>            position = CGPoint(x: (canvasSize.width - imageSize.width) / 2, y: (canvasSize.height - imageSize.height) / 2)<br>        }<br>        .ignoresSafeArea()<br>    }<br>}</pre><p>We’re able to use SwiftUI’s Canvas API to set a background color for the screen and draw an image with a background color applied. Then, in the onAppear, we&#39;re calculating the center point of the screen and using that as the starting location for the image.</p><h3>Aside: The Need For The Capture Group</h3><p>At this point, I encountered some interesting behavior with the Canvas API.</p><p>Unless I explicitly declared position in the Canvas&#39;s capture group, the image would <em>always</em> get drawn at .zero even though the position is updated in the onAppear.</p><p>This behavior becomes even more interesting when you compare it to a regular SwiftUI view with a similar setup, where everything behaves as expected.</p><p>In the following SwiftUI view, the Text element accurately reflects the center of the View, and the View gets redrawn when the position value changes in .onAppear.</p><pre>struct ContentView: View {<br>    @State private var position: CGPoint = .zero<br>    private let canvasSize: CGSize = UIScreen.main.bounds.size<br><br>    var body: some View {<br>        VStack {<br>            Text(&quot;\(position.x), \(position.y)&quot;)<br>        }<br>        .onAppear {<br>            // Set initial position to the center of the view after it appears<br>            position = CGPoint(x: canvasSize.width / 2, y: canvasSize.height / 2)<br>        }<br>    }<br>}</pre><p>Let’s chalk this up to a difference in how Canvas manages it&#39;s dependencies and continue on with our implementation. If you know what&#39;s going on here, I&#39;d love to <a href="/service/mailto:aryaman@digitalbunker.dev">hear about it</a>.</p><h3>Creating A Draw Loop</h3><p>So far, we’ve successfully replicated the appearance of the DVD screensaver.</p><p>Next, we’ll need to build a mechanism to trigger the View to redraw at regular intervals. My initial approach was to simply use a Timer that fired every 1/30th of a second, so the View would redraw at 30 frames per second.</p><p>After some <a href="https://www.reddit.com/r/SwiftUI/comments/1bqefci/recreating_the_dvd_screensaver_in_swiftui/">feedback from users on Reddit</a> about this original approach, it turns out that using CADisplayLink is a far better option.</p><blockquote><em>CADisplayLink is a timer object that allows your app to synchronize its drawing to the refresh rate of the display.</em></blockquote><p>Using CADisplayLink ensures that our UI updates appear smooth and free the visual artifacts that might occur if our updates are out of sync with the screen&#39;s refresh rate.</p><p>The following CADisplayLink wrapper below allows us to detect when the hardware intends to redraw the screen and trigger a custom completion handler at that time.</p><pre>@MainActor<br>final class DisplayLink {<br>    private var displaylink: CADisplayLink?<br>    private var update: (() -&gt; Void)?<br><br>    func start(update: @escaping () -&gt; Void) {<br>        self.update = update<br>        <br>        displaylink = CADisplayLink(<br>            target: self,<br>            selector: #selector(frame)<br>        )<br>        displaylink?.add(to: .current, forMode: .default)<br>    }<br><br>    func stop() {<br>        // Stops the display link from firing and effectively<br>        // invalidates it, removing it from all run loop modes. <br>        //<br>        // After calling .invalidate(), the display link <br>        // cannot be reused.<br>        displaylink?.invalidate()<br>        update = nil<br>    }<br><br>    @objc func frame() {<br>        update?()<br>    }<br>}</pre><p>Then, in our View, we can simply add:</p><pre>@State private var displayLink = DisplayLink()<br>....<br>.onAppear {<br>    displayLink.start {<br>    // Update position of the image<br>    }<br>}<br>.onDisappear {<br>    displayLink.stop()<br>}</pre><p>Now, we know we want the image to move every time the View is redrawn, but how fast should it move?</p><p>Let’s add a velocity vector to control the movement speed of the DVD logo:</p><pre>@State private var velocity: CGVector = CGVector(dx: 1, dy: 1)</pre><p>In the displayLink update block, we&#39;ll update the position.x and position.y by adding the corresponding values from the velocity vector. This adjustment allows us to control the speed of movement along the X and Y axis independently.</p><pre>displayLink.start {<br>    // Update position based on velocity<br>    position.x += velocity.dx<br>    position.y += velocity.dy<br>}</pre><p>If you were to run this now, you’d notice the image gradually drifting off-screen:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*xcnMtbUDb63K8J55.gif" /></figure><p>Next, we’ll add some boundary checks to ensure our image stays within the visible area of the screen.</p><h3>Adding Boundary Checks</h3><p>How should we respond when our image reaches an edge?</p><p>If the image is approaching the right side of the screen, then all we need to do is switch the direction of its horizontal movement, right? Similarly, if the image is approaching the top of the screen, we only need to flip its vertical movement while keeping its horizontal movement the same.</p><p>With this intuition in place, the boundary checks become very simple:</p><pre>displayLink.start {<br>    // Update position based on velocity<br>    position.x += velocity.dx<br>    position.y += velocity.dy<br><br>    // Check if image hits an edge<br>    if position.x + imageSize.width &gt;= canvasSize.width || position.x &lt;= 0  {<br>        // Flip horizontal direction<br>        velocity.dx *= -1<br>    }<br><br>    if position.y + imageSize.height &gt;= canvasSize.height || position.y &lt;= 0 {<br>        // Flip vertical direction<br>        velocity.dy *= -1<br>    }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*ABMGcqyK47HgYaqQ.gif" /><figcaption>This recording is sped up; velocity is (5,5).</figcaption></figure><p>We’re on the home stretch now! Now, all we need to do is change the color of the image every time it hits an edge.</p><pre>@State private var imageColor: Color = .green<br><br>// In Canvas<br>var image = context.resolve(image)<br>image.shading = .color(imageColor)<br><br>// In displayLink.start {..}<br>// Check if image hits a horizontal edge<br>if position.x + imageSize.width &gt;= canvasSize.width || position.x &lt;= 0  {<br>    // Flip horizontal direction<br>    velocity.dx *= -1<br>    imageColor = Color.random()<br>}<br><br>if position.y + imageSize.height &gt;= canvasSize.height || position.y &lt;= 0 {<br>    // Flip vertical direction<br>    velocity.dy *= -1<br>    imageColor = Color.random()<br>}<br><br>extension Color {<br>    static func random() -&gt; Color {<br>        let red = Double.random(in: 0...1)<br>        let green = Double.random(in: 0...1)<br>        let blue = Double.random(in: 0...1)<br>        return Color(red: red, green: green, blue: blue)<br>    }<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*FMMelxqFvQKkmNQC.gif" /><figcaption>Voila!</figcaption></figure><p>You can find the source code here:</p><p><a href="https://github.com/aryamansharda/DVDScreensaver">GitHub - aryamansharda/DVDScreensaver: SwiftUI implementation of the DVDScreensaver</a></p><p>Hope you enjoyed this article! If you did, please consider sharing 😊🙏.</p><p>If you’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’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><ul><li><a href="https://aryamansharda.gumroad.com/l/tcvck?ref=digitalbunker.dev">Ace The iOS Interview</a></li><li><a href="https://indie.watch/?ref=digitalbunker.dev">Indie Watch</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6284f525ec2f" width="1" height="1" alt=""><hr><p><a href="https://medium.com/hackernoon/recreating-the-dvd-screensaver-in-swiftui-6284f525ec2f">Recreating The DVD Screensaver In SwiftUI</a> was originally published in <a href="https://medium.com/hackernoon">HackerNoon.com</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Week In The Life Of A Staff Software Engineer]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://aryamansharda.medium.com/week-in-the-life-of-a-staff-software-engineer-c08f894639a8?source=rss-174dbe86cc41------2"><img src="https://cdn-images-1.medium.com/max/1400/0*7Jc74O1Vb6HcmXDn" width="1400"></a></p><p class="medium-feed-snippet">If you&#x2019;ve spent any time on YouTube, chances are you&#x2019;ve seen those &#x201C;Day In The Life Of A Software Engineer&#x201D; videos. They&#x2019;re often staged&#x2026;</p><p class="medium-feed-link"><a href="https://aryamansharda.medium.com/week-in-the-life-of-a-staff-software-engineer-c08f894639a8?source=rss-174dbe86cc41------2">Continue reading on Medium »</a></p></div>]]></description>
            <link>https://aryamansharda.medium.com/week-in-the-life-of-a-staff-software-engineer-c08f894639a8?source=rss-174dbe86cc41------2</link>
            <guid isPermaLink="false">https://medium.com/p/c08f894639a8</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[silicon-valley]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[ios-development]]></category>
            <dc:creator><![CDATA[Aryaman Sharda]]></dc:creator>
            <pubDate>Mon, 19 Feb 2024 19:11:11 GMT</pubDate>
            <atom:updated>2024-03-31T08:00:01.759Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Launching news.digitalbunker.dev ]]></title>
            <link>https://aryamansharda.medium.com/launching-news-digitalbunker-dev-2ae72bd2bd1e?source=rss-174dbe86cc41------2</link>
            <guid isPermaLink="false">https://medium.com/p/2ae72bd2bd1e</guid>
            <category><![CDATA[xcode]]></category>
            <category><![CDATA[mobile-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[swift-programming]]></category>
            <dc:creator><![CDATA[Aryaman Sharda]]></dc:creator>
            <pubDate>Mon, 18 Dec 2023 05:33:38 GMT</pubDate>
            <atom:updated>2023-12-18T05:33:38.740Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Vhnjf8OHZK6E7fZmP0-Dzg.png" /></figure><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’s be real, I’ll inevitably miss out on some cool content. There’s no easy way of staying on top of everything.</p><p>So, to solve the problem for myself and other developers, I’m excited to introduce a space that brings together everything you need for iOS Development — all in real-time.</p><p>From insightful blogs to engaging podcasts, curated newsletters, new package releases / updates, and YouTube videos, you’ll find everything you need here:</p><p><a href="https://news.digitalbunker.dev/">iOS Development News</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2ae72bd2bd1e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Working With XcodeKit @ SwiftLeeds]]></title>
            <link>https://aryamansharda.medium.com/working-with-xcodekit-swiftleeds-e315c4cf2182?source=rss-174dbe86cc41------2</link>
            <guid isPermaLink="false">https://medium.com/p/e315c4cf2182</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[xcode]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Aryaman Sharda]]></dc:creator>
            <pubDate>Wed, 11 Oct 2023 12:25:14 GMT</pubDate>
            <atom:updated>2023-10-11T12:25:14.599Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TM93mivPWCmqK_YWGYNXiA.png" /></figure><p>Just wrapped up Day 2 at SwiftLeeds! Big thanks to SwiftLeeds for having me as a speaker! It was a pleasure to meet and connect with so many people in the iOS Community.</p><p>Looking forward to many more exciting speaking engagements in the future!</p><p>I’ll include a link to the recorded talk once it’s available.</p><p>In the meantime, you can find a copy of the slides here:</p><p><a href="https://drive.google.com/file/d/1ECnlHTEHobgJRiCmsU91anSRQ77623lx/view?usp=sharing">SwiftLeeds Xcode Editor Extension Day 2.pdf</a></p><p>And all of the sample code here:</p><p><a href="https://github.com/aryamansharda/EditKitPro">GitHub - aryamansharda/EditKitPro: A multi-purpose Xcode Editor Extension for iOS and macOS developers</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e315c4cf2182" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>