<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.eliaszsawicki.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.eliaszsawicki.com/" rel="alternate" type="text/html" /><updated>2026-10-12T16:10:20+00:00</updated><id>https://www.eliaszsawicki.com/feed.xml</id><title type="html">Eliasz Sawicki</title><subtitle>Build a Jekyll blog in minutes, without touching the command line.</subtitle><entry><title type="html">Tuist Registry: registryEnabled Points at the Registry, –replace-scm-with-registry Actually Uses It</title><link href="https://www.eliaszsawicki.com/tuist-replace-scm-with-registry/" rel="alternate" type="text/html" title="Tuist Registry: registryEnabled Points at the Registry, –replace-scm-with-registry Actually Uses It" /><published>2026-10-12T00:00:00+00:00</published><updated>2026-10-12T00:00:00+00:00</updated><id>https://www.eliaszsawicki.com/tuist-replace-scm-with-registry</id><content type="html" xml:base="https://www.eliaszsawicki.com/tuist-replace-scm-with-registry/"><![CDATA[<p><strong>SCM</strong> = Source Control Management, i.e. git. An “SCM dependency” is any package declared by repository URL, which SwiftPM resolves by cloning the repo. A <strong>registry</strong> dependency is declared by an identity like <code class="language-plaintext highlighter-rouge">scope.name</code> and comes down as a single zip of one version, with no git history.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="nf">package</span><span class="p">(</span><span class="nv">url</span><span class="p">:</span> <span class="s">"https://github.com/pointfreeco/swift-composable-architecture"</span><span class="p">,</span> <span class="nv">from</span><span class="p">:</span> <span class="s">"1.15.0"</span><span class="p">)</span> <span class="c1">// SCM</span>
<span class="o">.</span><span class="nf">package</span><span class="p">(</span><span class="nv">id</span><span class="p">:</span> <span class="s">"pointfreeco.swift-composable-architecture"</span><span class="p">,</span> <span class="nv">from</span><span class="p">:</span> <span class="s">"1.15.0"</span><span class="p">)</span>                   <span class="c1">// registry</span>
</code></pre></div></div>

<p>Tuist has two switches for the registry, and they do different things.</p>

<p><strong><code class="language-plaintext highlighter-rouge">registryEnabled: true</code></strong> only tells SwiftPM <em>where</em> the registry is:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">tuist</span> <span class="o">=</span> <span class="kt">Tuist</span><span class="p">(</span>
    <span class="nv">project</span><span class="p">:</span> <span class="o">.</span><span class="nf">tuist</span><span class="p">(</span>
        <span class="nv">generationOptions</span><span class="p">:</span> <span class="o">.</span><span class="nf">options</span><span class="p">(</span><span class="nv">registryEnabled</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
    <span class="p">)</span>
<span class="p">)</span>
</code></pre></div></div>

<p>During <code class="language-plaintext highlighter-rouge">tuist generate</code> it writes the registry config into <code class="language-plaintext highlighter-rouge">App.xcworkspace/xcshareddata/swiftpm/configuration/</code>, so you no longer have to run <code class="language-plaintext highlighter-rouge">tuist registry setup</code> yourself. That’s all it does. Only packages you declared with <code class="language-plaintext highlighter-rouge">.package(id:)</code> go to the registry, and every <code class="language-plaintext highlighter-rouge">.package(url:)</code> is still cloned from git.</p>

<p><strong><code class="language-plaintext highlighter-rouge">--replace-scm-with-registry</code></strong> rewrites the SCM dependencies themselves:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">tuist</span> <span class="o">=</span> <span class="kt">Tuist</span><span class="p">(</span>
    <span class="nv">project</span><span class="p">:</span> <span class="o">.</span><span class="nf">tuist</span><span class="p">(</span>
        <span class="nv">installOptions</span><span class="p">:</span> <span class="o">.</span><span class="nf">options</span><span class="p">(</span>
            <span class="nv">passthroughSwiftPackageManagerArguments</span><span class="p">:</span> <span class="p">[</span><span class="s">"--replace-scm-with-registry"</span><span class="p">]</span>
        <span class="p">)</span>
    <span class="p">)</span>
<span class="p">)</span>
</code></pre></div></div>

<p>On <code class="language-plaintext highlighter-rouge">tuist install</code> (or plain <code class="language-plaintext highlighter-rouge">swift package --replace-scm-with-registry resolve</code>), SwiftPM asks the registry about every git URL in the graph, transitive dependencies included:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GET /identifiers?url=https://github.com/pointfreeco/swift-composable-architecture
→ 200 { "identifiers": ["pointfreeco.swift-composable-architecture"] }
</code></pre></div></div>

<p>If the registry knows the URL, SwiftPM downloads the package from the registry. If the registry doesn’t know it (404), that one package falls back to git. Your manifests can keep their URLs. For Xcode’s own package integration, the same switch is a per-machine default, <code class="language-plaintext highlighter-rouge">IDEPackageDependencySCMToRegistryTransformation = useRegistryIdentityAndSources</code>, which <code class="language-plaintext highlighter-rouge">tuist registry setup</code> writes for you.</p>

<p><strong>Why mixing the two styles breaks without the flag.</strong> SwiftPM tells packages apart by <em>identity</em>, not by what’s inside them. For a URL dependency the identity is the last path component, so the TCA URL becomes <code class="language-plaintext highlighter-rouge">swift-composable-architecture</code>. For a registry dependency the identity is the <code class="language-plaintext highlighter-rouge">scope.name</code> string, so the same package becomes <code class="language-plaintext highlighter-rouge">pointfreeco.swift-composable-architecture</code>. Those are two different strings, so SwiftPM treats them as two unrelated packages.</p>

<p>Now say your app adds TCA by registry ID, and some library you depend on declares TCA by GitHub URL in <em>its</em> <code class="language-plaintext highlighter-rouge">Package.swift</code>. You can’t edit that file. SwiftPM ends up with two copies of the same package in the graph, each with a target named <code class="language-plaintext highlighter-rouge">ComposableArchitecture</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>multiple packages ('pointfreeco.swift-composable-architecture', 'swift-composable-architecture')
declare targets with a conflicting name: 'ComposableArchitecture'
</code></pre></div></div>

<p>Even when the names don’t collide, you can get two independently resolved versions of the “same” library linked into one app. With <code class="language-plaintext highlighter-rouge">--replace-scm-with-registry</code>, the library’s URL goes through <code class="language-plaintext highlighter-rouge">/identifiers?url=</code>, gets mapped to <code class="language-plaintext highlighter-rouge">pointfreeco.swift-composable-architecture</code>, and becomes the <em>same node</em> in the graph as your registry dependency. SwiftPM then has one package with both sets of version requirements to satisfy, resolves one version, and builds one module. (There’s also a narrower flag, <code class="language-plaintext highlighter-rouge">--use-registry-identity-for-scm</code>, which only does this identity mapping and still fetches over git.)</p>

<p><strong>Takeaway:</strong> <code class="language-plaintext highlighter-rouge">registryEnabled</code> makes the registry <em>available</em>, and <code class="language-plaintext highlighter-rouge">--replace-scm-with-registry</code> makes the whole graph <em>use</em> it. Turning on only the first and switching a few dependencies to <code class="language-plaintext highlighter-rouge">.package(id:)</code> is the easiest way to end up with the duplicate-package error above, because the transitive URL dependencies stay on git under a different identity. Turn on both.</p>]]></content><author><name></name></author><category term="devlog" /><category term="swift" /><category term="spm" /><category term="swift-package-manager" /><category term="registry" /><category term="tuist" /><summary type="html"><![CDATA[SCM = Source Control Management, i.e. git. An “SCM dependency” is any package declared by repository URL, which SwiftPM resolves by cloning the repo. A registry dependency is declared by an identity like scope.name and comes down as a single zip of one version, with no git history.]]></summary></entry><entry><title type="html">SwiftPM Asks for a Different Package.swift per Swift Version, and Your Registry Has to Answer</title><link href="https://www.eliaszsawicki.com/spm-registry-versioned-manifests/" rel="alternate" type="text/html" title="SwiftPM Asks for a Different Package.swift per Swift Version, and Your Registry Has to Answer" /><published>2026-10-05T00:00:00+00:00</published><updated>2026-10-05T00:00:00+00:00</updated><id>https://www.eliaszsawicki.com/spm-registry-versioned-manifests</id><content type="html" xml:base="https://www.eliaszsawicki.com/spm-registry-versioned-manifests/"><![CDATA[<p>A package can ship more than one manifest. Next to <code class="language-plaintext highlighter-rouge">Package.swift</code> you can drop version-specific ones, and SwiftPM picks the most specific match for the toolchain it’s running on:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Package.swift            // fallback
Package@swift-5.9.swift  // used by Swift 5.9.x
Package@swift-6.swift    // used by any Swift 6.x
</code></pre></div></div>

<p>With git-based dependencies that just works, because SwiftPM checks out the whole repo and looks at the files itself. With a <strong>package registry</strong> it never sees the file list. It has to ask the server, which means the registry has to implement that part of the spec (SE-0292):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GET /{scope}/{name}/{version}/Package.swift
→ 200, body = Package.swift
  Link: &lt;…/Package.swift?swift-version=6&gt;; rel="alternate";
        filename="Package@swift-6.swift"; swift-tools-version="6.0"

GET /{scope}/{name}/{version}/Package.swift?swift-version=6
→ 200, body = Package@swift-6.swift
  (or 303 See Other → plain Package.swift if there isn't one)
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">Link</code> header lists the alternates that exist, and the <code class="language-plaintext highlighter-rouge">swift-version</code> query parameter fetches one of them. If a home-grown registry ignores the query param and always serves the plain <code class="language-plaintext highlighter-rouge">Package.swift</code>, resolution still “works”, just against the wrong manifest. You get the fallback’s tools version, platforms and dependencies, and you end up with build errors that never show up when the same package comes from git.</p>

<p><strong>Takeaway:</strong> if you run your own registry (or proxy one), serving <code class="language-plaintext highlighter-rouge">Package.swift</code> isn’t enough. Advertise <code class="language-plaintext highlighter-rouge">Package@swift-*.swift</code> files in the <code class="language-plaintext highlighter-rouge">Link</code> header and honor <code class="language-plaintext highlighter-rouge">?swift-version=</code>, or any package with versioned manifests will resolve differently than it does from git.</p>]]></content><author><name></name></author><category term="devlog" /><category term="swift" /><category term="spm" /><category term="swift-package-manager" /><category term="registry" /><summary type="html"><![CDATA[A package can ship more than one manifest. Next to Package.swift you can drop version-specific ones, and SwiftPM picks the most specific match for the toolchain it’s running on:]]></summary></entry><entry><title type="html">alignmentGuide Trick from WWDC’s Shaders Talk: Point One Edge at Another</title><link href="https://www.eliaszsawicki.com/alignment-guide-flip-edge/" rel="alternate" type="text/html" title="alignmentGuide Trick from WWDC’s Shaders Talk: Point One Edge at Another" /><published>2026-08-31T00:00:00+00:00</published><updated>2026-08-31T00:00:00+00:00</updated><id>https://www.eliaszsawicki.com/alignment-guide-flip-edge</id><content type="html" xml:base="https://www.eliaszsawicki.com/alignment-guide-flip-edge/"><![CDATA[<p>From the WWDC shaders session — a throwaway layout trick buried in a demo, worth stealing on its own. <code class="language-plaintext highlighter-rouge">.alignmentGuide</code> doesn’t just nudge a guide by an offset, you can redefine it to <em>return a different guide entirely</em>, which lets you pin one view’s top edge to another view’s bottom.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">Text</span><span class="p">(</span><span class="n">line</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
    <span class="o">.</span><span class="nf">overlay</span><span class="p">(</span><span class="nv">alignment</span><span class="p">:</span> <span class="o">.</span><span class="n">bottomLeading</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">Text</span><span class="p">(</span><span class="n">line</span><span class="o">.</span><span class="n">formattedTimestamp</span><span class="p">)</span>
            <span class="o">.</span><span class="nf">alignmentGuide</span><span class="p">(</span><span class="o">.</span><span class="n">bottom</span><span class="p">)</span> <span class="p">{</span> <span class="nv">$0</span><span class="p">[</span><span class="o">.</span><span class="n">top</span><span class="p">]</span> <span class="p">}</span>
    <span class="p">}</span>
</code></pre></div></div>

<p>The overlay is anchored <code class="language-plaintext highlighter-rouge">.bottomLeading</code>, so normally its <code class="language-plaintext highlighter-rouge">.bottom</code> guide lines up with the base text’s bottom edge — stacking the timestamp <em>inside</em>, over the last line. Overriding <code class="language-plaintext highlighter-rouge">.bottom</code> to resolve to <code class="language-plaintext highlighter-rouge">$0[.top]</code> (the overlay’s own top edge) tricks the alignment math: the position SwiftUI computes for “bottom” is now wherever the timestamp’s <em>top</em> is, which pushes the whole timestamp view upward until its top edge sits where its bottom used to — landing it flush above the text instead of overlapping it.</p>

<p><strong>Takeaway:</strong> <code class="language-plaintext highlighter-rouge">alignmentGuide</code>’s closure can return any <code class="language-plaintext highlighter-rouge">Dimension</code> off the view’s <code class="language-plaintext highlighter-rouge">ViewDimensions</code>, not just an offset from the guide you named. Swap in a different edge when you need to flip which side of an overlay touches the anchor, instead of reaching for manual offsets or a separate <code class="language-plaintext highlighter-rouge">VStack</code>.</p>]]></content><author><name></name></author><category term="devlog" /><category term="swiftui" /><category term="layout" /><category term="wwdc" /><summary type="html"><![CDATA[From the WWDC shaders session — a throwaway layout trick buried in a demo, worth stealing on its own. .alignmentGuide doesn’t just nudge a guide by an offset, you can redefine it to return a different guide entirely, which lets you pin one view’s top edge to another view’s bottom.]]></summary></entry><entry><title type="html">#Playground: Inline Playgrounds Without Leaving Your Source File</title><link href="https://www.eliaszsawicki.com/playgrounds-macro/" rel="alternate" type="text/html" title="#Playground: Inline Playgrounds Without Leaving Your Source File" /><published>2026-08-24T00:00:00+00:00</published><updated>2026-08-24T00:00:00+00:00</updated><id>https://www.eliaszsawicki.com/playgrounds-macro</id><content type="html" xml:base="https://www.eliaszsawicki.com/playgrounds-macro/"><![CDATA[<p><code class="language-plaintext highlighter-rouge">import Playgrounds</code> unlocks the <code class="language-plaintext highlighter-rouge">#Playground</code> macro — drop it straight into a regular <code class="language-plaintext highlighter-rouge">.swift</code> file and Xcode runs it as a live playground canvas next to your code, no separate <code class="language-plaintext highlighter-rouge">.playground</code> bundle needed.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">import</span> <span class="kt">Playgrounds</span>

<span class="cp">#Playground {</span>
    <span class="k">let</span> <span class="nv">items</span> <span class="o">=</span> <span class="nf">fetchItems</span><span class="p">()</span>
    <span class="k">let</span> <span class="nv">total</span> <span class="o">=</span> <span class="n">items</span><span class="o">.</span><span class="nf">reduce</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nv">$0</span> <span class="o">+</span> <span class="nv">$1</span><span class="o">.</span><span class="n">price</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Open the file in Xcode and the canvas (⌥⌘Return) shows each statement’s result inline, same as classic Playgrounds, but scoped to that block and living right beside the production code it’s exercising — so it can call your app’s real types and functions directly, no copy-pasting into a standalone playground.</p>

<p><strong>Takeaway:</strong> for quick “let me just try this” experiments against existing app code, reach for <code class="language-plaintext highlighter-rouge">#Playground { }</code> instead of spinning up a separate <code class="language-plaintext highlighter-rouge">.playground</code> file — it stays version-controlled with the code it tests and gets deleted in the same PR when you’re done.</p>]]></content><author><name></name></author><category term="devlog" /><category term="xcode" /><category term="swift" /><category term="playgrounds" /><category term="macros" /><summary type="html"><![CDATA[import Playgrounds unlocks the #Playground macro — drop it straight into a regular .swift file and Xcode runs it as a live playground canvas next to your code, no separate .playground bundle needed.]]></summary></entry><entry><title type="html">Xcode Build Settings: Quick Help Shows You the Actual Key</title><link href="https://www.eliaszsawicki.com/build-settings-key-inspector/" rel="alternate" type="text/html" title="Xcode Build Settings: Quick Help Shows You the Actual Key" /><published>2026-08-17T00:00:00+00:00</published><updated>2026-08-17T00:00:00+00:00</updated><id>https://www.eliaszsawicki.com/build-settings-key-inspector</id><content type="html" xml:base="https://www.eliaszsawicki.com/build-settings-key-inspector/"><![CDATA[<p>Build Settings shows friendly names like “Swift Language Version,” but <code class="language-plaintext highlighter-rouge">.xcconfig</code> files and <code class="language-plaintext highlighter-rouge">xcodebuild</code> need the raw key. Option-click the setting (or select it and open the Quick Help inspector, ⌥⌘2/⌘⌥3) and the key shows up right there.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Swift Language Version → SWIFT_VERSION
Enable Bitcode          → ENABLE_BITCODE
Other Linker Flags      → OTHER_LDFLAGS
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// Debug.xcconfig
SWIFT_VERSION = 5.0
</code></pre></div></div>

<p><strong>Takeaway:</strong> don’t guess or grep the internet for a setting’s underlying name — option-click it in the Build Settings editor and Quick Help gives you the exact key to drop into an <code class="language-plaintext highlighter-rouge">.xcconfig</code>.</p>]]></content><author><name></name></author><category term="devlog" /><category term="xcode" /><category term="build-settings" /><summary type="html"><![CDATA[Build Settings shows friendly names like “Swift Language Version,” but .xcconfig files and xcodebuild need the raw key. Option-click the setting (or select it and open the Quick Help inspector, ⌥⌘2/⌘⌥3) and the key shows up right there.]]></summary></entry><entry><title type="html">Xcode Schemes: Launch Arguments Silently Override UserDefaults</title><link href="https://www.eliaszsawicki.com/scheme-launch-arguments-userdefaults/" rel="alternate" type="text/html" title="Xcode Schemes: Launch Arguments Silently Override UserDefaults" /><published>2026-08-10T00:00:00+00:00</published><updated>2026-08-10T00:00:00+00:00</updated><id>https://www.eliaszsawicki.com/scheme-launch-arguments-userdefaults</id><content type="html" xml:base="https://www.eliaszsawicki.com/scheme-launch-arguments-userdefaults/"><![CDATA[<p><code class="language-plaintext highlighter-rouge">Edit Scheme → Run → Arguments → Arguments Passed On Launch</code> isn’t just for <code class="language-plaintext highlighter-rouge">-AppleLanguage</code>-style system flags. Anything typed there as <code class="language-plaintext highlighter-rouge">-key value</code> gets registered into <code class="language-plaintext highlighter-rouge">NSArgumentDomain</code>, and that domain outranks everything else in <code class="language-plaintext highlighter-rouge">UserDefaults</code>’ search order — including values you explicitly wrote with <code class="language-plaintext highlighter-rouge">.set()</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-disableAnimations YES
-apiEnvironment staging
</code></pre></div></div>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">UserDefaults</span><span class="o">.</span><span class="n">standard</span><span class="o">.</span><span class="nf">bool</span><span class="p">(</span><span class="nv">forKey</span><span class="p">:</span> <span class="s">"disableAnimations"</span><span class="p">)</span> <span class="c1">// true, no code change needed</span>
<span class="kt">UserDefaults</span><span class="o">.</span><span class="n">standard</span><span class="o">.</span><span class="nf">string</span><span class="p">(</span><span class="nv">forKey</span><span class="p">:</span> <span class="s">"apiEnvironment"</span><span class="p">)</span>   <span class="c1">// "staging"</span>
</code></pre></div></div>

<p>Even if the app has previously called <code class="language-plaintext highlighter-rouge">UserDefaults.standard.set(false, forKey: "disableAnimations")</code> and that’s sitting on disk, the scheme argument wins on every launch — it doesn’t persist, it just shadows whatever’s stored for the life of that process.</p>

<p>Non-string values need plist syntax, quoted so the shell doesn’t eat the parens:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-featureFlags '(chat, offlineSync)'
</code></pre></div></div>

<p><strong>Takeaway:</strong> for temporary overrides — feature flags, locale testing, pointing at a different API environment — duplicate the scheme and set the argument there instead of adding <code class="language-plaintext highlighter-rouge">#if DEBUG</code> branches or hand-editing defaults in the simulator. Switching schemes becomes the toggle.</p>]]></content><author><name></name></author><category term="devlog" /><category term="xcode" /><category term="ios" /><category term="debugging" /><category term="userdefaults" /><summary type="html"><![CDATA[Edit Scheme → Run → Arguments → Arguments Passed On Launch isn’t just for -AppleLanguage-style system flags. Anything typed there as -key value gets registered into NSArgumentDomain, and that domain outranks everything else in UserDefaults’ search order — including values you explicitly wrote with .set().]]></summary></entry><entry><title type="html">SwiftUI Group Lab: There’s No API for ‘On Top of Everything’</title><link href="https://www.eliaszsawicki.com/swiftui-overlay-everything/" rel="alternate" type="text/html" title="SwiftUI Group Lab: There’s No API for ‘On Top of Everything’" /><published>2026-08-03T00:00:00+00:00</published><updated>2026-08-03T00:00:00+00:00</updated><id>https://www.eliaszsawicki.com/swiftui-overlay-everything</id><content type="html" xml:base="https://www.eliaszsawicki.com/swiftui-overlay-everything/"><![CDATA[<p><em>Notes from a SwiftUI Group Lab Q&amp;A — a summary of what the panel said, not my own take.</em></p>

<p>Question: how do you present a full-screen overlay above <em>everything</em>, including whatever sheet or full-screen-cover is already up — say, a forced login screen after a network disconnect? Short answer from the panel: SwiftUI doesn’t have an API for that, and that’s arguably on purpose.</p>

<p>Presentations in SwiftUI form a hierarchy. From the root, one <code class="language-plaintext highlighter-rouge">fullScreenCover</code> on top is easy:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">RootView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
    <span class="kd">@State</span> <span class="kd">private</span> <span class="k">var</span> <span class="nv">showLogin</span> <span class="o">=</span> <span class="kc">false</span>

    <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="kt">ContentView</span><span class="p">()</span>
            <span class="o">.</span><span class="nf">fullScreenCover</span><span class="p">(</span><span class="nv">isPresented</span><span class="p">:</span> <span class="err">$</span><span class="n">showLogin</span><span class="p">)</span> <span class="p">{</span>
                <span class="kt">LoginView</span><span class="p">()</span>
            <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>But if something else already presented a sheet or cover deeper in the hierarchy, you can’t just reach in from outside and stack another presentation above it — you’d need to track every active presentation yourself and coordinate through that, since SwiftUI has no built-in registry of “what’s currently on top.”</p>

<p>When that coordination isn’t worth building, the panel’s fallback is to drop to UIKit: with the UIKit scene lifecycle you get direct access to <code class="language-plaintext highlighter-rouge">UIWindowScene</code>, so you can spin up a new <code class="language-plaintext highlighter-rouge">UIWindow</code>, give it a <code class="language-plaintext highlighter-rouge">UIHostingController</code> root, and position it above everything — SwiftUI content included.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="kd">class</span> <span class="kt">OverlayWindowController</span> <span class="p">{</span>
    <span class="kd">private</span> <span class="k">var</span> <span class="nv">overlayWindow</span><span class="p">:</span> <span class="kt">UIWindow</span><span class="p">?</span>

    <span class="kd">func</span> <span class="nf">showOverlay</span><span class="p">(</span><span class="k">in</span> <span class="nv">scene</span><span class="p">:</span> <span class="kt">UIWindowScene</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">window</span> <span class="o">=</span> <span class="kt">UIWindow</span><span class="p">(</span><span class="nv">windowScene</span><span class="p">:</span> <span class="n">scene</span><span class="p">)</span>
        <span class="n">window</span><span class="o">.</span><span class="n">rootViewController</span> <span class="o">=</span> <span class="kt">UIHostingController</span><span class="p">(</span><span class="nv">rootView</span><span class="p">:</span> <span class="kt">LoginView</span><span class="p">())</span>
        <span class="n">window</span><span class="o">.</span><span class="n">windowLevel</span> <span class="o">=</span> <span class="o">.</span><span class="n">alert</span> <span class="o">+</span> <span class="mi">1</span>
        <span class="n">window</span><span class="o">.</span><span class="nf">makeKeyAndVisible</span><span class="p">()</span>
        <span class="n">overlayWindow</span> <span class="o">=</span> <span class="n">window</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The catch: this doesn’t scale past one team doing it. If two parts of the app both decide their thing “always needs to be on top,” it degenerates into whichever <code class="language-plaintext highlighter-rouge">UIWindow</code> was made last winning — a distributed layering decision with no source of truth, which is exactly the kind of bug SwiftUI’s presentation model is designed to prevent.</p>

<p><strong>Takeaway:</strong> the panel’s real advice was to question the design brief before reaching for this. A forced full-screen cover over an arbitrary presentation state is disruptive to the user regardless of how it’s implemented — often the better fix is unwinding the navigation/presentation stack to a base state and restoring it after login, rather than layering on top of whatever’s currently shown. If you do need a global overlay, a single coordinated owner (not ad hoc <code class="language-plaintext highlighter-rouge">UIWindow</code>s from wherever) is what keeps it from becoming a last-window-wins race.</p>]]></content><author><name></name></author><category term="devlog" /><category term="swiftui" /><category term="ios" /><category term="presentation" /><summary type="html"><![CDATA[Notes from a SwiftUI Group Lab Q&amp;A — a summary of what the panel said, not my own take.]]></summary></entry><entry><title type="html">SwiftUI Group Lab: Spot Invalidations with a Random Background Color</title><link href="https://www.eliaszsawicki.com/swiftui-random-color-invalidation/" rel="alternate" type="text/html" title="SwiftUI Group Lab: Spot Invalidations with a Random Background Color" /><published>2026-07-27T00:00:00+00:00</published><updated>2026-07-27T00:00:00+00:00</updated><id>https://www.eliaszsawicki.com/swiftui-random-color-invalidation</id><content type="html" xml:base="https://www.eliaszsawicki.com/swiftui-random-color-invalidation/"><![CDATA[<p><em>Notes from a SwiftUI Group Lab Q&amp;A — a summary of what the panel said, not my own take.</em></p>

<p>Body invalidation in SwiftUI is invisible by default — a view can re-evaluate far more often than you’d guess, with nothing on screen to show it. The panel’s trick: give the view a random background color on every <code class="language-plaintext highlighter-rouge">body</code> evaluation, so each re-render literally flashes a different color.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">RowView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">item</span><span class="p">:</span> <span class="kt">Item</span>

    <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="kt">Text</span><span class="p">(</span><span class="n">item</span><span class="o">.</span><span class="n">title</span><span class="p">)</span>
            <span class="o">.</span><span class="nf">padding</span><span class="p">()</span>
            <span class="o">.</span><span class="nf">background</span><span class="p">(</span><span class="kt">Color</span><span class="p">(</span>
                <span class="nv">red</span><span class="p">:</span> <span class="o">.</span><span class="nf">random</span><span class="p">(</span><span class="nv">in</span><span class="p">:</span> <span class="mi">0</span><span class="o">...</span><span class="mi">1</span><span class="p">),</span>
                <span class="nv">green</span><span class="p">:</span> <span class="o">.</span><span class="nf">random</span><span class="p">(</span><span class="nv">in</span><span class="p">:</span> <span class="mi">0</span><span class="o">...</span><span class="mi">1</span><span class="p">),</span>
                <span class="nv">blue</span><span class="p">:</span> <span class="o">.</span><span class="nf">random</span><span class="p">(</span><span class="nv">in</span><span class="p">:</span> <span class="mi">0</span><span class="o">...</span><span class="mi">1</span><span class="p">)</span>
            <span class="p">))</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Since the color is generated inside <code class="language-plaintext highlighter-rouge">body</code>, it changes on every invalidation. A row that should only redraw when its own data changes but instead flickers on every parent update, scroll, or unrelated state change becomes obvious at a glance — no Instruments session required.</p>

<p><strong>Takeaway:</strong> wrap a suspect view’s background in a random color during development to turn invisible re-renders into a visible signal, then strip it out before shipping.</p>]]></content><author><name></name></author><category term="devlog" /><category term="swiftui" /><category term="performance" /><category term="ios" /><category term="debugging" /><summary type="html"><![CDATA[Notes from a SwiftUI Group Lab Q&amp;A — a summary of what the panel said, not my own take.]]></summary></entry><entry><title type="html">SwiftUI Group Lab: Track Visible Items, Not Scroll Offset</title><link href="https://www.eliaszsawicki.com/swiftui-infinite-scroll/" rel="alternate" type="text/html" title="SwiftUI Group Lab: Track Visible Items, Not Scroll Offset" /><published>2026-07-20T00:00:00+00:00</published><updated>2026-07-20T00:00:00+00:00</updated><id>https://www.eliaszsawicki.com/swiftui-infinite-scroll</id><content type="html" xml:base="https://www.eliaszsawicki.com/swiftui-infinite-scroll/"><![CDATA[<p><em>Notes from a SwiftUI Group Lab Q&amp;A — a summary of what the panel said, not my own take.</em></p>

<p>Treat a scroll view’s content offset as an implementation detail — with lazy stacks it’s only estimated from the heights of off-screen views and carries no reliable semantic meaning. Trigger UI changes off the views actually on screen instead.</p>

<p><code class="language-plaintext highlighter-rouge">scrollPosition</code> tracks which item is currently visible by ID, not by offset — so it stays relative to your data model rather than a pixel value:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">FeedView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
    <span class="kd">@State</span> <span class="kd">private</span> <span class="k">var</span> <span class="nv">position</span> <span class="o">=</span> <span class="kt">ScrollPosition</span><span class="p">(</span><span class="nv">idType</span><span class="p">:</span> <span class="kt">Item</span><span class="o">.</span><span class="kt">ID</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>

    <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="kt">ScrollView</span> <span class="p">{</span>
            <span class="kt">LazyVStack</span> <span class="p">{</span>
                <span class="kt">ForEach</span><span class="p">(</span><span class="n">items</span><span class="p">)</span> <span class="p">{</span> <span class="n">item</span> <span class="k">in</span>
                    <span class="kt">ItemRow</span><span class="p">(</span><span class="nv">item</span><span class="p">:</span> <span class="n">item</span><span class="p">)</span><span class="o">.</span><span class="nf">id</span><span class="p">(</span><span class="n">item</span><span class="o">.</span><span class="n">id</span><span class="p">)</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="o">.</span><span class="nf">scrollPosition</span><span class="p">(</span><span class="err">$</span><span class="n">position</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>For visibility-based triggers — analytics, impression tracking — <code class="language-plaintext highlighter-rouge">onScrollVisibilityChange(threshold:)</code> fires when a view crosses a percentage-visible threshold, instead of you computing that from offsets yourself:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">ItemRow</span><span class="p">(</span><span class="nv">item</span><span class="p">:</span> <span class="n">item</span><span class="p">)</span>
    <span class="o">.</span><span class="nf">onScrollVisibilityChange</span><span class="p">(</span><span class="nv">threshold</span><span class="p">:</span> <span class="mf">0.8</span><span class="p">)</span> <span class="p">{</span> <span class="n">isVisible</span> <span class="k">in</span>
        <span class="k">if</span> <span class="n">isVisible</span> <span class="p">{</span> <span class="n">analytics</span><span class="o">.</span><span class="nf">logImpression</span><span class="p">(</span><span class="n">item</span><span class="p">)</span> <span class="p">}</span>
    <span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">onGeometryChange</code> and scroll transitions round out the toolkit: the former replaces <code class="language-plaintext highlighter-rouge">GeometryReader</code> hacks for reading frame changes, the latter offloads scroll-driven visual effects to the system and is often cheaper than computing them by hand from offset.</p>

<p><strong>Infinite scrolling</strong>, per the panel, doesn’t need any offset math either — put a sentinel view at the end of the list and let its <code class="language-plaintext highlighter-rouge">onAppear</code> trigger the next page fetch:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">LazyVStack</span> <span class="p">{</span>
    <span class="kt">ForEach</span><span class="p">(</span><span class="n">items</span><span class="p">)</span> <span class="p">{</span> <span class="n">item</span> <span class="k">in</span>
        <span class="kt">ItemRow</span><span class="p">(</span><span class="nv">item</span><span class="p">:</span> <span class="n">item</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="kt">Color</span><span class="o">.</span><span class="n">clear</span>
        <span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">height</span><span class="p">:</span> <span class="mi">1</span><span class="p">)</span>
        <span class="o">.</span><span class="n">onAppear</span> <span class="p">{</span> <span class="kt">Task</span> <span class="p">{</span> <span class="k">await</span> <span class="nf">loadMore</span><span class="p">()</span> <span class="p">}</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Takeaway:</strong> stop reasoning about scroll views in terms of content offset. Whatever you’re after — revealing a view at some scroll depth, tracking impressions, or paging in more data — there’s an API keyed to the views on screen instead.</p>]]></content><author><name></name></author><category term="devlog" /><category term="swiftui" /><category term="performance" /><category term="ios" /><summary type="html"><![CDATA[Notes from a SwiftUI Group Lab Q&amp;A — a summary of what the panel said, not my own take.]]></summary></entry><entry><title type="html">Swift Testing: Test.cancel()</title><link href="https://www.eliaszsawicki.com/swift-testing-test-cancel/" rel="alternate" type="text/html" title="Swift Testing: Test.cancel()" /><published>2026-07-13T00:00:00+00:00</published><updated>2026-07-13T00:00:00+00:00</updated><id>https://www.eliaszsawicki.com/swift-testing-test-cancel</id><content type="html" xml:base="https://www.eliaszsawicki.com/swift-testing-test-cancel/"><![CDATA[<p>Swift Testing 2.0 (Swift 6.4) adds <code class="language-plaintext highlighter-rouge">Test.cancel()</code> — a way to programmatically cancel the currently running test, marking it as cancelled rather than failed.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@Test</span> <span class="kd">func</span> <span class="nf">featureRequiresDevice</span><span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="p">{</span>
    <span class="k">guard</span> <span class="k">await</span> <span class="kt">DeviceCapability</span><span class="o">.</span><span class="n">isSupported</span> <span class="k">else</span> <span class="p">{</span>
        <span class="kt">Test</span><span class="o">.</span><span class="n">current</span><span class="p">?</span><span class="o">.</span><span class="nf">cancel</span><span class="p">()</span>
        <span class="k">return</span>
    <span class="p">}</span>
    <span class="c1">// test body runs only when supported</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This is different from <code class="language-plaintext highlighter-rouge">#require</code> (which records a failure) or throwing a <code class="language-plaintext highlighter-rouge">Skip</code> (which marks the test skipped). <code class="language-plaintext highlighter-rouge">Test.cancel()</code> cancels the underlying task — the test shows up as cancelled in the report, signalling “not applicable” rather than “broken.”</p>

<p>Because it goes through Swift concurrency cancellation, any <code class="language-plaintext highlighter-rouge">try await</code> after the call will throw <code class="language-plaintext highlighter-rouge">CancellationError</code> — so a bare <code class="language-plaintext highlighter-rouge">cancel()</code> followed by <code class="language-plaintext highlighter-rouge">return</code> is the safest pattern.</p>

<p>Cancellation also propagates to child tests in a suite:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@Test</span> <span class="kd">func</span> <span class="nf">suite</span><span class="p">()</span> <span class="k">async</span> <span class="p">{</span>
    <span class="k">if</span> <span class="kt">ProcessInfo</span><span class="o">.</span><span class="n">processInfo</span><span class="o">.</span><span class="n">environment</span><span class="p">[</span><span class="s">"CI"</span><span class="p">]</span> <span class="o">==</span> <span class="kc">nil</span> <span class="p">{</span>
        <span class="kt">Test</span><span class="o">.</span><span class="n">current</span><span class="p">?</span><span class="o">.</span><span class="nf">cancel</span><span class="p">()</span>
        <span class="k">return</span>
    <span class="p">}</span>
    <span class="c1">// child tests run only in CI</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Takeaway:</strong> use <code class="language-plaintext highlighter-rouge">Test.cancel()</code> when a test is conditionally irrelevant — not wrong, just not applicable. Reserve <code class="language-plaintext highlighter-rouge">#require</code> for preconditions that <em>should</em> hold, and keep <code class="language-plaintext highlighter-rouge">cancel()</code> for environment or capability gates.</p>]]></content><author><name></name></author><category term="devlog" /><category term="swift" /><category term="testing" /><category term="swift6" /><summary type="html"><![CDATA[Swift Testing 2.0 (Swift 6.4) adds Test.cancel() — a way to programmatically cancel the currently running test, marking it as cancelled rather than failed.]]></summary></entry></feed>