https://oleb.net/blog/
Ole Begemann
2024-03-05T18:54:44Z
Ole Begemann
Copyright © 2009–2024 Ole Begemann
https://oleb.net/2024/swift-toolchains/
Building with nightly Swift toolchains on macOS
2024-03-05T18:54:44Z
2024-03-05T18:54:44Z
Ole Begemann
<p>The Swift website provides nightly builds of the Swift compiler (called toolchains) <a href="https://www.swift.org/download/">for download</a>. Building with a nightly compiler can be useful if you want to check if a bug has already been fixed on main, or if you want to experiment with upcoming language features such as Embedded Swift, <a href="https://forums.swift.org/t/embedded-swift-on-the-raspberry-pi-pico-rp2040-without-the-pico-sdk/69338" title="Swift forum post: Embedded Swift on the Raspberry Pi Pico/RP2040">as I’ve been doing lately</a>.</p>
<p>A toolchain is distributed as a <code>.pkg</code> installer that installs itself into <code>/Library/Developer/Toolchains</code> (or the equivalent path in your home directory). After installation, you have several options to select the toolchain you want to build with:</p>
<h2 id="in-xcode">In Xcode</h2>
<p>In Xcode, select the toolchain from the main menu (Xcode > Toolchains), then build and/or run your code normally.</p>
<p>Not all Xcode features work with a custom toolchain. For example, playgrounds don’t work, and Xcode will always use its built-in copy of the Swift Package Manager, so you won’t be able to use unreleased SwiftPM features in this way. Also, Apple won’t accept apps built with a non-standard toolchain for submission to the App Store.</p>
<h2 id="on-the-command-line">On the command line</h2>
<p>When building on the command line there are multiple options, depending on your preferences and what tool you want to use.</p>
<h3 id="the-toolchains-environment-variable">The <code>TOOLCHAINS</code> environment variable</h3>
<p>All of the various Swift build tools respect the <code>TOOLCHAINS</code> environment variable. This should be set to the desired toolchain’s bundle ID, which you can find in the <code>Info.plist</code> file in the toolchain’s directory.</p>
<p>Example (I’m using a nightly toolchain from 2024-03-03 here):</p>
<div class="highlight"><pre class="highlight shell"><code><span class="c"># My normal Swift version is 5.10</span>
<span class="nv">$ </span>swift <span class="nt">--version</span>
swift-driver version: 1.90.11.1 Apple Swift version 5.10 <span class="o">(</span>swiftlang-5.10.0.13 clang-1500.3.9.4<span class="o">)</span>
<span class="c"># The nightly toolchain is 6.0-dev</span>
<span class="nv">$ </span><span class="nb">export </span><span class="nv">TOOLCHAINS</span><span class="o">=</span>org.swift.59202403031a
<span class="nv">$ </span>swift <span class="nt">--version</span>
Apple Swift version 6.0-dev <span class="o">(</span>LLVM 0c7823cab15dec9, Swift 0cc05909334c6f7<span class="o">)</span>
</code></pre></div>
<h3 id="toolchain-name-vs-bundle-id">Toolchain name vs. bundle ID</h3>
<p>I <em>think</em> the <code>TOOLCHAINS</code> variable is also supposed to accept the toolchain’s name instead of the bundle ID, but this doesn’t work reliably for me. I tried passing:</p>
<ul>
<li>the <code>DisplayName</code> from <code>Info.plist</code> (“Swift Development Snapshot 2024-03-03 (a)”),</li>
<li>the <code>ShortDisplayName</code> (“Swift Development Snapshot”; not unique if you have more than one toolchain installed!),</li>
<li>the directory name, both with and without the <code>.xctoolchain</code> suffix,</li>
</ul>
<p>but none of them worked reliably, especially if you have multiple toolchains installed.</p>
<p>In my limited testing, it seems that Swift picks the first toolchain that matches the short name prefix (“Swift Development Snapshot”) and ignores the long name components. For example, when I select “Swift Development Snapshot 2024-03-03 (a)”, Swift picks <code>swift-DEVELOPMENT-SNAPSHOT-2024-01-30-a</code>, presumably because that’s the “first” one (in alphabetical order) I have installed.</p>
<p>My advice: stick to the bundle ID, it works. Here’s a useful command to find the bundle ID of the latest toolchain you have installed (you may have to adjust the path if you install your toolchains in <code>~/Library</code> instead of <code>/Library</code>):</p>
<div class="highlight"><pre class="highlight shell"><code><span class="nv">$ </span>plutil <span class="nt">-extract</span> CFBundleIdentifier raw /Library/Developer/Toolchains/swift-latest.xctoolchain/Info.plist
org.swift.59202403031
<span class="c"># Set the toolchain to the latest installed:</span>
<span class="nb">export </span><span class="nv">TOOLCHAINS</span><span class="o">=</span><span class="si">$(</span>plutil <span class="nt">-extract</span> CFBundleIdentifier raw /Library/Developer/Toolchains/swift-latest.xctoolchain/Info.plist<span class="si">)</span>
</code></pre></div>
<h3 id="xcrun-and-xcodebuild">
<code>xcrun</code> and <code>xcodebuild</code>
</h3>
<p><code>xcrun</code> and <code>xcodebuild</code> respect the <code>TOOLCHAINS</code> variable too. As an alternative, they also provide an equivalent command line parameter named <code>--toolchain</code>. The parameter has the same semantics as the environment variable: you pass the toolchain’s bundle ID. Example:</p>
<div class="highlight"><pre class="highlight shell"><code><span class="nv">$ </span>xcrun <span class="nt">--toolchain</span> org.swift.59202403031a <span class="nt">--find</span> swiftc
/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-03-03-a.xctoolchain/usr/bin/swiftc
</code></pre></div>
<h3 id="swift-package-manager">Swift Package Manager</h3>
<p>SwiftPM also respects the <code>TOOLCHAINS</code> variable, and it has a <code>--toolchains</code> parameter as well, but this one expects the <em>path</em> to the toolchain, <em>not</em> its bundle ID. Example:</p>
<div class="highlight"><pre class="highlight shell"><code><span class="nv">$ </span>swift build <span class="nt">--toolchain</span> /Library/Developer/Toolchains/swift-latest.xctoolchain
</code></pre></div>
<h2 id="missing-toolchains-are-silently-ignored">Missing toolchains are (silently) ignored</h2>
<p>Another thing to be aware of: if you specify a toolchain that isn’t installed (e.g. because of a typo or because you’re trying to run a script that was developed in a different environment), none of the tools will fail:</p>
<ul>
<li>
<code>swift</code>, <code>xcrun</code>, and <code>xcodebuild</code> silently ignore the toolchain setting and use the default Swift toolchain (set via <code>xcode-select</code>).</li>
<li>SwiftPM silently ignores a missing toolchain set via <code>TOOLCHAINS</code>. If you pass an invalid directory to the <code>--toolchains</code> parameter, it at least prints a warning before it continues building with the default toolchain.</li>
</ul>
<p>I don’t like this. I’d much rather get an error if the build tool can’t find the toolchain I told it to use. It’s especially dangerous in scripts.</p>
https://oleb.net/2024/dispatchqueue-mainactor/
How the Swift compiler knows that DispatchQueue.main implies @MainActor
2024-02-29T18:54:47Z
2024-03-01T10:16:58Z
Ole Begemann
<p>You may have noticed that the Swift compiler automatically treats the closure of a <code>DispatchQueue.main.async</code> call as <a href="https://developer.apple.com/documentation/swift/mainactor"><code>@MainActor</code></a>. In other words, we can call a main-actor-isolated function in the closure:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">import</span> <span class="kt">Dispatch</span>
<span class="kd">@MainActor</span> <span class="kd">func</span> <span class="nf">mainActorFunc</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
<span class="kt">DispatchQueue</span><span class="o">.</span><span class="n">main</span><span class="o">.</span><span class="k">async</span> <span class="p">{</span>
<span class="c1">// The compiler lets us call this because</span>
<span class="c1">// it knows we're on the main actor.</span>
<span class="nf">mainActorFunc</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div>
<p>This behavior is welcome and very convenient, but it bugs me that it’s so hidden. As far as I know it isn’t documented, and neither Xcode nor any other editor/IDE I’ve used do a good job of showing me the actor context a function or closure will run in, even though the compiler has this information. I’ve written about a similar case before in <a href="https://oleb.net/2022/swiftui-task-mainactor/"><em>Where <code>View.task</code> gets its main-actor isolation from</em></a>, where Swift/Xcode hide essential information from the programmer by not showing certain attributes in declarations or the documentation.</p>
<h2 id="its-a-syntax-check">It’s a syntax check</h2>
<p>So how is the magic behavior for <code>DispatchQueue.main.async</code> implemented? It can’t be an attribute or other annotation on the closure parameter of the <a href="https://developer.apple.com/documentation/dispatch/dispatchqueue/2016098-async"><code>DispatchQueue.async</code></a> method because the actual queue instance isn’t known at that point.</p>
<p>A bit of experimentation reveals that it is in fact a relatively coarse source-code-based check that singles out invocations on <code>DispatchQueue.main</code>, <em>in exactly that spelling</em>. For example, the following variations do produce warnings/errors (in Swift 5.10/6.0, respectively), even though they are just as safe as the previous code snippet. This is because we aren’t using the “correct” <code>DispatchQueue.main.async</code> spelling:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="k">let</span> <span class="nv">queue</span> <span class="o">=</span> <span class="kt">DispatchQueue</span><span class="o">.</span><span class="n">main</span>
<span class="n">queue</span><span class="o">.</span><span class="k">async</span> <span class="p">{</span>
<span class="c1">// Error: Call to main actor-isolated global function</span>
<span class="c1">// 'mainActorFunc()' in a synchronous nonisolated context</span>
<span class="nf">mainActorFunc</span><span class="p">()</span> <span class="c1">// ❌</span>
<span class="p">}</span>
<span class="kd">typealias</span> <span class="kt">DP</span> <span class="o">=</span> <span class="kt">DispatchQueue</span>
<span class="kt">DP</span><span class="o">.</span><span class="n">main</span><span class="o">.</span><span class="k">async</span> <span class="p">{</span>
<span class="c1">// Error: Call to main actor-isolated global function</span>
<span class="c1">// 'mainActorFunc()' in a synchronous nonisolated context</span>
<span class="nf">mainActorFunc</span><span class="p">()</span> <span class="c1">// ❌</span>
<span class="p">}</span>
</code></pre></div>
<p>I found the place in the Swift compiler source code where the check happens. In the compiler’s semantic analysis stage (called “Sema”; this is the phase right after parsing), the type checker <a href="https://github.com/apple/swift/blob/8d5cd5a47d6a01e9d65865482eb2984a2defac6f/lib/Sema/ConstraintSystem.cpp#L2615-L2617">calls a function named <code>adjustFunctionTypeForConcurrency</code></a>, passing in a Boolean it obtained from <a href="https://github.com/apple/swift/blob/8d5cd5a47d6a01e9d65865482eb2984a2defac6f/lib/Sema/ConstraintSystem.cpp#L2023-L2025"><code>isMainDispatchQueueMember</code></a>, which returns <code>true</code> if the source code literally references <code>DispatchQueue.main</code>. In that case, the type checker <a href="https://github.com/apple/swift/blob/8d5cd5a47d6a01e9d65865482eb2984a2defac6f/lib/Sema/TypeCheckConcurrency.cpp#L5594-L5600">adds</a> the <a href="https://github.com/apple/swift/blob/main/docs/ReferenceGuides/UnderscoredAttributes.md#_unsafemainactor-_unsafesendable"><code>@_unsafeMainActor</code></a> attribute to the function type. Good to know.</p>
<p>Fun fact: since this is a purely syntax-based check, if you define your own type named <code>DispatchQueue</code>, give it a static <code>main</code> property and a function named <code>async</code> that takes a closure, the compiler will apply the same “fix” to it. This is NOT recommended:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="c1">// Define our own `DispatchQueue.main.async`</span>
<span class="kd">struct</span> <span class="kt">DispatchQueue</span> <span class="p">{</span>
<span class="kd">static</span> <span class="k">let</span> <span class="nv">main</span><span class="p">:</span> <span class="k">Self</span> <span class="o">=</span> <span class="o">.</span><span class="nf">init</span><span class="p">()</span>
<span class="kd">func</span> <span class="nf">async</span><span class="p">(</span><span class="n">_</span> <span class="nv">work</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="p">()</span> <span class="o">-></span> <span class="kt">Void</span><span class="p">)</span> <span class="p">{}</span>
<span class="p">}</span>
<span class="c1">// This calls our </span>
<span class="kt">DispatchQueue</span><span class="o">.</span><span class="n">main</span><span class="o">.</span><span class="k">async</span> <span class="p">{</span>
<span class="c1">// No error! Compiler has inserted `@_unsafeMainActor`</span>
<span class="nf">mainActorFunc</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div>
<h2 id="perplexity-through-obscurity">Perplexity through obscurity</h2>
<p>I love that this automatic <code>@MainActor</code> inference for <code>DispatchQueue.main</code> exists. I do <em>not</em> love that it’s another piece of hidden, implicit behavior that makes Swift concurrency harder to learn. I want to see all the <code>@_unsafeMainActor</code> and <a href="https://github.com/apple/swift/blob/main/docs/ReferenceGuides/UnderscoredAttributes.md#_unsafeinheritexecutor"><code>@_unsafeInheritExecutor</code></a> and <a href="https://github.com/apple/swift/blob/main/docs/ReferenceGuides/UnderscoredAttributes.md#_inheritactorcontext"><code>@_inheritActorContext</code></a> annotations! I believe Apple is doing the community a disservice by hiding these in Xcode.</p>
<p>The biggest benefit of Swift’s concurrency model over what we had before is that so many things are statically known at compile time. It’s a shame that the compiler <em>knows</em> on which executor a particular line of code will run, but none of the tools seem to be able to show me this. Instead, I’m forced to hunt for <code>@MainActor</code> annotations and hidden attributes in superclasses, protocols, etc. This feels especially problematic during the Swift 5-to-6 transition phase we’re currently in where it’s so easy to misuse concurrency and <em>not</em> get a compiler error (and sometimes not even a warning if you forget to enable strict concurrency checking).</p>
<p><strong>The most impactful change Apple can do to make Swift concurrency less confusing is to show the inferred executor context for <em>each line of code</em> in Xcode.</strong> Make it <em>really</em> obvious what code runs on the main actor, some other actor, or the global cooperative pool. Use colors or whatnot! (Other Swift IDEs should do this too, of course. I’m just picking on Xcode because Apple has the most leverage.)</p>
https://oleb.net/2023/swiftui-relative-size-in-stacks/
How the relative size modifier interacts with stack views
2023-03-24T20:14:49Z
2023-03-25T12:18:51Z
Ole Begemann
<h3>And what it can teach us about SwiftUI’s stack layout algorithm</h3>
<p>I have one more thing to say on the relative sizing view modifier from my previous post, <a href="https://oleb.net/2023/swiftui-relative-size/">Working with percentages in SwiftUI layout</a>. I’m assuming you’ve read that article. The following is good to know if you want to use the modifier in your own code, but I hope you’ll also learn some general tidbits about SwiftUI’s layout algorithm for HStacks and VStacks.</p>
<h2 id="using-relative-sizing-inside-a-stack-view">Using relative sizing inside a stack view</h2>
<p>Let’s apply the <code>relativeProposed</code> modifier to one of the subviews of an <a href="https://developer.apple.com/documentation/swiftui/hstack"><code>HStack</code></a>:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kt">HStack</span><span class="p">(</span><span class="nv">spacing</span><span class="p">:</span> <span class="mi">10</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">Color</span><span class="o">.</span><span class="n">blue</span>
<span class="_hl"><span class="o">.</span><span class="nf">relativeProposed</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mf">0.5</span><span class="p">)</span></span>
<span class="kt">Color</span><span class="o">.</span><span class="n">green</span>
<span class="kt">Color</span><span class="o">.</span><span class="n">yellow</span>
<span class="p">}</span>
<span class="o">.</span><span class="nf">border</span><span class="p">(</span><span class="o">.</span><span class="n">primary</span><span class="p">)</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">80</span><span class="p">)</span>
</code></pre></div>
<p>What do you expect to happen here? Will the blue view take up 50 % of the available width? The answer is no. In fact, the blue rectangle becomes narrower than the others:</p>
<div class="figure-container">
<figure style="max-width: 584px;">
<a href="https://oleb.net/media/2023-03-24-SwiftUI-relative-sizing-HStack-1-1294px.png">
<img src="https://oleb.net/media/2023-03-24-SwiftUI-relative-sizing-HStack-1-1294px.png" alt="A blue, a green, and a yellow rectangle in a horizontal line. The blue rectangle is 100 units wide, the other two 250 units each.">
</a>
</figure>
</div>
<p>This is because the HStack only proposes a proportion of its available width to each of its children. Here, the stack proposes one third of the available space to its first child, the relative sizing modifier. The modifier then halves this value, resulting in one sixth of the total width (minus spacing) for the blue color. The other two rectangles then become wider than one third because the first child view didn’t use up its full proposed width.</p>
<h2 id="order-matters">Order matters</h2>
<p>Now let’s move the modifier to the green color in the middle:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kt">HStack</span><span class="p">(</span><span class="nv">spacing</span><span class="p">:</span> <span class="mi">10</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">Color</span><span class="o">.</span><span class="n">blue</span>
<span class="kt">Color</span><span class="o">.</span><span class="n">green</span>
<span class="_hl"><span class="o">.</span><span class="nf">relativeProposed</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mf">0.5</span><span class="p">)</span></span>
<span class="kt">Color</span><span class="o">.</span><span class="n">yellow</span>
<span class="p">}</span>
</code></pre></div>
<p>Naively, I’d expect an equivalent result: the green rectangle should become 100 pt wide, and blue and yellow should be 250 pt each. But that’s not what happens — the yellow view ends up being wider than the blue one:</p>
<div class="figure-container">
<figure style="max-width: 584px;">
<a href="https://oleb.net/media/2023-03-24-SwiftUI-relative-sizing-HStack-2-1294px.png">
<img src="https://oleb.net/media/2023-03-24-SwiftUI-relative-sizing-HStack-2-1294px.png" alt="A blue, a green, and a yellow rectangle in a horizontal line. The blue rectangle is 200 units wide, the green 100, and the yellow 300.">
</a>
</figure>
</div>
<p>I found this unintuitive at first, but it makes sense if you understand that the HStack processes its children in sequence:</p>
<ol>
<li>
<p>The HStack proposes one third of its available space to the blue view: <code>(620 – 20) / 3 = 200</code>. The blue view accepts the proposal and becomes 200 pt wide.</p>
</li>
<li>
<p>Next up is the <code>relativeProposed</code> modifier. The HStack divides the remaining space by the number of remaining subviews and proposes that: <code>400 / 2 = 200</code>. Our modifier halves this proposal and proposes 100 pt to the green view, which accepts it. The modifier in turn adopts the size of its child and returns 100 pt to the HStack.</p>
</li>
<li>
<p>Since the second subview used less space than proposed, the HStack now has 300 pt left over to propose to its final child, the yellow color.</p>
</li>
</ol>
<p>Important: the order in which the stack lays out its subviews happens to be from left to right in this example, but that’s not always the case. <strong>In general, HStacks and VStacks first group their subviews by layout priority (more on that below), and then order the views inside each group by <em>flexibility</em> such that the least flexible views are laid out first.</strong> For more on this, see <a href="https://www.objc.io/blog/2020/11/10/hstacks-child-ordering/">How an HStack Lays out Its Children</a> by Chris Eidhof. The views in our example are all equally flexible (they all can become any width between 0 and infinity), so the stack processes them in their “natural” order.</p>
<h2 id="leftover-space-isnt-redistributed">Leftover space isn’t redistributed</h2>
<p>By now you may be able guess how the layout turns out when we move our view modifier to the last child view:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kt">HStack</span><span class="p">(</span><span class="nv">spacing</span><span class="p">:</span> <span class="mi">10</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">Color</span><span class="o">.</span><span class="n">blue</span>
<span class="kt">Color</span><span class="o">.</span><span class="n">green</span>
<span class="kt">Color</span><span class="o">.</span><span class="n">yellow</span>
<span class="_hl"><span class="o">.</span><span class="nf">relativeProposed</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mf">0.5</span><span class="p">)</span></span>
<span class="p">}</span>
</code></pre></div>
<div class="figure-container">
<figure style="max-width: 584px;">
<a href="https://oleb.net/media/2023-03-24-SwiftUI-relative-sizing-HStack-3-1294px.png">
<img src="https://oleb.net/media/2023-03-24-SwiftUI-relative-sizing-HStack-3-1294px.png" alt="A blue, a green, and a yellow rectangle in a horizontal line. The blue and green rectangles are each 200 units wide, the yellow one is 100 units wide. The complete HStack is 520 units wide, the available width is 620 units.">
</a>
</figure>
</div>
<ul>
<li>
<p>Blue and green each receive one third of the available width and become 200 pt wide. No surprises there.</p>
</li>
<li>
<p>When the HStack reaches the <code>relativeProposed</code> modifier, it has 200 pt left to distribute. Again, the modifier and the yellow rectangle only use half of this amount.</p>
</li>
</ul>
<p>The end result is that the HStack ends up with 100 pt left over. The process stops here — the HStack does <em>not</em> start over in an attempt to find a “better” solution. The stack makes itself just big enough to contain its subviews (= 520 pt incl. spacing) and reports that size to its parent.</p>
<h2 id="layout-priority">Layout priority</h2>
<p>We can use the <a href="https://developer.apple.com/documentation/swiftui/view/layoutpriority(_:)"><code>layoutPriority</code></a> view modifier to influence how stacks and other containers lay out their children. Let’s give the subview with the relative sizing modifier a higher layout priority (the default priority is 0):</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kt">HStack</span><span class="p">(</span><span class="nv">spacing</span><span class="p">:</span> <span class="mi">10</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">Color</span><span class="o">.</span><span class="n">blue</span>
<span class="kt">Color</span><span class="o">.</span><span class="n">green</span>
<span class="kt">Color</span><span class="o">.</span><span class="n">yellow</span>
<span class="o">.</span><span class="nf">relativeProposed</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mf">0.5</span><span class="p">)</span>
<span class="_hl"><span class="o">.</span><span class="nf">layoutPriority</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span></span>
<span class="p">}</span>
</code></pre></div>
<p>This results in a layout where the yellow rectangle actually takes up 50 % of the available space:</p>
<div class="figure-container">
<figure style="max-width: 584px;">
<a href="https://oleb.net/media/2023-03-24-SwiftUI-relative-sizing-HStack-4-1294px.png">
<img src="https://oleb.net/media/2023-03-24-SwiftUI-relative-sizing-HStack-4-1294px.png" alt="A blue, a green, and a yellow rectangle in a horizontal line. The blue and green rectangles are each 150 units wide, and the yellow 300.">
</a>
</figure>
</div>
<p>Explanation:</p>
<ol>
<li>
<p>The HStack groups its children by layout priority and then processes each group in sequence, from highest to lowest priority. Each group is proposed the <em>entire</em> remaining space.</p>
</li>
<li>
<p>The first layout group only contains a single view, our relative sizing modifier with the yellow color. The HStack proposes the entire available space (minus spacing) = 600 pt. Our modifier halves the proposal, resulting in 300 pt for the yellow view.</p>
</li>
<li>
<p>There are 300 pt left over for the second layout group. These are distributed equally among the two children because each subview accepts the proposed size.</p>
</li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p>The code I used to generate the images in this article <a href="https://gist.github.com/ole/7577deed8081ef6294f761704cff8a1d#file-znestedinhstack-swift">is available on GitHub</a>. I only looked at HStacks here, but VStacks work in exactly the same way for the vertical dimension.</p>
<p>SwiftUI’s layout algorithm always follows this basic pattern of proposed sizes and responses. Each of the built-in “primitive” views (e.g. <a href="https://developer.apple.com/documentation/swiftui/view/frame(width:height:alignment:)">fixed</a> and <a href="https://developer.apple.com/documentation/swiftui/view/frame(minwidth:idealwidth:maxwidth:minheight:idealheight:maxheight:alignment:)">flexible frames</a>, <a href="https://developer.apple.com/documentation/swiftui/layout-fundamentals">stacks</a>, <a href="https://developer.apple.com/documentation/swiftui/text"><code>Text</code></a>, <a href="https://developer.apple.com/documentation/swiftui/image"><code>Image</code></a>, <a href="https://developer.apple.com/documentation/swiftui/spacer"><code>Spacer</code></a>, <a href="https://developer.apple.com/documentation/swiftui/shape">shapes</a>, <a href="https://developer.apple.com/documentation/swiftui/view/padding(_:)-68shk"><code>padding</code></a>, <a href="https://developer.apple.com/documentation/swiftui/view/background(alignment:content:)"><code>background</code></a>, <a href="https://developer.apple.com/documentation/swiftui/view/overlay(alignment:content:)"><code>overlay</code></a>) has a well-defined (if not always well-documented) layout behavior that can be expressed as a function <code>(ProposedViewSize) -> CGSize</code>. You’ll need to learn the behavior for view to work effectively with SwiftUI.</p>
<p>A concrete lesson I’m taking away from this analysis: <code>HStack</code> and <code>VStack</code> don’t treat layout as an optimization problem that tries to find the optimal solution for a set of constraints (autolayout style). Rather, they sort their children in a particular way and then do a single proposal-and-response pass over them. If there’s space leftover at the end, or if the available space isn’t enough, then so be it.</p>
https://oleb.net/2023/swiftui-relative-size/
Working with percentages in SwiftUI layout
2023-03-23T22:31:11Z
2023-03-24T20:14:49Z
Ole Begemann
<p>SwiftUI’s layout primitives generally don’t provide <em>relative</em> sizing options, e.g. “make this view 50 % of the width of its container”. Let’s build our own!</p>
<h2 id="use-case-chat-bubbles">Use case: chat bubbles</h2>
<p>Consider this chat conversation view as an example of what I want to build. The chat bubbles always remain 80 % as wide as their container as the view is resized:</p>
<div class="figure-container">
<figure style="max-width: 844px;">
<video controls="" width="844">
<source src="/media/2023-03-23-SwiftUI-relativeWidth-demo-1.mp4" type="video/mp4"></source>
</video>
<figcaption>
The chat bubbles should become 80 % as wide as their container. <a href="https://oleb.net/media/2023-03-23-SwiftUI-relativeWidth-demo-1.mp4">Download video</a>
</figcaption>
</figure>
</div>
<h2 id="building-a-proportional-sizing-modifier">Building a proportional sizing modifier</h2>
<h3 id="the-layout">1. The Layout</h3>
<p>We can build our own relative sizing modifier on top of the <a href="https://developer.apple.com/documentation/swiftui/layout"><code>Layout</code></a> protocol. The layout multiplies its own proposed size (which it receives from its parent view) with the given factors for width and height. It then proposes this modified size to its only subview. Here’s the implementation (<a href="https://gist.github.com/ole/7577deed8081ef6294f761704cff8a1d">the full code, including the demo app, is on GitHub</a>):</p>
<div class="highlight"><pre class="highlight swift"><code><span class="c1">/// A custom layout that proposes a percentage of its</span>
<span class="c1">/// received proposed size to its subview.</span>
<span class="c1">///</span>
<span class="c1">/// - Precondition: must contain exactly one subview.</span>
<span class="kd">fileprivate</span> <span class="kd">struct</span> <span class="kt">RelativeSizeLayout</span><span class="p">:</span> <span class="kt">Layout</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">relativeWidth</span><span class="p">:</span> <span class="kt">Double</span>
<span class="k">var</span> <span class="nv">relativeHeight</span><span class="p">:</span> <span class="kt">Double</span>
<span class="kd">func</span> <span class="nf">sizeThatFits</span><span class="p">(</span>
<span class="nv">proposal</span><span class="p">:</span> <span class="kt">ProposedViewSize</span><span class="p">,</span>
<span class="nv">subviews</span><span class="p">:</span> <span class="kt">Subviews</span><span class="p">,</span>
<span class="nv">cache</span><span class="p">:</span> <span class="nf">inout</span> <span class="p">()</span>
<span class="p">)</span> <span class="o">-></span> <span class="kt">CGSize</span> <span class="p">{</span>
<span class="nf">assert</span><span class="p">(</span><span class="n">subviews</span><span class="o">.</span><span class="n">count</span> <span class="o">==</span> <span class="mi">1</span><span class="p">,</span> <span class="s">"expects a single subview"</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">resizedProposal</span> <span class="o">=</span> <span class="kt">ProposedViewSize</span><span class="p">(</span>
<span class="nv">width</span><span class="p">:</span> <span class="n">proposal</span><span class="o">.</span><span class="n">width</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="nv">$0</span> <span class="o">*</span> <span class="n">relativeWidth</span> <span class="p">},</span>
<span class="nv">height</span><span class="p">:</span> <span class="n">proposal</span><span class="o">.</span><span class="n">height</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="nv">$0</span> <span class="o">*</span> <span class="n">relativeHeight</span> <span class="p">}</span>
<span class="p">)</span>
<span class="k">return</span> <span class="n">subviews</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="nf">sizeThatFits</span><span class="p">(</span><span class="n">resizedProposal</span><span class="p">)</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">placeSubviews</span><span class="p">(</span>
<span class="k">in</span> <span class="nv">bounds</span><span class="p">:</span> <span class="kt">CGRect</span><span class="p">,</span>
<span class="nv">proposal</span><span class="p">:</span> <span class="kt">ProposedViewSize</span><span class="p">,</span>
<span class="nv">subviews</span><span class="p">:</span> <span class="kt">Subviews</span><span class="p">,</span>
<span class="nv">cache</span><span class="p">:</span> <span class="nf">inout</span> <span class="p">()</span>
<span class="p">)</span> <span class="p">{</span>
<span class="nf">assert</span><span class="p">(</span><span class="n">subviews</span><span class="o">.</span><span class="n">count</span> <span class="o">==</span> <span class="mi">1</span><span class="p">,</span> <span class="s">"expects a single subview"</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">resizedProposal</span> <span class="o">=</span> <span class="kt">ProposedViewSize</span><span class="p">(</span>
<span class="nv">width</span><span class="p">:</span> <span class="n">proposal</span><span class="o">.</span><span class="n">width</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="nv">$0</span> <span class="o">*</span> <span class="n">relativeWidth</span> <span class="p">},</span>
<span class="nv">height</span><span class="p">:</span> <span class="n">proposal</span><span class="o">.</span><span class="n">height</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="nv">$0</span> <span class="o">*</span> <span class="n">relativeHeight</span> <span class="p">}</span>
<span class="p">)</span>
<span class="n">subviews</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="nf">place</span><span class="p">(</span>
<span class="nv">at</span><span class="p">:</span> <span class="kt">CGPoint</span><span class="p">(</span><span class="nv">x</span><span class="p">:</span> <span class="n">bounds</span><span class="o">.</span><span class="n">midX</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="n">bounds</span><span class="o">.</span><span class="n">midY</span><span class="p">),</span>
<span class="nv">anchor</span><span class="p">:</span> <span class="o">.</span><span class="n">center</span><span class="p">,</span>
<span class="nv">proposal</span><span class="p">:</span> <span class="n">resizedProposal</span>
<span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>Notes:</p>
<ul>
<li>
<p>I made the type private because I want to control how it can be used. This is important for maintaining the assumption that the layout only ever has a single subview (which makes the math much simpler).</p>
</li>
<li>
<p><a href="https://developer.apple.com/documentation/swiftui/proposedviewsize">Proposed sizes</a> in SwiftUI can be <code>nil</code> or infinity in either dimension. Our layout passes these special values through unchanged (infinity times a percentage is still infinity). I’ll discuss below what implications this has for users of the layout.</p>
</li>
</ul>
<h3 id="the-view-extension">2. The View extension</h3>
<p>Next, we’ll add an extension on <a href="https://developer.apple.com/documentation/swiftui/view"><code>View</code></a> that uses the layout we just wrote. This becomes our public API:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">extension</span> <span class="kt">View</span> <span class="p">{</span>
<span class="c1">/// Proposes a percentage of its received proposed size to `self`.</span>
<span class="kd">public</span> <span class="kd">func</span> <span class="nf">relativeProposed</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="kt">Double</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="kt">Double</span> <span class="o">=</span> <span class="mi">1</span><span class="p">)</span> <span class="o">-></span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
<span class="kt">RelativeSizeLayout</span><span class="p">(</span><span class="nv">relativeWidth</span><span class="p">:</span> <span class="n">width</span><span class="p">,</span> <span class="nv">relativeHeight</span><span class="p">:</span> <span class="n">height</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Wrap content view in a container to make sure the layout only</span>
<span class="c1">// receives a single subview. Because views are lists!</span>
<span class="kt">VStack</span> <span class="p">{</span> <span class="c1">// alternatively: `_UnaryViewAdaptor(self)`</span>
<span class="k">self</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>Notes:</p>
<ul>
<li>
<p>I decided to go with a verbose name, <code>relativeProposed(width:height:)</code>, to make the semantics clear: we’re changing the <em>proposed</em> size for the subview, which won’t always result in a different actual size. More on this below.</p>
</li>
<li>
<p>We’re wrapping the subview (<code>self</code> in the code above) in a <a href="https://developer.apple.com/documentation/swiftui/vstack"><code>VStack</code></a>. This might seem redundant, but it’s necessary to make sure the layout only receives a single element in its subviews collection. See Chris Eidhof’s <a href="https://chris.eidhof.nl/post/swiftui-views-are-lists/">SwiftUI Views are Lists</a> for an explanation.</p>
</li>
</ul>
<h2 id="usage">Usage</h2>
<p>The layout code for a single chat bubble in the demo video above looks like this:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="k">let</span> <span class="nv">alignment</span><span class="p">:</span> <span class="kt">Alignment</span> <span class="o">=</span> <span class="n">message</span><span class="o">.</span><span class="n">sender</span> <span class="o">==</span> <span class="o">.</span><span class="n">me</span> <span class="p">?</span> <span class="o">.</span><span class="nv">trailing</span> <span class="p">:</span> <span class="o">.</span><span class="n">leading</span>
<span class="n">chatBubble</span>
<span class="o">.</span><span class="nf">relativeProposed</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mf">0.8</span><span class="p">)</span>
<span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">maxWidth</span><span class="p">:</span> <span class="o">.</span><span class="n">infinity</span><span class="p">,</span> <span class="nv">alignment</span><span class="p">:</span> <span class="n">alignment</span><span class="p">)</span>
</code></pre></div>
<p>The outermost flexible frame with <code>maxWidth: .infinity</code> is responsible for positioning the chat bubble with leading or trailing alignment, depending on who’s speaking.</p>
<p>You can even add another frame that limits the width to a maximum, say 400 points:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="k">let</span> <span class="nv">alignment</span><span class="p">:</span> <span class="kt">Alignment</span> <span class="o">=</span> <span class="n">message</span><span class="o">.</span><span class="n">sender</span> <span class="o">==</span> <span class="o">.</span><span class="n">me</span> <span class="p">?</span> <span class="o">.</span><span class="nv">trailing</span> <span class="p">:</span> <span class="o">.</span><span class="n">leading</span>
<span class="n">chatBubble</span>
<span class="_hl"><span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">maxWidth</span><span class="p">:</span> <span class="mi">400</span><span class="p">)</span></span>
<span class="o">.</span><span class="nf">relativeProposed</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mf">0.8</span><span class="p">)</span>
<span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">maxWidth</span><span class="p">:</span> <span class="o">.</span><span class="n">infinity</span><span class="p">,</span> <span class="nv">alignment</span><span class="p">:</span> <span class="n">alignment</span><span class="p">)</span>
</code></pre></div>
<p>Here, our relative sizing modifier only has an effect as the bubbles become narrower than 400 points. In a wider window the width-limiting frame takes precedence. I like how composable this is!</p>
<div class="figure-container">
<figure style="max-width: 844px;">
<video controls="" width="844">
<source src="/media/2023-03-23-SwiftUI-relativeWidth-demo-2.mp4" type="video/mp4"></source>
</video>
<figcaption>
<a href="https://oleb.net/media/2023-03-23-SwiftUI-relativeWidth-demo-2.mp4">Download video</a>
</figcaption>
</figure>
</div>
<h3 id="wont-always-result-in-80">80 % won’t always result in 80 %</h3>
<p>If you watch the debugging guides I’m drawing in the video above, you’ll notice that the relative sizing modifier never reports a width greater than 400, even if the window is wide enough:</p>
<div class="figure-container">
<figure style="max-width: 844px;">
<a href="https://oleb.net/media/2023-03-23-SwiftUI-relativeWidth-and-maxWidth-1688px.png">
<img src="https://oleb.net/media/2023-03-23-SwiftUI-relativeWidth-and-maxWidth-1688px.png" alt="A Mac window showing a mockup of a chat conversation with bubbles for the speakers. Overlaid on the chat bubbles are debugging views showing the widths of different components. The total container width is 753. The relW=80% debugging guide shows a width of 400.">
</a>
<figcaption>
The relative sizing modifier accepts the actual size of its subview as its own size.
</figcaption>
</figure>
</div>
<p>This is because our layout only adjusts the <em>proposed</em> size for its subview but then accepts the subview’s <em>actual</em> size as its own. Since SwiftUI views always choose their own size (which the parent can’t override), the subview is free to ignore our proposal. In this example, the layout’s subview is the <code>frame(maxWidth: 400)</code> view, which sets its own width to the proposed width or 400, whichever is smaller.</p>
<h2 id="understanding-the-modifiers-behavior">Understanding the modifier’s behavior</h2>
<h3 id="proposed-size--actual-size">Proposed size ≠ actual size</h3>
<p>It’s important to internalize that the modifier works on the basis of proposed sizes. This means it depends on the cooperation of its subview to achieve its goal: views that ignore their proposed size will be unaffected by our modifier. I don’t find this particularly problematic because SwiftUI’s entire layout system works like this. Ultimately, SwiftUI views always determine their own size, so you can’t write a modifier that “does the right thing” (whatever that is) for an arbitrary subview hierarchy.</p>
<h3 id="nil-and-infinity">
<code>nil</code> and infinity</h3>
<p>I already mentioned another thing to be aware of: if the parent of the relative sizing modifier proposes <code>nil</code> or <code>.infinity</code>, the modifier will pass the proposal through unchanged. Again, I don’t think this is particularly bad, but it’s something to be aware of.</p>
<p>Proposing <code>nil</code> is SwiftUI’s way of telling a view to become its ideal size (<a href="https://developer.apple.com/documentation/swiftui/view/fixedsize(horizontal:vertical:)"><code>fixedSize</code></a> does this). Would you ever want to tell a view to become, say, 50 % of its ideal width? I’m not sure. Maybe it’d make sense for resizable images and similar views.</p>
<p>By the way, you could modify the layout to do something like this:</p>
<ol>
<li>If the proposal is <code>nil</code> or infinity, forward it to the subview unchanged.</li>
<li>Take the reported size of the subview as the new basis and apply the scaling factors to that size (this still breaks down if the child returns infinity).</li>
<li>Now propose the scaled size to the subview. The subview might respond with a different actual size.</li>
<li>Return this latest reported size as your own size.</li>
</ol>
<p>This process of sending multiple proposals to child views is called <em>probing</em>. Lots of built-in containers views do this too, e.g. <code>VStack</code> and <code>HStack</code>.</p>
<h3 id="nesting-in-other-container-views">Nesting in other container views</h3>
<p>The relative sizing modifier interacts in an interesting way with stack views and other containers that distribute the available space among their children. I thought this was such an interesting topic that I wrote a separate article about it: <a href="https://oleb.net/2023/swiftui-relative-size-in-stacks/">How the relative size modifier interacts with stack views</a>.</p>
<div class="figure-container">
<figure style="max-width: 584px;">
<a href="https://oleb.net/media/2023-03-24-SwiftUI-relative-sizing-HStack-1-1294px.png">
<img src="https://oleb.net/media/2023-03-24-SwiftUI-relative-sizing-HStack-1-1294px.png" alt="A blue, a green, and a yellow rectangle in a horizontal line. The blue rectangle is 100 units wide, the other two 250 units each.">
</a>
</figure>
</div>
<h2 id="the-code">The code</h2>
<p>The complete code is available <a href="https://gist.github.com/ole/7577deed8081ef6294f761704cff8a1d">in a Gist on GitHub</a>.</p>
<h2 id="digression-proportional-sizing-in-early-swiftui-betas">Digression: Proportional sizing in early SwiftUI betas</h2>
<p>The very first SwiftUI betas in 2019 <em>did</em> include proportional sizing modifiers, but they were taken out before the final release. <a href="https://chris.eidhof.nl/">Chris Eidhof</a> preserved <a href="https://gist.github.com/chriseidhof/b770a50641e3c430112bc404ca430b82#file-swiftui-swift-L8257">a copy of SwiftUI’s “header file” from that time</a> that shows their API, including quite lengthy documentation.</p>
<p>I don’t know why these modifiers didn’t survive the beta phase. <a href="https://developer.apple.com/documentation/ios-ipados-release-notes/ios-13-release-notes#SwiftUI">The release notes from 2019</a> don’t give a reason:</p>
<blockquote>
<p>The <code>relativeWidth(_:)</code>, <code>relativeHeight(_:)</code>, and <code>relativeSize(width:height:)</code> modifiers are deprecated. Use other modifiers like <a href="https://developer.apple.com/documentation/SwiftUI/View/frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:)"><code>frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:)</code></a> instead. (51494692)</p>
</blockquote>
<p>I also don’t remember how these modifiers worked. They probably had somewhat similar semantics to my solution, but I can’t be sure. The doc comments linked above sound straightforward (“Sets the width of this view to the specified proportion of its parent’s width.”), but they don’t mention the intricacies of the layout algorithm (proposals and responses) at all.</p>
https://oleb.net/2023/photos-keyboard-shortcuts/
Keyboard shortcuts for Export Unmodified Original in Photos for Mac
2023-03-21T21:42:04Z
2023-03-21T21:55:40Z
Ole Begemann
<h2 id="problem">Problem</h2>
<ol>
<li>The Photos app on macOS doesn’t provide a keyboard shortcut for the <em>Export Unmodified Original</em> command.</li>
<li>macOS allows you to add your own app-specific keyboard shortcuts via System Settings > Keyboard > Keyboard Shortcuts > App Shortcuts. You need to enter the exact spelling of the menu item you want to invoke.</li>
<li>Photos renames the command depending on what’s selected: Export Unmodified Original For 1 Photo“ turns into ”… Original<em>s</em> For 2 Video<em>s</em>” turns into “… For 3 Items” (for mixed selections), and so on. Argh!</li>
<li>The System Settings UI for assigning keyboard shortcuts is extremely tedious to use if you want to add more than one or two shortcuts.</li>
</ol>
<div class="figure-container">
<figure style="max-width: 770px;" class="no-border">
<a href="https://oleb.net/media/2023-03-21-photos-app-export-unmodified-originals-1540px.jpg">
<img src="https://oleb.net/media/2023-03-21-photos-app-export-unmodified-originals-1540px.jpg" alt="Screenshot of the File > Export submenu of the Photos app on macOS. The selected menu command is called 'Export Unmodified Originals For 16 Items'">
</a>
<figcaption>
Dynamically renaming menu commands is cute, but it becomes a problem when you want to assign keyboard shortcuts.
</figcaption>
</figure>
</div>
<h2 id="solution-shell-script">Solution: shell script</h2>
<p>Here’s a Bash script<sup id="fnref:1" role="doc-noteref"><a href="https://oleb.net/2023/photos-keyboard-shortcuts/#fn:1" class="footnote" rel="footnote">1</a></sup> that assigns <kbd>Ctrl</kbd> + <kbd>Opt</kbd> + <kbd>Cmd</kbd> + <kbd>E</kbd> to <em>Export Unmodified Originals</em> for up to 20 selected items:</p>
<div class="highlight"><pre class="highlight shell"><code><span class="c">#!/bin/bash</span>
<span class="c"># Assigns a keyboard shortcut to the Export Unmodified Originals</span>
<span class="c"># menu command in Photos.app on macOS.</span>
<span class="c"># @ = Command</span>
<span class="c"># ^ = Control</span>
<span class="c"># ~ = Option</span>
<span class="c"># $ = Shift</span>
<span class="nv">shortcut</span><span class="o">=</span><span class="s1">'@~^e'</span>
<span class="c"># Set shortcut for 1 selected item</span>
<span class="nb">echo</span> <span class="s2">"Setting shortcut for 1 item"</span>
defaults write com.apple.Photos NSUserKeyEquivalents <span class="nt">-dict-add</span> <span class="s2">"Export Unmodified Original For 1 Photo"</span> <span class="s2">"</span><span class="nv">$shortcut</span><span class="s2">"</span>
defaults write com.apple.Photos NSUserKeyEquivalents <span class="nt">-dict-add</span> <span class="s2">"Export Unmodified Original For 1 Video"</span> <span class="s2">"</span><span class="nv">$shortcut</span><span class="s2">"</span>
<span class="c"># Set shortcut for 2-20 selected items</span>
<span class="nv">objects</span><span class="o">=(</span>Photos Videos Items<span class="o">)</span>
<span class="k">for </span>i <span class="k">in</span> <span class="o">{</span>2..20<span class="o">}</span>
<span class="k">do
</span><span class="nb">echo</span> <span class="s2">"Setting shortcut for </span><span class="nv">$i</span><span class="s2"> items"</span>
<span class="k">for </span>object <span class="k">in</span> <span class="s2">"</span><span class="k">${</span><span class="nv">objects</span><span class="p">[@]</span><span class="k">}</span><span class="s2">"</span>
<span class="k">do
</span>defaults write com.apple.Photos NSUserKeyEquivalents <span class="nt">-dict-add</span> <span class="s2">"Export Unmodified Originals For </span><span class="nv">$i</span><span class="s2"> </span><span class="nv">$object</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$shortcut</span><span class="s2">"</span>
<span class="k">done
done</span>
<span class="c"># Use this command to verify the result:</span>
<span class="c"># defaults read com.apple.Photos NSUserKeyEquivalents</span>
</code></pre></div>
<p>The script is <a href="https://gist.github.com/ole/2ddddd45098c1acf0cdc969714ae7156">also available on GitHub</a>.</p>
<p>Usage:</p>
<ol>
<li>Quit Photos.app.</li>
<li>Run the script. Feel free to change the key combo or count higher than 20.</li>
<li>Open Photos.app.</li>
</ol>
<div class="figure-container">
<figure style="max-width: 715px;" class="no-border">
<a href="https://oleb.net/media/2023-03-21-macos-system-settings-keyboard-shortcuts-1430px.png">
<img src="https://oleb.net/media/2023-03-21-macos-system-settings-keyboard-shortcuts-1430px.png" alt="Screenshot of the Keyboard Shortcuts window in System Settings in macOS 13.2, listing a bunch of custom keyboard shortcuts for Photos.app.">
</a>
</figure>
</div>
<div class="box">
<p><strong>Note: There’s a bug in Photos.app on macOS 13.2</strong> (and at least some earlier versions). Custom keyboard shortcuts don’t work until you’ve opened the menu of the respective command at least once. So you must manually open the File > Export once before the shortcut will work. (For Apple folks: FB11967573.)</p>
</div>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p>I still write Bash scripts because <a href="https://www.shellcheck.net/">Shellcheck</a> doesn’t support Zsh. <a href="https://oleb.net/2023/photos-keyboard-shortcuts/#fnref:1" class="reversefootnote" role="doc-backlink">↩︎</a></p>
</li>
</ol>
</div>
https://oleb.net/2023/alfred-swift-evolution/
Swift Evolution proposals in Alfred
2023-03-09T22:33:14Z
2023-03-09T22:33:14Z
Ole Begemann
<p>I rarely participate actively in the Swift Evolution process, but I frequently refer to <a href="https://www.swift.org/swift-evolution/">evolution proposals</a> for my work, often multiple times per week. The proposals aren’t always easy to read, but they’re the most comprehensive (and sometimes only) documentation we have for many Swift features.</p>
<p>For years, my tool of choice for searching Swift Evolution proposals has been <a href="https://github.com/attaswift/alfred-swift-evolution">Karoy Lorentey’s swift-evolution workflow</a> for <a href="https://www.alfredapp.com/">Alfred</a>.</p>
<p>The workflow broke recently due to data format changes. Karoy was kind enough to add me as a maintainer so I could fix it.</p>
<div class="figure-container">
<figure style="max-width: 740px;">
<a href="https://oleb.net/media/2023-03-09-Alfred-Swift-Evolution-workflow.jpg">
<img src="https://oleb.net/media/2023-03-09-Alfred-Swift-Evolution-workflow.jpg" alt="Alfred window on macOS displaying a text field containing 'se collection'. Below it is a list of 9 Swift Evolution proposals matching the search query. The sixth list item is selected.">
</a>
</figure>
</div>
<p><a href="https://github.com/attaswift/alfred-swift-evolution/releases/tag/v2.1.0">The new version 2.1.0 is now available on GitHub</a>. Download the <code>.alfredworkflow</code> file and double-click to install. Besides the fix, the update has a few other improvements:</p>
<ul>
<li>The proposal title is now displayed more prominently.</li>
<li>New actions to copy the proposal title (hold down <kbd>Command</kbd>) or copy it as a Markdown link (hold down <kbd>Shift</kbd> + <kbd>Command</kbd>).</li>
<li>The script forwards the main metadata of the selected proposal (id, title, status, URL) to Alfred. If you want to extend the workflow with your own actions, you can refer to these variables.</li>
</ul>
https://oleb.net/2023/catch-error-code/
Pattern matching on error codes
2023-02-27T19:32:22Z
2023-02-27T19:32:22Z
Ole Begemann
<p><strong>Foundation overloads the pattern matching operator <code>~=</code> to enable matching against error codes in <code>catch</code> clauses.</strong></p>
<p><code>catch</code> clauses in Swift support pattern matching, using the same <a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/patterns/">patterns</a> you’d use in a <code>case</code> clause inside a <code>switch</code> or in an <code>if case …</code> statement. For example, to handle a <a href="https://developer.apple.com/documentation/foundation/cocoaerror/2506537-filereadnosuchfile">file-not-found error</a> you might write:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">import</span> <span class="kt">Foundation</span>
<span class="k">do</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">fileURL</span> <span class="o">=</span> <span class="kt">URL</span><span class="p">(</span><span class="nv">filePath</span><span class="p">:</span> <span class="s">"/abc"</span><span class="p">)</span> <span class="c1">// non-existent file</span>
<span class="k">let</span> <span class="nv">data</span> <span class="o">=</span> <span class="k">try</span> <span class="kt">Data</span><span class="p">(</span><span class="nv">contentsOf</span><span class="p">:</span> <span class="n">fileURL</span><span class="p">)</span>
<span class="p">}</span> <span class="_hl"><span class="k">catch</span> <span class="k">let</span> <span class="nv">error</span> <span class="k">as</span> <span class="kt">CocoaError</span> <span class="k">where</span> <span class="n">error</span><span class="o">.</span><span class="n">code</span> <span class="o">==</span> <span class="o">.</span><span class="n">fileReadNoSuchFile</span></span> <span class="p">{</span>
<span class="nf">print</span><span class="p">(</span><span class="s">"File doesn't exist"</span><span class="p">)</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
<span class="nf">print</span><span class="p">(</span><span class="s">"Other error: </span><span class="se">\(</span><span class="n">error</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div>
<p>This binds a value of type <a href="https://developer.apple.com/documentation/foundation/cocoaerror"><code>CocoaError</code></a> to the variable <code>error</code> and then uses a <code>where</code> clause to check the specific error code.</p>
<p>However, if you don’t need access to the complete error instance, there’s a shorter way to write this, matching directly against the error code:</p>
<div class="highlight"><pre class="highlight diff"><code> let data = try Data(contentsOf: fileURL)
<span class="gd">- } catch let error as CocoaError where error.code == .fileReadNoSuchFile {
</span><span class="gi">+ } catch CocoaError.fileReadNoSuchFile {
</span> print("File doesn't exist")
</code></pre></div>
<h2 id="foundation-overloads-">Foundation overloads <code>~=</code>
</h2>
<p>I was wondering why this shorter syntax works. Is there some special compiler magic for pattern matching against error codes of <a href="https://developer.apple.com/documentation/foundation/nserror"><code>NSError</code></a> instances? Turns out: no, the answer is much simpler. Foundation includes an overload for <a href="https://developer.apple.com/documentation/swift/~=(_:_:)">the pattern matching operator <code>~=</code></a> that matches error values against error codes.<sup id="fnref:1" role="doc-noteref"><a href="https://oleb.net/2023/catch-error-code/#fn:1" class="footnote" rel="footnote">1</a></sup></p>
<p>The implementation looks something like this:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">public</span> <span class="kd">func</span> <span class="o">~=</span> <span class="p">(</span><span class="nv">code</span><span class="p">:</span> <span class="kt">CocoaError</span><span class="o">.</span><span class="kt">Code</span><span class="p">,</span> <span class="nv">error</span><span class="p">:</span> <span class="n">any</span> <span class="kt">Error</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Bool</span> <span class="p">{</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">error</span> <span class="o">=</span> <span class="n">error</span> <span class="k">as?</span> <span class="kt">CocoaError</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="kc">false</span> <span class="p">}</span>
<span class="k">return</span> <span class="n">error</span><span class="o">.</span><span class="n">code</span> <span class="o">==</span> <span class="n">code</span>
<span class="p">}</span>
</code></pre></div>
<p>The actual code in Foundation is a little more complex because it goes through a hidden protocol named <code>_ErrorCodeProtocol</code>, but that’s not important. You can check out the code in the Foundation repository: <a href="https://github.com/apple/swift-corelibs-foundation/blob/6e3a6ac9069f05301e63a93ed365713faf2c5d10/Darwin/Foundation-swiftoverlay/NSError.swift#L503-L510">Darwin version</a>, <a href="https://github.com/apple/swift-corelibs-foundation/blob/6e3a6ac9069f05301e63a93ed365713faf2c5d10/Sources/Foundation/NSError.swift#L596-L603">swift-corelibs-foundation version</a>.</p>
<p>This matching on error codes is available for <a href="https://developer.apple.com/documentation/foundation/cocoaerror"><code>CocoaError</code></a>, <a href="https://developer.apple.com/documentation/foundation/urlerror"><code>URLError</code></a>, <a href="https://developer.apple.com/documentation/foundation/posixerror"><code>POSIXError</code></a>, and <a href="https://developer.apple.com/documentation/foundation/macherror"><code>MachError</code></a> (and possibly more types in other Apple frameworks, I haven’t checked).</p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p>I wrote about the <code>~=</code> operator before, way back in 2015(!): <a href="https://oleb.net/blog/2015/09/swift-pattern-matching/">Pattern matching in Swift</a> and <a href="https://oleb.net/blog/2015/09/more-pattern-matching-examples/">More pattern matching examples</a>. <a href="https://oleb.net/2023/catch-error-code/#fnref:1" class="reversefootnote" role="doc-backlink">↩︎</a></p>
</li>
</ol>
</div>
https://oleb.net/2023/double-fine-adventure/
You should watch Double Fine Adventure
2023-01-31T18:39:27Z
2023-01-31T18:39:27Z
Ole Begemann
<p>I know I’m almost a decade late to this party, but <a href="https://xkcd.com/1053/">I’m probably not the only one</a>, so here goes.</p>
<p><em>Double Fine Adventure</em> was <a href="https://www.kickstarter.com/projects/doublefine/double-fine-adventure">a wildly successful 2012 Kickstarter project</a> to crowdfund the development of a point-and-click adventure game and, crucially, to document its development on video. The resulting game <a href="https://www.doublefine.com/games/broken-age">Broken Age</a> was eventually released in two parts in 2014 and 2015. Broken Age is a beautiful game and I recommend you try it. It’s available for lots of platforms and is pretty cheap (10–15 euros/dollars or less). I played it on the Nintendo Switch, which worked very well.</p>
<div class="figure-container">
<figure style="max-width: 960px;">
<a href="https://oleb.net/media/2023-01-31-broken-age.jpg">
<img src="https://oleb.net/media/2023-01-31-broken-age.jpg" alt="Screenshot from Broken Age. A tall girl in a pink dress is talking to a shorter girl in a bird costume. They are standing on a cloud.">
</a>
<figcaption>
Broken Age.
</figcaption>
</figure>
</div>
<p>But the real gem to me was watching <a href="https://www.youtube.com/playlist?list=PLIhLvue17Sd7F6pU2ByRRb0igiI-WKk3D">the 12.5-hour documentary on YouTube</a>. A video production team followed the entire three-year development process from start to finish. It provides a refreshingly candid and transparent insight into “how the sausage is made”, including sensitive topics such as financial problems, layoffs, and long work hours. Throughout all the ups and downs there’s a wonderful sense of fun and camaraderie among the team at Double Fine, which made watching the documentary even more enjoyable to me than playing Broken Age. You can tell these people love working with each other. I highly recommend taking a look if you find this mildly interesting.</p>
<div class="figure-container">
<figure style="max-width: 960px;">
<a href="https://oleb.net/media/2023-01-31-double-fine-adventure.jpg">
<img src="https://oleb.net/media/2023-01-31-double-fine-adventure.jpg" alt="Four people sitting at a conference table in an office. The wall in the background is covered in pencil drawings.">
</a>
<figcaption>
The Double Fine Adventure documentary.
</figcaption>
</figure>
</div>
<p>The first major game spoilers don’t come until episode 15, so you can safely watch most of the documentary before playing the game (and this is how the original Kickstarter backers experienced it). However, I think it’s even more interesting to play the game first, or to experience both side-by-side. My suggestion: watch two or three episodes of the documentary. If you like it, start playing Broken Age alongside it.</p>
https://oleb.net/2022/swiftui-view-lifecycle/
Understanding SwiftUI view lifecycles
2022-12-15T20:52:46Z
2022-12-16T09:52:45Z
Ole Begemann
<p>I wrote an app called <a href="https://github.com/ole/swiftui-view-lifecycle">SwiftUI View Lifecycle</a>. The app allows you to observe how different SwiftUI constructs and containers affect a view’s lifecycle, including the lifetime of its state and when <code>onAppear</code> gets called. <a href="https://github.com/ole/swiftui-view-lifecycle">The code for the app is on GitHub</a>. It can be built for iOS and macOS.</p>
<div class="figure-container">
<figure style="max-width: 667px;" class="no-border">
<a href="https://oleb.net/media/2022-12-15-swiftui-view-lifecycle-ios.png">
<img src="https://oleb.net/media/2022-12-15-swiftui-view-lifecycle-ios-1334px.png" alt="iPhone screenshots of the SwiftUI View Lifecycle app">
</a>
</figure>
</div>
<div class="figure-container">
<figure style="max-width: 750px;" class="no-border">
<a href="https://oleb.net/media/2022-12-15-swiftui-view-lifecycle-mac.png">
<img src="https://oleb.net/media/2022-12-15-swiftui-view-lifecycle-mac-1500px.png" alt="Mac screenshot of the SwiftUI View Lifecycle app">
</a>
</figure>
</div>
<h1 id="the-view-tree-and-the-render-tree">The view tree and the render tree</h1>
<p>When we write SwiftUI code, we construct a view tree that consists of nested view <em>values</em>. Instances of the view tree are ephemeral: SwiftUI constantly destroys and recreates (parts of) the view tree as it processes state changes.</p>
<p>The view tree serves as a blueprint from which SwiftUI creates a second tree, which represents the actual view “objects” that are “on screen” at any given time (the “objects” could be actual <code>UIView</code> or <code>NSView</code> objects, but also other representations; the exact meaning of “on screen” can vary depending on context). <a href="https://twitter.com/chriseidhof/status/1593508702081916930">Chris Eidhof likes to call this second tree the <em>render tree</em></a> (the link points to a 3 minute video where Chris demonstrates this duality, highly recommended).</p>
<p>The render tree persists across state changes and is used by SwiftUI to establish view <em>identity</em>. When a state change causes a change in a view’s <em>value</em>, SwiftUI will find the corresponding view <em>object</em> in the render tree and update it in place, rather than recreating a new view object from scratch. This is of course key to making SwiftUI efficient, but the render tree has another important function: it controls the lifetimes of views and their state.</p>
<h1 id="view-lifecycles-and-state">View lifecycles and state</h1>
<p>We can define a view’s <em>lifetime</em> as the timespan it exists in the render tree. The lifetime begins with the insertion into the render tree and ends with the removal. Importantly, the lifetime extends to view state defined with <a href="https://developer.apple.com/documentation/swiftui/state"><code>@State</code></a> and <a href="https://developer.apple.com/documentation/swiftui/stateobject"><code>@StateObject</code></a>: when a view gets removed from the render tree, its state is lost; when the view gets inserted again later, the state will be recreated with its initial value.</p>
<p><a href="https://github.com/ole/swiftui-view-lifecycle">The SwiftUI View Lifecycle app</a> tracks three lifecycle events for a view and displays them as timestamps:</p>
<ul>
<li>@State = when the view’s state was created (equivalent to the start of the view’s lifetime)</li>
<li>onAppear = when <a href="https://developer.apple.com/documentation/swiftui/view/onappear(perform:)"><code>onAppear</code></a> was last called</li>
<li>onDisappear = when <a href="https://developer.apple.com/documentation/swiftui/view/ondisappear(perform:)"><code>onDisappear</code></a> was last called</li>
</ul>
<div class="figure-container">
<figure style="max-width: 331px;">
<a href="https://oleb.net/media/2022-12-15-swiftui-view-lifecycle-monitor.png">
<img src="https://oleb.net/media/2022-12-15-swiftui-view-lifecycle-monitor.png" alt="A table with three rows. @State: 1:26 ago. onAppear: 0:15 ago. onDisappear: 0:47 ago.">
</a>
<figcaption>
The lifecycle monitor view displays the timestamps when certain lifecycle events last occurred.
</figcaption>
</figure>
</div>
<p>The app allows you to observe these events in different contexts. As you click your way through the examples, you’ll notice that the timing of these events changes depending on the context a view is embedded in. For example:</p>
<ul>
<li>An <code>if</code>/<code>else</code> statement creates and destroys its child views every time the condition changes; state is not preserved.</li>
<li>A <a href="https://developer.apple.com/documentation/swiftui/scrollview"><code>ScrollView</code></a> eagerly inserts all of its children into the render tree, regardless of whether they’re inside the viewport or not. All children <em>appear</em> right away and never <em>disappear</em>.</li>
<li>A <a href="https://developer.apple.com/documentation/swiftui/list"><code>List</code></a> with dynamic content (using <a href="https://developer.apple.com/documentation/swiftui/foreach"><code>ForEach</code></a>) lazily inserts only the child views that are currently visible. But once a child view’s lifetime has started, the list will keep its state alive even when it gets scrolled offscreen again. <code>onAppear</code> and <code>onDisappear</code> get called repeatedly as views are scrolled into and out of the viewport.</li>
<li>A <a href="https://developer.apple.com/documentation/swiftui/navigationstack"><code>NavigationStack</code></a> calls <code>onAppear</code> and <code>onDisappear</code> as views are pushed and popped. State for parent levels in the stack is preserved when a child view is pushed.</li>
<li>A <a href="https://developer.apple.com/documentation/swiftui/tabview"><code>TabView</code></a> starts the lifetime of all child views right away, even the non-visible tabs. <code>onAppear</code> and <code>onDisappear</code> get called repeatedly as the user switches tabs, but the tab view keeps the state alive for all tabs.</li>
</ul>
<h1 id="lessons">Lessons</h1>
<p>Here are a few lessons to take away from this:</p>
<ul>
<li>Different container views may have different performance and memory usage behaviors, depending on how long they keep child views alive.</li>
<li>
<code>onAppear</code> isn’t necessarily called when the state is created. It can happen later (but never earlier).</li>
<li>
<code>onAppear</code> can be called multiple times in some container views. If you need a side effect to happen exactly once in a view’s lifetime, consider writing yourself an <code>onFirstAppear</code> helper, as shown by Ian Keen and Jordan Morgan in <a href="https://www.swiftjectivec.com/swiftui-run-code-only-once-versus-onappear-or-task/">Running Code Only Once in SwiftUI (2022-11-01)</a>.</li>
</ul>
<p>I’m sure you’ll find more interesting tidbits when you play with the app. Feedback is welcome!</p>
https://oleb.net/2022/clipped-hit-testing/
clipped() doesn’t affect hit testing
2022-11-24T18:30:58Z
2022-11-25T10:48:10Z
Ole Begemann
<p>The <a href="https://developer.apple.com/documentation/swiftui/view/clipped(antialiased:)"><code>clipped()</code></a> modifier in SwiftUI clips a view to its bounds, hiding any out-of-bounds content. But note that clipping doesn’t affect hit testing; the clipped view can still receive taps/clicks outside the visible area.</p>
<p>I tested this on iOS 16.1 and macOS 13.0.</p>
<h2 id="example">Example</h2>
<p>Here’s a 300×300 square, which we then constrain to a 100×100 frame. I also added a border around the outer frame to visualize the views:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kt">Rectangle</span><span class="p">()</span>
<span class="o">.</span><span class="nf">fill</span><span class="p">(</span><span class="o">.</span><span class="n">orange</span><span class="o">.</span><span class="n">gradient</span><span class="p">)</span>
<span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mi">300</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">300</span><span class="p">)</span>
<span class="c1">// Set view to 100×100 → renders out of bounds</span>
<span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mi">100</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">100</span><span class="p">)</span>
<span class="o">.</span><span class="nf">border</span><span class="p">(</span><span class="o">.</span><span class="n">blue</span><span class="p">)</span>
</code></pre></div>
<p>SwiftUI views don’t clip their content by default, hence the full 300×300 square remains visible. Notice the blue border that indicates the 100×100 outer frame:</p>
<div class="figure-container">
<figure style="max-width: 465px;">
<a href="https://oleb.net/media/2022-11-24-unclipped-rectangle.png">
<img src="https://oleb.net/media/2022-11-24-unclipped-rectangle.png" alt="Xcode preview displaying an orange square. A smaller square blue outline is centered in the orange square.">
</a>
</figure>
</div>
<p>Now let’s add <code>.clipped()</code> to clip the large square to the 100×100 frame. I also made the square tappable and added a button:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kt">VStack</span> <span class="p">{</span>
<span class="kt">Button</span><span class="p">(</span><span class="s">"You can't tap me!"</span><span class="p">)</span> <span class="p">{</span>
<span class="n">buttonTapCount</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="p">}</span>
<span class="o">.</span><span class="nf">buttonStyle</span><span class="p">(</span><span class="o">.</span><span class="n">borderedProminent</span><span class="p">)</span>
<span class="kt">Rectangle</span><span class="p">()</span>
<span class="o">.</span><span class="nf">fill</span><span class="p">(</span><span class="o">.</span><span class="n">orange</span><span class="o">.</span><span class="n">gradient</span><span class="p">)</span>
<span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mi">300</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">300</span><span class="p">)</span>
<span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mi">100</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">100</span><span class="p">)</span>
<span class="_hl"><span class="o">.</span><span class="nf">clipped</span><span class="p">()</span></span>
<span class="o">.</span><span class="n">onTapGesture</span> <span class="p">{</span>
<span class="n">rectTapCount</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>When you run this code, you’ll discover that the button isn’t tappable at all. This is because the (unclipped) square, despite not being fully visible, obscures the button and “steals” all taps.</p>
<div class="figure-container">
<figure style="max-width: 465px;">
<a href="https://oleb.net/media/2022-11-24-clipped-rectangle-visualized.png">
<img src="https://oleb.net/media/2022-11-24-clipped-rectangle-visualized.png" alt="Xcode preview displaying a blue button and a small orange square. A larger dashed orange outline covers both the smaller square and the button.">
</a>
<figcaption>
The dashed outline indicates the hit area of the orange square. The button isn’t tappable because it’s covered by the clipped view with respect to hit testing.
</figcaption>
</figure>
</div>
<h2 id="the-fix-contentshape">The fix: <code>.contentShape()</code>
</h2>
<p>The <a href="https://developer.apple.com/documentation/swiftui/view/contentshape(_:eofill:)"><code>contentShape(_:)</code></a> modifier defines the hit testing area for a view. By adding <code>.contentShape(Rectangle())</code> to the 100×100 frame, we limit hit testing to that area, making the button tappable again:</p>
<div class="highlight"><pre class="highlight swift"><code> <span class="kt">Rectangle</span><span class="p">()</span>
<span class="o">.</span><span class="nf">fill</span><span class="p">(</span><span class="o">.</span><span class="n">orange</span><span class="o">.</span><span class="n">gradient</span><span class="p">)</span>
<span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mi">300</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">300</span><span class="p">)</span>
<span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mi">100</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">100</span><span class="p">)</span>
<span class="_hl"><span class="o">.</span><span class="nf">contentShape</span><span class="p">(</span><span class="kt">Rectangle</span><span class="p">())</span></span>
<span class="o">.</span><span class="nf">clipped</span><span class="p">()</span>
</code></pre></div>
<p>Note that the order of <code>.contentShape(Rectangle())</code> and <code>.clipped()</code> could be swapped. The important thing is that <code>contentShape</code> is an (indirect) parent of the 100×100 frame modifier that defines the size of the hit testing area.</p>
<h2 id="video-demo">Video demo</h2>
<p>I made a short video that demonstrates the effect:</p>
<ul>
<li>Initially, taps on the button, or even on the surrounding whitespace, register as taps on the square.</li>
<li>The top switch toggles display of the square before clipping. This illustrates its hit testing area.</li>
<li>The second switch adds <code>.contentShape(Rectangle())</code> to limit hit testing to the visible area. Now tapping the button increments the button’s tap count.</li>
</ul>
<p>The full code for this demo is <a href="https://gist.github.com/ole/7dfad1b91a0bf7185c6ad6ff37ab8c1b">available on GitHub</a>.</p>
<div class="figure-container">
<figure style="max-width: 501px;">
<video controls="" width="501">
<source src="/media/2022-11-24-clipped-hit-testing-demo.mp4" type="video/mp4"></source>
</video>
<figcaption>
<a href="https://oleb.net/media/2022-11-24-clipped-hit-testing-demo.mp4">Download video</a>
</figcaption>
</figure>
</div>
<h2 id="summary">Summary</h2>
<p>The <code>clipped()</code> modifier doesn’t affect the clipped view’s hit testing region. The same is true for <a href="https://developer.apple.com/documentation/swiftui/view/clipshape(_:style:)"><code>clipShape(_:)</code></a>. It’s often a good idea to combine these modifiers with <code>.contentShape(Rectangle())</code> to bring the hit testing logic in sync with the UI.</p>
https://oleb.net/2022/animation-modifier-position/
When .animation animates more (or less) than it’s supposed to
2022-11-10T21:48:45Z
2022-11-16T12:14:24Z
Ole Begemann
<h3>On the positioning of the .animation modifier in the view tree, or: “Rendering” vs. “non-rendering” view modifiers</h3>
<p>The documentation for SwiftUI’s <a href="https://developer.apple.com/documentation/swiftui/view/animation(_:value:)"><code>animation</code> modifier</a> says:</p>
<blockquote>
<p>Applies the given animation to this view when the specified value changes.</p>
</blockquote>
<p>This sounds unambiguous to me: it sets the animation for “this view”, i.e. the part of the view tree that <code>.animation</code> is being applied to. This should give us complete control over which modifiers we want to animate, right? Unfortunately, it’s not that simple: it’s easy to run into situations where a view change inside an animated subtree doesn’t get animated, or vice versa.</p>
<h1 id="unsurprising-examples">Unsurprising examples</h1>
<p>Let me give you some examples, starting with those that do work as documented. I tested all examples on iOS 16.1 and macOS 13.0.</p>
<h2 id="sibling-views-can-have-different-animations">1. Sibling views can have different animations</h2>
<p>Independent subtrees of the view tree can be animated independently. In this example we have three sibling views, two of which are animated with different durations, and one that isn’t animated at all:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">struct</span> <span class="kt">Example1</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">flag</span><span class="p">:</span> <span class="kt">Bool</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">HStack</span><span class="p">(</span><span class="nv">spacing</span><span class="p">:</span> <span class="mi">40</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">Rectangle</span><span class="p">()</span>
<span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">80</span><span class="p">)</span>
<span class="o">.</span><span class="nf">foregroundColor</span><span class="p">(</span><span class="o">.</span><span class="n">green</span><span class="p">)</span>
<span class="o">.</span><span class="nf">scaleEffect</span><span class="p">(</span><span class="n">flag</span> <span class="p">?</span> <span class="mi">1</span> <span class="p">:</span> <span class="mf">1.5</span><span class="p">)</span>
<span class="_hl"><span class="o">.</span><span class="nf">animation</span><span class="p">(</span><span class="o">.</span><span class="nf">easeOut</span><span class="p">(</span><span class="nv">duration</span><span class="p">:</span> <span class="mf">0.5</span><span class="p">),</span> <span class="nv">value</span><span class="p">:</span> <span class="n">flag</span><span class="p">)</span></span>
<span class="kt">Rectangle</span><span class="p">()</span>
<span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">80</span><span class="p">)</span>
<span class="o">.</span><span class="nf">foregroundColor</span><span class="p">(</span><span class="n">flag</span> <span class="p">?</span> <span class="o">.</span><span class="nv">yellow</span> <span class="p">:</span> <span class="o">.</span><span class="n">red</span><span class="p">)</span>
<span class="o">.</span><span class="nf">rotationEffect</span><span class="p">(</span><span class="n">flag</span> <span class="p">?</span> <span class="o">.</span><span class="nv">zero</span> <span class="p">:</span> <span class="o">.</span><span class="nf">degrees</span><span class="p">(</span><span class="mi">45</span><span class="p">))</span>
<span class="_hl"><span class="o">.</span><span class="nf">animation</span><span class="p">(</span><span class="o">.</span><span class="nf">easeOut</span><span class="p">(</span><span class="nv">duration</span><span class="p">:</span> <span class="mf">2.0</span><span class="p">),</span> <span class="nv">value</span><span class="p">:</span> <span class="n">flag</span><span class="p">)</span></span>
<span class="kt">Rectangle</span><span class="p">()</span>
<span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">80</span><span class="p">)</span>
<span class="o">.</span><span class="nf">foregroundColor</span><span class="p">(</span><span class="n">flag</span> <span class="p">?</span> <span class="o">.</span><span class="nv">pink</span> <span class="p">:</span> <span class="o">.</span><span class="n">mint</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>The two <code>animation</code> modifiers each apply to their own subtree. They don’t interfere with each other and have no effect on the rest of the view hierarchy:</p>
<div class="figure-container">
<figure style="max-width: 932px;">
<video controls="" width="932">
<source src="/media/2022-11-10-SwiftUI-animation-example-1.mp4" type="video/mp4"></source>
</video>
<figcaption>
<a href="https://oleb.net/media/2022-11-10-SwiftUI-animation-example-1.mp4">Download video</a>
</figcaption>
</figure>
</div>
<h2 id="nested-animation-modifiers">2. Nested <code>animation</code> modifiers</h2>
<p>When two <code>animation</code> modifiers are nested in a single view tree such that one is an indirect parent of the other, the inner modifier can override the outer animation for its subviews. The outer animation applies to view modifiers that are placed between the two <code>animation</code> modifiers.</p>
<p>In this example we have one rectangle view with animated <a href="https://developer.apple.com/documentation/swiftui/view/scaleeffect(_:anchor:)-pmi7">scale</a> and <a href="https://developer.apple.com/documentation/swiftui/view/rotationeffect(_:anchor:)">rotation</a> effects. The outer animation applies to the entire subtree, including both effects. The inner <code>animation</code> modifier overrides the outer animation only for what’s nested below it in the view tree, i.e. the scale effect:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">struct</span> <span class="kt">Example2</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">flag</span><span class="p">:</span> <span class="kt">Bool</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">Rectangle</span><span class="p">()</span>
<span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">80</span><span class="p">)</span>
<span class="o">.</span><span class="nf">foregroundColor</span><span class="p">(</span><span class="o">.</span><span class="n">green</span><span class="p">)</span>
<span class="o">.</span><span class="nf">scaleEffect</span><span class="p">(</span><span class="n">flag</span> <span class="p">?</span> <span class="mi">1</span> <span class="p">:</span> <span class="mf">1.5</span><span class="p">)</span>
<span class="_hl"><span class="o">.</span><span class="nf">animation</span><span class="p">(</span><span class="o">.</span><span class="k">default</span><span class="p">,</span> <span class="nv">value</span><span class="p">:</span> <span class="n">flag</span><span class="p">)</span> <span class="c1">// inner</span></span>
<span class="o">.</span><span class="nf">rotationEffect</span><span class="p">(</span><span class="n">flag</span> <span class="p">?</span> <span class="o">.</span><span class="nv">zero</span> <span class="p">:</span> <span class="o">.</span><span class="nf">degrees</span><span class="p">(</span><span class="mi">45</span><span class="p">))</span>
<span class="_hl"><span class="o">.</span><span class="nf">animation</span><span class="p">(</span><span class="o">.</span><span class="k">default</span><span class="o">.</span><span class="nf">speed</span><span class="p">(</span><span class="mf">0.3</span><span class="p">),</span> <span class="nv">value</span><span class="p">:</span> <span class="n">flag</span><span class="p">)</span> <span class="c1">// outer</span></span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>As a result, the scale and rotation changes animate at different speeds:</p>
<div class="figure-container">
<figure style="max-width: 932px;">
<video controls="" width="932">
<source src="/media/2022-11-10-SwiftUI-animation-example-2.mp4" type="video/mp4"></source>
</video>
<figcaption>
<a href="https://oleb.net/media/2022-11-10-SwiftUI-animation-example-2.mp4">Download video</a>
</figcaption>
</figure>
</div>
<p>Note that we could also pass <code>.animation(nil, value: flag)</code> to selectively disable animations for a subtree, overriding a non-<code>nil</code> animation further up the view tree.</p>
<h2 id="animation-only-animates-its-children-with-exceptions">3. <code>animation</code> only animates its children (with exceptions)</h2>
<p>As a general rule, the <code>animation</code> modifier only applies to its subviews. In other words, views and modifiers that are direct or indirect <em>parents</em> of an <code>animation</code> modifier should not be animated. As we’ll see below, it doesn’t always work like that, but here’s an example where it does. This is a slight variation of the previous code snippet where I removed the outer <code>animation</code> modifier (and changed the color for good measure):</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">struct</span> <span class="kt">Example3</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">flag</span><span class="p">:</span> <span class="kt">Bool</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">Rectangle</span><span class="p">()</span>
<span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">80</span><span class="p">)</span>
<span class="o">.</span><span class="nf">foregroundColor</span><span class="p">(</span><span class="o">.</span><span class="n">orange</span><span class="p">)</span>
<span class="o">.</span><span class="nf">scaleEffect</span><span class="p">(</span><span class="n">flag</span> <span class="p">?</span> <span class="mi">1</span> <span class="p">:</span> <span class="mf">1.5</span><span class="p">)</span>
<span class="_hl"><span class="o">.</span><span class="nf">animation</span><span class="p">(</span><span class="o">.</span><span class="k">default</span><span class="p">,</span> <span class="nv">value</span><span class="p">:</span> <span class="n">flag</span><span class="p">)</span></span>
<span class="c1">// Don't animate the rotation</span>
<span class="o">.</span><span class="nf">rotationEffect</span><span class="p">(</span><span class="n">flag</span> <span class="p">?</span> <span class="o">.</span><span class="nv">zero</span> <span class="p">:</span> <span class="o">.</span><span class="nf">degrees</span><span class="p">(</span><span class="mi">45</span><span class="p">))</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>Recall that the order in which view modifiers are written in code is inverted with respect to the actual view tree hierarchy. Each view modifier is a new view that wraps the view it’s being applied to. So in our example, the scale effect is the child of the <code>animation</code> modifier, whereas the rotation effect is its parent. Accordingly, only the scale change gets animated:</p>
<div class="figure-container">
<figure style="max-width: 932px;">
<video controls="" width="932">
<source src="/media/2022-11-10-SwiftUI-animation-example-3.mp4" type="video/mp4"></source>
</video>
<figcaption>
<a href="https://oleb.net/media/2022-11-10-SwiftUI-animation-example-3.mp4">Download video</a>
</figcaption>
</figure>
</div>
<h1 id="surprising-examples">Surprising examples</h1>
<p>Now it’s time for the “fun” part. It turns out not all view modifiers behave as intuitively as <a href="https://developer.apple.com/documentation/swiftui/view/scaleeffect(_:anchor:)-pmi7"><code>scaleEffect</code></a> and <a href="https://developer.apple.com/documentation/swiftui/view/rotationeffect(_:anchor:)"><code>rotationEffect</code></a> when combined with the <code>animation</code> modifier.</p>
<h2 id="some-modifiers-dont-respect-the-rules">4. Some modifiers don’t respect the rules</h2>
<p>In this example we’re changing the color, size, and alignment of the rectangle. Only the size change should be animated, which is why we’ve placed the alignment and color mutations outside the <code>animation</code> modifier:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">struct</span> <span class="kt">Example4</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">flag</span><span class="p">:</span> <span class="kt">Bool</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="k">let</span> <span class="nv">size</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="n">flag</span> <span class="p">?</span> <span class="mi">80</span> <span class="p">:</span> <span class="mi">120</span>
<span class="kt">Rectangle</span><span class="p">()</span>
<span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="n">size</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="n">size</span><span class="p">)</span>
<span class="_hl"><span class="o">.</span><span class="nf">animation</span><span class="p">(</span><span class="o">.</span><span class="k">default</span><span class="p">,</span> <span class="nv">value</span><span class="p">:</span> <span class="n">flag</span><span class="p">)</span></span>
<span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">maxWidth</span><span class="p">:</span> <span class="o">.</span><span class="n">infinity</span><span class="p">,</span> <span class="nv">alignment</span><span class="p">:</span> <span class="n">flag</span> <span class="p">?</span> <span class="o">.</span><span class="nv">leading</span> <span class="p">:</span> <span class="o">.</span><span class="n">trailing</span><span class="p">)</span>
<span class="o">.</span><span class="nf">foregroundColor</span><span class="p">(</span><span class="n">flag</span> <span class="p">?</span> <span class="o">.</span><span class="nv">pink</span> <span class="p">:</span> <span class="o">.</span><span class="n">indigo</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>Unfortunately, this doesn’t work as intended, as all three changes are animated:</p>
<div class="figure-container">
<figure style="max-width: 932px;">
<video controls="" width="932">
<source src="/media/2022-11-10-SwiftUI-animation-example-4.mp4" type="video/mp4"></source>
</video>
<figcaption>
<a href="https://oleb.net/media/2022-11-10-SwiftUI-animation-example-4.mp4">Download video</a>
</figcaption>
</figure>
</div>
<p>It behaves as if the <code>animation</code> modifier were the outermost element of this view subtree.</p>
<h2 id="padding-and-border">5. <code>padding</code> and <code>border</code>
</h2>
<p>This one’s sort of the inverse of the previous example because a change we want to animate doesn’t get animated. The <code>padding</code> is a child of the <code>animation</code> modifier, so I’d expect changes to it to be animated, i.e. the border should grow and shrink smoothly:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">struct</span> <span class="kt">Example5</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">flag</span><span class="p">:</span> <span class="kt">Bool</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">Rectangle</span><span class="p">()</span>
<span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">80</span><span class="p">)</span>
<span class="o">.</span><span class="nf">padding</span><span class="p">(</span><span class="n">flag</span> <span class="p">?</span> <span class="mi">20</span> <span class="p">:</span> <span class="mi">40</span><span class="p">)</span>
<span class="_hl"><span class="o">.</span><span class="nf">animation</span><span class="p">(</span><span class="o">.</span><span class="k">default</span><span class="p">,</span> <span class="nv">value</span><span class="p">:</span> <span class="n">flag</span><span class="p">)</span></span>
<span class="o">.</span><span class="nf">border</span><span class="p">(</span><span class="o">.</span><span class="n">primary</span><span class="p">)</span>
<span class="o">.</span><span class="nf">foregroundColor</span><span class="p">(</span><span class="o">.</span><span class="n">cyan</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>But that’s not what happens:</p>
<div class="figure-container">
<figure style="max-width: 932px;">
<video controls="" width="932">
<source src="/media/2022-11-10-SwiftUI-animation-example-5.mp4" type="video/mp4"></source>
</video>
<figcaption>
<a href="https://oleb.net/media/2022-11-10-SwiftUI-animation-example-5.mp4">Download video</a>
</figcaption>
</figure>
</div>
<h2 id="font-modifiers">6. Font modifiers</h2>
<p>Font modifiers also behave seemingly erratic with respect to the <code>animation</code> modifier. In this example, we want to animate the font width, but not the size or weight (smooth text animation is a new feature in iOS 16):</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">struct</span> <span class="kt">Example6</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">flag</span><span class="p">:</span> <span class="kt">Bool</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="s">"Hello!"</span><span class="p">)</span>
<span class="o">.</span><span class="nf">fontWidth</span><span class="p">(</span><span class="n">flag</span> <span class="p">?</span> <span class="o">.</span><span class="nv">condensed</span> <span class="p">:</span> <span class="o">.</span><span class="n">expanded</span><span class="p">)</span>
<span class="_hl"><span class="o">.</span><span class="nf">animation</span><span class="p">(</span><span class="o">.</span><span class="k">default</span><span class="p">,</span> <span class="nv">value</span><span class="p">:</span> <span class="n">flag</span><span class="p">)</span></span>
<span class="o">.</span><span class="nf">font</span><span class="p">(</span><span class="o">.</span><span class="nf">system</span><span class="p">(</span>
<span class="nv">size</span><span class="p">:</span> <span class="n">flag</span> <span class="p">?</span> <span class="mi">40</span> <span class="p">:</span> <span class="mi">60</span><span class="p">,</span>
<span class="nv">weight</span><span class="p">:</span> <span class="n">flag</span> <span class="p">?</span> <span class="o">.</span><span class="nv">regular</span> <span class="p">:</span> <span class="o">.</span><span class="n">heavy</span><span class="p">)</span>
<span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>You guessed it, this doesn’t work as intended. Instead, all text properties animate smoothly:</p>
<div class="figure-container">
<figure style="max-width: 932px;">
<video controls="" width="932">
<source src="/media/2022-11-10-SwiftUI-animation-example-6.mp4" type="video/mp4"></source>
</video>
<figcaption>
<a href="https://oleb.net/media/2022-11-10-SwiftUI-animation-example-6.mp4">Download video</a>
</figcaption>
</figure>
</div>
<h1 id="why-does-it-work-like-this">Why does it work like this?</h1>
<p>In summary, the placement of the <code>animation</code> modifier in the view tree allows <em>some</em> control over which changes get animated, but it isn’t perfect. Some modifiers, such as <code>scaleEffect</code> and <code>rotationEffect</code>, behave as expected, whereas others (<code>frame</code>, <code>padding</code>, <code>foregroundColor</code>, <code>font</code>) are less controllable.</p>
<p>I don’t fully understand the rules, but the important factor seems to be if a view modifier actually “renders” something or not. For instance, <a href="https://developer.apple.com/documentation/swiftui/view/foregroundcolor(_:)"><code>foregroundColor</code></a> just writes a color into the environment; the modifier itself doesn’t draw anything. I suppose this is why its position with respect to <code>animation</code> is irrelevant:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kt">RoundedRectangle</span><span class="p">(</span><span class="nv">cornerRadius</span><span class="p">:</span> <span class="n">flag</span> <span class="p">?</span> <span class="mi">0</span> <span class="p">:</span> <span class="mi">40</span><span class="p">)</span>
<span class="o">.</span><span class="nf">animation</span><span class="p">(</span><span class="o">.</span><span class="k">default</span><span class="p">,</span> <span class="nv">value</span><span class="p">:</span> <span class="n">flag</span><span class="p">)</span>
<span class="c1">// Color change still animates, even though we’re outside .animation</span>
<span class="o">.</span><span class="nf">foregroundColor</span><span class="p">(</span><span class="n">flag</span> <span class="p">?</span> <span class="o">.</span><span class="nv">pink</span> <span class="p">:</span> <span class="o">.</span><span class="n">indigo</span><span class="p">)</span>
</code></pre></div>
<p>The rendering presumably takes place on the level of the <code>RoundedRectangle</code>, which reads the color from the environment. At this point the <code>animation</code> modifier is active, so SwiftUI will animate all changes that affect how the rectangle is rendered, regardless of where in the view tree they’re coming from.</p>
<p>The same explanation makes intuitive sense for the font modifiers in <a href="https://oleb.net/2022/animation-modifier-position/#font-modifiers">example 6</a>. The actual rendering, and therefore the animation, occurs on the level of the <code>Text</code> view. The various font modifiers affect how the text is drawn, but they don’t render anything themselves.</p>
<p>Similarly, <code>padding</code> and <code>frame</code> (including the frame’s alignment) are “non-rendering” modifiers too. They don’t use the environment, but they influence the layout algorithm, which ultimately affects the size and position of one or more “rendering” views, such as the rectangle in <a href="https://oleb.net/2022/animation-modifier-position/#some-modifiers-dont-respect-the-rules">example 4</a>. That rectangle sees a combined change in its geometry, but it can’t tell where the change came from, so it’ll animate the full geometry change.</p>
<p>In <a href="https://oleb.net/2022/animation-modifier-position/#padding-and-border">example 5</a>, the “rendering” view that’s affected by the padding change is the <code>border</code> (which is implemented as <a href="https://twitter.com/olebegemann/status/1579878430505852928">a stroked rectangle in an overlay</a>). Since the border is a parent of the <code>animation</code> modifier, its geometry change is not animated.</p>
<p>In contrast to <code>frame</code> and <code>padding</code>, <a href="https://developer.apple.com/documentation/swiftui/view/scaleeffect(_:anchor:)-pmi7"><code>scaleEffect</code></a> and <a href="https://developer.apple.com/documentation/swiftui/view/rotationeffect(_:anchor:)"><code>rotationEffect</code></a> are “rendering” modifiers. They apparently perform the animations themselves.</p>
<h1 id="conclusion">Conclusion</h1>
<p>SwiftUI views and view modifiers can be divided into “rendering“ and “non-rendering” groups (I wish I had better terms for these). In iOS 16/macOS 13, the placement of the <code>animation</code> modifier with respect to non-rendering modifiers is irrelevant for deciding if a change gets animated or not.</p>
<p>Non-rendering modifiers include (non-exhaustive list):</p>
<ul>
<li>Layout modifiers (<code>frame</code>, <code>padding</code>, <code>position</code>, <code>offset</code>)</li>
<li>Font modifiers (<code>font</code>, <code>bold</code>, <code>italic</code>, <code>fontWeight</code>, <code>fontWidth</code>)</li>
<li>Other modifiers that write data into the environment, e.g. <code>foregroundColor</code>, <code>foregroundStyle</code>, <code>symbolRenderingMode</code>, <code>symbolVariant</code>
</li>
</ul>
<p>Rendering modifiers include (non-exhaustive list):</p>
<ul>
<li>
<code>clipShape</code>, <code>cornerRadius</code>
</li>
<li>Geometry effects, e.g. <code>scaleEffect</code>, <code>rotationEffect</code>, <code>projectionEffect</code>
</li>
<li>Graphical effects, e.g. <code>blur</code>, <code>brightness</code>, <code>hueRotation</code>, <code>opacity</code>, <code>saturation</code>, <code>shadow</code>
</li>
</ul>
https://oleb.net/2022/xcode-14-mac-concurrency-bugs/
Xcode 14.0 generates wrong concurrency code for macOS targets
2022-10-12T19:12:17Z
2022-10-12T19:12:17Z
Ole Begemann
<p>Mac apps built with Xcode 14.0 and 14.0.1 may contain concurrency bugs because the Swift 5.7 compiler can generate invalid code when targeting the macOS 12.3 SDK. If you distribute Mac apps, you should build them with Xcode 13.4.1 until Xcode 14.1 is released.</p>
<p>Here’s what happened:</p>
<ol>
<li>
<p>Swift 5.7 implements <a href="https://github.com/apple/swift-evolution/blob/main/proposals/0338-clarify-execution-non-actor-async.md">SE-0338: Clarify the Execution of Non-Actor-Isolated Async Functions</a>, which introduces new rules how async functions hop between executors. Because of SE-0338, when compiling concurrency code, the Swift 5.7 compiler places executor hops in different places than Swift 5.6.</p>
</li>
<li>
<p>Some standard library functions need to opt out of the new rules. They are annotated with a new, unofficial attribute <a href="https://github.com/apple/swift/blob/d977dd101fa8da3a53c9b690e5232be8c5aafa28/docs/ReferenceGuides/UnderscoredAttributes.md#_unsafeinheritexecutor"><code>@_unsafeInheritExecutor</code></a>, which was introduced for this purpose. When the Swift 5.7 compiler sees this attribute, it generates different executor hops.</p>
</li>
<li>
<p>The attribute is only present in the Swift 5.7 standard library, i.e. in the iOS 16 and macOS 13 SDKs. This is fine for iOS because compiler version and the SDK’s standard library version match in Xcode 14.0. But for macOS targets, Xcode 14.0 uses the Swift 5.7 compiler with the standard library from Swift 5.6, which doesn’t contain the <code>@_unsafeInheritExecutor</code> attribute. This is what causes the bugs.</p>
<p>Note that the issue is caused purely by the version mismatch at compile-time. The standard library version used by the compiled app at run-time (which depends on the OS version the app runs on) isn’t relevant. As soon as Xcode 14.1 gets released with the macOS 13 SDK, the version mismatch will go away, and Mac targets built with Xcode 14.1 won’t exhibit these bugs.</p>
</li>
<li>
<p>Third-party developers had little chance of discovering the bug during the Xcode 14.0 beta phase because the betas ship with the new beta macOS SDK. The version mismatch occurs when the final Xcode release in September reverts back to the old macOS SDK to accommodate the different release schedules of iOS and macOS.</p>
</li>
</ol>
<h1 id="sources">Sources</h1>
<p>Breaking concurrency invariants is a serious issue, though I’m not sure how much of a problem this is in actual production apps. Here are all related bug reports that I know of:</p>
<ul>
<li><a href="https://forums.swift.org/t/concurrency-is-broken-in-xcode-14-for-macos/60294">Concurrency is broken in Xcode 14 for macOS (2022-09-14)</a></li>
<li><a href="https://github.com/apple/swift/issues/61485"><code>withUnsafeContinuation</code> can break actor isolation (2022-10-07)</a></li>
</ul>
<p>And explanations of the cause from John McCall of the Swift team at Apple:</p>
<p><a href="https://github.com/groue/Semaphore/discussions/2#discussioncomment-3826233">John McCall (2022-10-07)</a>:</p>
<blockquote>
<p>This guarantee is unfortunately broken with Xcode 14 when compiling for macOS because it’s shipping with an old macOS SDK that doesn’t declare that <code>withUnsafeContinuation</code> inherits its caller’s execution context. And yes, there is a related actor-isolation issue because of this bug. That will be fixed by the release of the new macOS SDK.</p>
</blockquote>
<p><a href="https://forums.swift.org/t/clarification-needed-on-unsafecontinuation-documentation/57803/16">John McCall (2022-10-07)</a>:</p>
<blockquote>
<p>Now, there is a bug in Xcode 14 when compiling for the macOS SDK because it ships with an old SDK. That bug doesn’t actually break any of the ordering properties above. It does, however, break Swift’s data isolation guarantees because it causes <code>withUnsafeContinuation</code>, when called from an actor-isolated context, to send a non-<code>Sendable</code> function to a non-isolated executor and then call it, which is completely against the rules. And in fact, if you turn strict sendability checking on when compiling against that SDK, you will get a diagnostic about calling <code>withUnsafeContinuation</code> because it thinks that you’re violating the rules (because <code>withUnsafeContinuation</code> doesn’t properly inherit the execution context of its caller).</p>
</blockquote>
<h1 id="poor-communication-from-apple">Poor communication from Apple</h1>
<p>What bugs me most about the situation is Apple’s poor communication. When the official, current release of your programming language ships with a broken compiler for one of your most important platforms, the least I’d expect is a big red warning at the top of the release notes. I can’t find any mention of this issue in the <a href="https://developer.apple.com/documentation/xcode-release-notes/xcode-14-release-notes">Xcode 14.0 release notes</a> or <a href="https://developer.apple.com/documentation/xcode-release-notes/xcode-14_0_1-release-notes">Xcode 14.0.1 release notes</a>, however.</p>
<p>Even better: the warning should be displayed prominently in Xcode, or Xcode 14.0 should outright refuse to build Mac apps. I’m sure the latter option isn’t practical for all sorts of reasons, although it sounds logical to me: if the only safe compiler/SDK combinations are either 5.6 with the macOS 12 SDK or 5.7 with the macOS 13 SDK, there shouldn’t be an official Xcode version that combines the 5.7 compiler with the macOS 12 SDK.</p>
https://oleb.net/2022/swiftui-task-mainactor/
Where View.task gets its main-actor isolation from
2022-10-11T16:41:34Z
2022-10-12T08:46:31Z
Ole Begemann
<p>SwiftUI’s <a href="https://developer.apple.com/documentation/swiftui/view/task(priority:_:)"><code>.task</code> modifier</a> inherits its actor context from the surrounding function. If you call <code>.task</code> inside a view’s <code>body</code> property, the async operation will run on <a href="https://developer.apple.com/documentation/swift/mainactor">the main actor</a> because <code>View.body</code> is (semi-secretly) annotated with <code>@MainActor</code>. However, if you call <code>.task</code> from a helper property or function that isn’t <code>@MainActor</code>-annotated, the async operation will run in the cooperative thread pool.</p>
<h1 id="example">Example</h1>
<p>Here’s an example. Notice the two <code>.task</code> modifiers in <code>body</code> and <code>helperView</code>. The code is identical in both, yet only one of them compiles — in <code>helperView</code>, the call to a main-actor-isolated function fails because we’re not on the main actor in that context:</p>
<div class="figure-container">
<figure style="max-width: 735px;">
<a href="https://oleb.net/media/xcode-error-expression-async-not-marked-with-await-1470px.png">
<img src="https://oleb.net/media/xcode-error-expression-async-not-marked-with-await-1470px.png" alt="Xcode showing the compiler diagnostic 'Expression is 'async' but is not marked with await'">
</a>
<figcaption>
We can call a main-actor-isolated function from inside <code>body</code>, but not from a helper property.
</figcaption>
</figure>
</div>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">import</span> <span class="kt">SwiftUI</span>
<span class="kd">@MainActor</span> <span class="kd">func</span> <span class="nf">onMainActor</span><span class="p">()</span> <span class="p">{</span>
<span class="nf">print</span><span class="p">(</span><span class="s">"on MainActor"</span><span class="p">)</span>
<span class="p">}</span>
<span class="kd">struct</span> <span class="kt">ContentView</span><span class="p">:</span> <span class="kt">View</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">VStack</span> <span class="p">{</span>
<span class="n">helperView</span>
<span class="kt">Text</span><span class="p">(</span><span class="s">"in body"</span><span class="p">)</span>
<span class="o">.</span><span class="n">task</span> <span class="p">{</span>
<span class="c1">// We can call a @MainActor func without await</span>
<span class="nf">onMainActor</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">var</span> <span class="nv">helperView</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="s">"in helperView"</span><span class="p">)</span>
<span class="o">.</span><span class="n">task</span> <span class="p">{</span>
<span class="c1">// ❗️ Error: Expression is 'async' but is not marked with 'await'</span>
<span class="nf">onMainActor</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<h1 id="why-does-it-work-like-this">Why does it work like this?</h1>
<p>This behavior is caused by two (semi-)hidden annotations in the SwiftUI framework:</p>
<ol>
<li>
<p>The <a href="https://developer.apple.com/documentation/swiftui/view"><code>View</code></a> protocol annotates its <a href="https://developer.apple.com/documentation/swiftui/view/body-swift.property"><code>body</code></a> property with <code>@MainActor</code>. This transfers to all conforming types.</p>
</li>
<li>
<p><a href="https://developer.apple.com/documentation/swiftui/view/task(priority:_:)"><code>View.task</code></a> annotates its <code>action</code> parameter with <a href="https://github.com/apple/swift/blob/main/docs/ReferenceGuides/UnderscoredAttributes.md#_inheritactorcontext"><code>@_inheritActorContext</code></a>, causing it to adopt the actor context from its use site.</p>
</li>
</ol>
<p>Sadly, none of these annotations are visible in the SwiftUI documentation, making it very difficult to understand what’s going on. The <code>@MainActor</code> annotation on <code>View.body</code> <em>is</em> present in Xcode’s generated Swift interface for SwiftUI (Jump to Definition of <code>View</code>), but that feature doesn’t work reliably for me, and as we’ll see, it doesn’t show the whole truth, either.</p>
<div class="figure-container">
<figure style="max-width: 791px;">
<a href="https://oleb.net/media/xcode-swiftui-generated-interface-view-body-1582px.png">
<img src="https://oleb.net/media/xcode-swiftui-generated-interface-view-body-1582px.png" alt="Xcode showing the generated interface for SwiftUI’s View protocol. The @MainActor annotation on View.body is selected.">
</a>
<figcaption>
<code>View.body</code> is annotated with <code>@MainActor</code> in Xcode’s generated interface for SwiftUI.
</figcaption>
</figure>
</div>
<h1 id="swiftuis-module-interface">SwiftUI’s module interface</h1>
<p>To really see the declarations the compiler sees, we need to look at SwiftUI’s <em>module interface</em> file. A module interface is like a <a href="https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html">header file</a> for Swift modules. It lists the module’s public declarations and even the implementations of inlinable functions. Module interfaces use normal Swift syntax and have the <code>.swiftinterface</code> file extension.</p>
<p>SwiftUI’s module interface is located at:</p>
<div class="highlight"><pre class="highlight plaintext"><code>[Path to Xcode.app]/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/SwiftUI.framework/Modules/SwiftUI.swiftmodule/arm64e-apple-ios.swiftinterface
</code></pre></div>
<p><small>
(There can be multiple <code>.swiftinterface</code> files in that directory, one per CPU architecture. Pick any one of them. Pro tip for viewing the file in Xcode: Editor > Syntax Coloring > Swift enables syntax highlighting.)
</small></p>
<p>Inside, you’ll find that <code>View.body</code> has the <code>@MainActor(unsafe)</code> attribute:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">@available</span><span class="p">(</span><span class="n">iOS</span> <span class="mf">13.0</span><span class="p">,</span> <span class="n">macOS</span> <span class="mf">10.15</span><span class="p">,</span> <span class="n">tvOS</span> <span class="mf">13.0</span><span class="p">,</span> <span class="n">watchOS</span> <span class="mf">6.0</span><span class="p">,</span> <span class="o">*</span><span class="p">)</span>
<span class="kd">@_typeEraser</span><span class="p">(</span><span class="kt">AnyView</span><span class="p">)</span> <span class="kd">public</span> <span class="kd">protocol</span> <span class="kt">View</span> <span class="p">{</span>
<span class="c1">// …</span>
<span class="kd">@SwiftUI</span><span class="o">.</span><span class="kt">ViewBuilder</span> <span class="kd">@_Concurrency</span><span class="o">.</span><span class="kt">MainActor</span><span class="p">(</span><span class="n">unsafe</span><span class="p">)</span> <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="k">Self</span><span class="o">.</span><span class="kt">Body</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>And you’ll find this declaration for <code>.task</code>, including the <code>@_inheritActorContext</code> attribute:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">@available</span><span class="p">(</span><span class="n">iOS</span> <span class="mf">15.0</span><span class="p">,</span> <span class="n">macOS</span> <span class="mf">12.0</span><span class="p">,</span> <span class="n">tvOS</span> <span class="mf">15.0</span><span class="p">,</span> <span class="n">watchOS</span> <span class="mf">8.0</span><span class="p">,</span> <span class="o">*</span><span class="p">)</span>
<span class="kd">extension</span> <span class="kt">SwiftUI</span><span class="o">.</span><span class="kt">View</span> <span class="p">{</span>
<span class="cp">#if compiler(>=5.3) && $AsyncAwait && $Sendable && $InheritActorContext</span>
<span class="kd">@inlinable</span> <span class="kd">public</span> <span class="kd">func</span> <span class="nf">task</span><span class="p">(</span>
<span class="nv">priority</span><span class="p">:</span> <span class="n">_Concurrency</span><span class="o">.</span><span class="kt">TaskPriority</span> <span class="o">=</span> <span class="o">.</span><span class="n">userInitiated</span><span class="p">,</span>
<span class="kd">@_inheritActorContext</span> <span class="n">_</span> <span class="nv">action</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="kd">@Sendable</span> <span class="p">()</span> <span class="k">async</span> <span class="o">-></span> <span class="kt">Swift</span><span class="o">.</span><span class="kt">Void</span>
<span class="p">)</span> <span class="o">-></span> <span class="kd">some</span> <span class="kt">SwiftUI</span><span class="o">.</span><span class="kt">View</span> <span class="p">{</span>
<span class="nf">modifier</span><span class="p">(</span><span class="nf">_TaskModifier</span><span class="p">(</span><span class="nv">priority</span><span class="p">:</span> <span class="n">priority</span><span class="p">,</span> <span class="nv">action</span><span class="p">:</span> <span class="n">action</span><span class="p">))</span>
<span class="p">}</span>
<span class="cp">#endif</span>
<span class="c1">// …</span>
<span class="p">}</span>
</code></pre></div>
<div class="figure-container">
<figure style="max-width: 864px;">
<a href="https://oleb.net/media/xcode-swiftui-swiftinterface-task-1728px.png">
<img src="https://oleb.net/media/xcode-swiftui-swiftinterface-task-1728px.png" alt="Xcode showing the declaration for the View.task method in the SwiftUI.swiftinterface file. The @_inheritActorContext annotation is selected.">
</a>
<figcaption>
SwiftUI’s module interface file shows the <code>@_inheritActorContext</code> annotatation on <code>View.task</code>.
</figcaption>
</figure>
</div>
<h1 id="putting-it-all-together">Putting it all together</h1>
<p>Armed with this knowledge, everything makes more sense:</p>
<ul>
<li>When used inside <code>body</code>, <code>task</code> inherits the <code>@MainActor</code> context from <code>body</code>.</li>
<li>When used outside of <code>body</code>, there is no implicit <code>@MainActor</code> annotation, so <code>task</code> will run its operation on the cooperative thread pool by default. (Unless the view contains an <code>@ObservedObject</code> or <code>@StateObject</code> property, which somehow makes the entire view <code>@MainActor</code>. But that’s a different topic.)</li>
</ul>
<p>The lesson: if you use helper properties or functions in your view, consider annotating them with <code>@MainActor</code> to get the same semantics as <code>body</code>.</p>
<p>By the way, note that the actor context only applies to code that is placed directly inside the async closure, as well as to synchronous functions the closure calls. Async functions choose their own execution context, so any call to an async function can switch to a different executor. For example, if you call <a href="https://developer.apple.com/documentation/foundation/urlsession/3767353-data"><code>URLSession.data(from:)</code></a> inside a main-actor-annotated function, the runtime will hop to the global cooperative executor to execute that method. See <a href="https://github.com/apple/swift-evolution/blob/main/proposals/0338-clarify-execution-non-actor-async.md">SE-0338: Clarify the Execution of Non-Actor-Isolated Async Functions</a> for the precise rules.</p>
<h1 id="on-apples-policy-to-hide-annotations-in-documentation">On Apple’s policy to hide annotations in documentation</h1>
<p>I understand Apple’s impetus not to show unofficial API or language features in the documentation lest developers get the preposterous idea to use these features in their own code!</p>
<p>But it makes understanding <em>so</em> much harder. Before I saw the annotations in the <code>.swiftinterface</code> file, the behavior of the code at the beginning of this article never made sense to me. Hiding the details makes things seem like magic when they actually aren’t. And that’s not good, either.</p>
https://oleb.net/2022/live-activity/
Experimenting with Live Activities
2022-08-03T16:50:39Z
2022-09-22T21:17:10Z
Ole Begemann
<p>iOS 16 beta 4 is the first SDK release <a href="https://developer.apple.com/news/?id=hi37aek8">that supports Live Activities</a>. A Live Activity is a widget-like view an app can place on your lock screen and update in real time. Examples where this can be useful include live sports scores or train departure times.</p>
<p>These are my notes on playing with the API and implementing my first Live Activity.</p>
<h2 id="a-bike-computer-on-your-lock-screen">A bike computer on your lock screen</h2>
<p>My Live Activity is a display for a bike computer that I’ve been developing with a group a friends. Here’s a video of it in action:</p>
<div class="figure-container">
<figure style="max-width: 640px;">
<video controls="" width="640">
<source src="/media/live-activity-bike-demo.mp4" type="video/mp4"></source>
</video>
<figcaption>
<a href="https://oleb.net/media/live-activity-bike-demo.mp4">Download video</a>
</figcaption>
</figure>
</div>
<p>And here with simulated data:</p>
<div class="figure-container">
<figure style="max-width: 540px;">
<video controls="" width="540">
<source src="/media/live-activity-simulated.mp4" type="video/mp4"></source>
</video>
<figcaption>
<a href="https://oleb.net/media/live-activity-simulated.mp4">Download video</a>
</figcaption>
</figure>
</div>
<p>I haven’t talked much about our bike computer project publicly yet; that will hopefully change someday. In short, a group of friends and I designed a little box that connects to your bike’s <a href="https://en.wikipedia.org/wiki/Hub_dynamo">hub dynamo</a>, measures speed and distance, and sends the data via Bluetooth to an iOS app. The app records all your rides and can also act as a live speedometer when mounted on your bike’s handlebar. It’s this last feature that I wanted to replicate in the Live Activity.</p>
<h2 id="follow-apples-guide">Follow Apple’s guide</h2>
<p>Adding a Live Activity to the app wasn’t hard. I found Apple’s guide <a href="https://developer.apple.com/documentation/activitykit/displaying-live-data-on-the-lock-screen-with-live-activities">Displaying live data on the Lock Screen with Live Activities</a> easy to follow and quite comprehensive.</p>
<h2 id="no-explicit-user-approval">No explicit user approval</h2>
<p>iOS doesn’t ask the user for approval when an app wants to show a Live Activity. I found this odd since it seems to invite developers to abuse the feature, but maybe it’s OK because of the foreground requirement (see below). Plus, users can disallow Live Activities on a per-app basis in Settings.</p>
<p>Users can dismiss an active Live Activity from the lock screen by swiping (like a notification).</p>
<p>Most apps will probably need to ask the user for notification permissions to update their Live Activities.</p>
<h2 id="the-app-must-be-in-the-foreground-to-start-an-activity">The app must be in the foreground to start an activity</h2>
<p>To start a Live Activity, an app must be open in the foreground. This isn’t ideal for the bike computer because the speedometer can’t appear magically on the lock screen when the user starts riding (even though iOS wakes up the app in the background at this point to deliver the Bluetooth events from the bike). The user has to open the app manually at least once.</p>
<p>On the other hand, this limitation may not be an issue for most use cases and will probably cut down on spamming/abuse significantly.</p>
<h2 id="the-app-must-keep-running-in-the-background-to-update-the-activity-or-use-push-notifications">The app must keep running in the background to update the activity (or use push notifications)</h2>
<p>As long as the app keeps running (in the foreground or background), it can update the Live Activity as often as it wants (I think). This is ideal for the bike computer as the app keeps running in the background processing Bluetooth events while the bike is in motion. I assume the same applies to other apps that can remain alive in the background, such as audio players or navigation apps doing continuous location monitoring.</p>
<p>Updating the Live Activity once per second was no problem in my testing, and I didn’t experience any rate limiting.</p>
<p>Most apps get suspended in the background, however. They must use push notifications to update their Live Activity (or <a href="https://developer.apple.com/documentation/backgroundtasks">background tasks</a> or some other mechanism to have the system wake you up). Apple introduced a new kind of push notification that is delivered directly to the Live Activity, bypassing the app altogether. I haven’t played with push notification updates, so I don’t know the benefits of using this method over sending a silent push notification to wake the app and updating the Live Activity from there. Probably less aggressive rate limiting?</p>
<h2 id="lock-screen-color-matching">Lock screen color matching</h2>
<p>I haven’t found a good way to match my Live Activity’s colors to the current system colors on the lock screen. By default, text in a Live Activity is black in light mode, whereas the built-in lock screen themes seem to favor white or other light text colors. If there is an API or environment value that allows apps to match the color style of the current lock screen, I haven’t found it. I experimented with various foreground styles, such as materials, without success.</p>
<p>I ended up hardcoding the foreground color, but I’m not satisfied with the result. Depending on the user’s lock screen theme, the Live Activity can look out of place.</p>
<div class="figure-container">
<figure style="max-width: 400px;" class="no-border">
<a href="https://oleb.net/media/live-activity-default-color.jpg">
<img src="https://oleb.net/media/live-activity-default-color.jpg" alt="The lock screen of an iPhone running iOS 16. The system text (clock, date) is in a light, whitish color. The Live Activity at the bottom of the screen has black text.">
</a>
<figcaption>
The default text color of a Live Activity in light mode is black. This doesn’t match most lock screen themes.
</figcaption>
</figure>
</div>
<h2 id="animations-cant-be-disabled">Animations can’t be disabled</h2>
<p><a href="https://developer.apple.com/documentation/activitykit/displaying-live-data-on-the-lock-screen-with-live-activities#Animate-content-updates">Apple’s guide</a> clearly states that developers have little control over animations in a Live Activity:</p>
<blockquote>
<p><strong>Animate content updates</strong></p>
<p>When you define the user interface of your Live Activity, the system ignores any animation modifiers — for example, <a href="https://developer.apple.com/documentation/SwiftUI/withAnimation(_:_:)"><code>withAnimation(_:_:)</code></a> and <a href="https://developer.apple.com/documentation/SwiftUI/View/animation(_:value:)"><code>animation(_:value:)</code></a> — and uses the system’s animation timing instead. However, the system performs some animation when the dynamic content of the Live Activity changes. Text views animate content changes with blurred content transitions, and the system animates content transitions for images and SF Symbols. If you add or remove views from the user interface based on content or state changes, views fade in and out. Use the following view transitions to configure these built-in transitions: <a href="https://developer.apple.com/documentation/SwiftUI/AnyTransition/opacity"><code>opacity</code></a>, <a href="https://developer.apple.com/documentation/SwiftUI/AnyTransition/move(edge:)"><code>move(edge:)</code></a>, <a href="https://developer.apple.com/documentation/SwiftUI/AnyTransition/slide"><code>slide</code></a>, <a href="https://developer.apple.com/documentation/SwiftUI/AnyTransition/push(from:)"><code>push(from:)</code></a>, or combinations of them. Additionally, request animations for timer text with <a href="https://developer.apple.com/documentation/SwiftUI/ContentTransition/numericText(countsDown:)"><code>numericText(countsDown:)</code></a>.</p>
</blockquote>
<p>It makes total sense to me that Apple doesn’t want developers to go crazy with animations on the lock screen, and perhaps having full control over animations also makes it easier for Apple to integrate Live Activities into the always-on display that’s probably coming on the next iPhone.</p>
<p>What surprised me is that I couldn’t find a way to <em>disable</em> the text change animations altogether. I find the blurred text transitions for the large speed value quite distracting and I think this label would look better without any animations. But no combination of <code>.animation(nil)</code>, <code>.contentTransition(.identity)</code>, and <code>.transition(.identity)</code> would do this.</p>
<h2 id="sharing-code-between-app-and-widget">Sharing code between app and widget</h2>
<p>A Live Activity is very much like a widget: the UI must live in your app’s widget extension. You start the Live Activity with code that runs in your app, though. Both targets (the app and the widget extension) need access to a common data type that represents the data the widget displays. You should have a third target (a framework or SwiftPM package) that contains such shared types and APIs and that the downstream targets import.</p>
<h2 id="availability-annotations">Availability annotations</h2>
<div class="update">
<p><strong>Update September 22, 2022:</strong> This limitation no longer applies. The iOS 16.1 SDK added the ability to have availability conditions in <a href="https://developer.apple.com/documentation/swiftui/widgetbundle"><code>WidgetBundle</code></a>. Source: <a href="https://twitter.com/luka_bernardi/status/1572000451775844352">Tweet from Luca Bernardi (2022-09-20)</a>.</p>
</div>
<p><code>WidgetBundle</code> apparently doesn’t support widgets with different minimum deployment targets. If your widget extension has a deployment target of iOS 14 or 15 for an existing widget and you now want to add a Live Activity, I’d expect your widget bundle to look like this:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">@main</span>
<span class="kd">struct</span> <span class="kt">MyWidgets</span><span class="p">:</span> <span class="kt">WidgetBundle</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">Widget</span> <span class="p">{</span>
<span class="kt">MyNormalWidget</span><span class="p">()</span>
<span class="c1">// Error: Closure containing control flow statement cannot</span>
<span class="c1">// be used with result builder 'WidgetBundleBuilder'</span>
<span class="k">if</span> <span class="kd">#available(iOSApplicationExtension 16.0, *)</span> <span class="p">{</span>
<span class="kt">MyLiveActivityWidget</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>But this doesn’t compile because <a href="https://developer.apple.com/documentation/swiftui/widgetbundlebuilder">the result builder type used by <code>WidgetBundle</code></a> doesn’t support availability conditions. I hope Apple fixes this.</p>
<p>This wasn’t a problem for me because our app didn’t have any widgets until now, so I just set the deployment target of the widget extension to iOS 16.0. If you have existing widgets and can’t require iOS 16 yet, a workaround is to add a second widget extension target just for the Live Activity. I haven’t tried this, but <a href="https://developer.apple.com/documentation/widgetkit/creating-a-widget-extension">WidgetKit explicitly supports having multiple widget extensions</a>, so it should work:</p>
<blockquote>
<p>Typically, you include all your widgets in a single widget extension, although your app can contain multiple extensions.</p>
</blockquote>
https://oleb.net/2022/how-mainactor-works/
How @MainActor works
2022-05-05T13:52:42Z
2022-05-06T17:47:57Z
Ole Begemann
<p><a href="https://developer.apple.com/documentation/swift/mainactor#"><code>@MainActor</code></a> is a Swift annotation to coerce a function to always run on the main thread <em>and</em> to enable the compiler to verify this. How does this work? In this article, I’m going to reimplement <code>@MainActor</code> in a slightly simplified form for illustration purposes, mainly to show how little “magic” there is to it. <a href="https://github.com/apple/swift/blob/main/stdlib/public/Concurrency/MainActor.swift">The code of the real implementation</a> in the Swift standard library is available in the Swift repository.</p>
<p><code>@MainActor</code> relies on two Swift features, one of them unofficial: global actors and custom executors.</p>
<h2 id="global-actors">Global actors</h2>
<p><code>MainActor</code> is a <a href="https://github.com/apple/swift-evolution/blob/main/proposals/0316-global-actors.md">global actor</a>. That is, it provides a single actor instance that is shared between all places in the code that are annotated with <code>@MainActor</code>.</p>
<p>All global actors must implement the <code>shared</code> property that’s defined in the <a href="https://developer.apple.com/documentation/swift/globalactor"><code>GlobalActor</code> protocol</a> (every global actor implicitly conforms to this protocol):</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">@globalActor</span>
<span class="kd">final</span> <span class="kd">actor</span> <span class="kt">MyMainActor</span> <span class="p">{</span>
<span class="c1">// Requirements from the implicit GlobalActor conformance</span>
<span class="kd">typealias</span> <span class="kt">ActorType</span> <span class="o">=</span> <span class="kt">MyMainActor</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">shared</span><span class="p">:</span> <span class="kt">ActorType</span> <span class="o">=</span> <span class="kt">MyMainActor</span><span class="p">()</span>
<span class="c1">// Don’t allow others to create instances</span>
<span class="kd">private</span> <span class="nf">init</span><span class="p">()</span> <span class="p">{}</span>
<span class="p">}</span>
</code></pre></div>
<p>At this point, we have a global actor that has the same semantics as any other actor. That is, functions annotated with <code>@MyMainActor</code> will run on a thread in the cooperative thread pool managed by the Swift runtime. To move the work to the main thread, we need another concept, custom executors.</p>
<h2 id="executors">Executors</h2>
<p>A bit of terminology:</p>
<ul>
<li>The compiler splits async code into <strong>jobs</strong>. A job roughly corresponds to the code from one <code>await</code> (= potential suspension point) to the next.</li>
<li>The runtime submits each job to an <strong>executor</strong>. The executor is the object that decides in which order and in which context (i.e. which thread or dispatch queue) to run the jobs.</li>
</ul>
<p>Swift ships with two built-in executors: the default concurrent executor, used for “normal”, non-actor-isolated async functions, and a default serial executor. Every actor instance has its own instance of this default serial executor and runs its code on it. Since the serial executor, like a serial dispatch queue, only runs a single job at a time, this prevents concurrent accesses to the actor’s state.</p>
<h3 id="custom-executors">Custom executors</h3>
<p>As of Swift 5.6, executors are an implementation detail of Swift’s concurrency system, but it’s almost certain that they will become an official feature fairly soon. Why? Because it can sometimes be useful to have more control over the execution context of async code. Some examples are listed in <a href="https://github.com/rjmccall/swift-evolution/blob/custom-executors/proposals/0000-custom-executors.md">a draft proposal for allowing developers to implement custom executors</a> that was first pitched in February 2021 but then didn’t make the cut for Swift 5.5.</p>
<p><code>@MainActor</code> already uses the unofficial ability for an actor to provide a custom executor, and we’re going to do the same for our reimplementation. A serial executor that runs its job on the main dispatch queue is implemented as follows. The interesting bit is the <code>enqueue</code> method, where we tell the job to run on the main dispatch queue:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">final</span> <span class="kd">class</span> <span class="kt">MainExecutor</span><span class="p">:</span> <span class="kt">SerialExecutor</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">asUnownedSerialExecutor</span><span class="p">()</span> <span class="o">-></span> <span class="kt">UnownedSerialExecutor</span> <span class="p">{</span>
<span class="kt">UnownedSerialExecutor</span><span class="p">(</span><span class="nv">ordinary</span><span class="p">:</span> <span class="k">self</span><span class="p">)</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">enqueue</span><span class="p">(</span><span class="n">_</span> <span class="nv">job</span><span class="p">:</span> <span class="kt">UnownedJob</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">DispatchQueue</span><span class="o">.</span><span class="n">main</span><span class="o">.</span><span class="k">async</span> <span class="p">{</span>
<span class="n">job</span><span class="o">.</span><span class="nf">_runSynchronously</span><span class="p">(</span><span class="nv">on</span><span class="p">:</span> <span class="k">self</span><span class="o">.</span><span class="nf">asUnownedSerialExecutor</span><span class="p">())</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>We’re responsible for keeping an instance of the executor alive, so let’s store it in a global:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">private</span> <span class="k">let</span> <span class="nv">mainExecutor</span> <span class="o">=</span> <span class="kt">MainExecutor</span><span class="p">()</span>
</code></pre></div>
<p>Finally, we need to tell our global actor to use the new executor:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">import</span> <span class="kt">Dispatch</span>
<span class="kd">@globalActor</span>
<span class="kd">final</span> <span class="kd">actor</span> <span class="kt">MyMainActor</span> <span class="p">{</span>
<span class="c1">// ...</span>
<span class="c1">// Requirement from the implicit GlobalActor conformance</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">sharedUnownedExecutor</span><span class="p">:</span> <span class="kt">UnownedSerialExecutor</span> <span class="p">{</span>
<span class="n">mainExecutor</span><span class="o">.</span><span class="nf">asUnownedSerialExecutor</span><span class="p">()</span>
<span class="p">}</span>
<span class="c1">// Requirement from the implicit Actor conformance</span>
<span class="kd">nonisolated</span> <span class="k">var</span> <span class="nv">unownedExecutor</span><span class="p">:</span> <span class="kt">UnownedSerialExecutor</span> <span class="p">{</span>
<span class="n">mainExecutor</span><span class="o">.</span><span class="nf">asUnownedSerialExecutor</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>That’s all there is to reimplement the basics of <code>@MainActor</code>.</p>
<h2 id="conclusion">Conclusion</h2>
<p><a href="https://gist.github.com/ole/10a832ab2388163099841d683cfed102">The full code is on GitHub</a>, including a usage example to demonstrate that the <code>@MyMainActor</code> annotations work.</p>
<p><a href="https://github.com/rjmccall/swift-evolution/blob/custom-executors/proposals/0000-custom-executors.md">John McCall’s draft proposal for custom executors</a> is worth reading, particularly <a href="https://github.com/rjmccall/swift-evolution/blob/custom-executors/proposals/0000-custom-executors.md#philosophy-of-thread-usage">the philosophy section</a>. It’s an easy-to-read summary of some of the design principles behind Swift’s concurrency system:</p>
<blockquote>
<p>Swift’s concurrency design sees system threads as expensive and rather precious resources. …</p>
<p>It is therefore best if the system allocates a small number of threads — just enough to saturate the available cores — and for those threads [to] only block for extended periods when there is no pending work in the program. Individual functions cannot effectively make this decision about blocking, because they lack a holistic understanding of the state of the program. Instead, the decision must be made by a centralized system which manages most of the execution resources in the program.</p>
<p>This basic philosophy of how best to use system threads drives some of the most basic aspects of Swift’s concurrency design. In particular, the main reason to add async functions is to make it far easier to write functions that, unlike standard functions, will reliably abandon a thread when they need to wait for something to complete.</p>
</blockquote>
<p><a href="https://github.com/rjmccall/swift-evolution/blob/custom-executors/proposals/0000-custom-executors.md#the-default-global-concurrent-executor">And</a>:</p>
<blockquote>
<p>The default concurrent executor is used to run jobs that don’t need to run somewhere more specific. It is based on a fixed-width thread pool that scales to the number of available cores. Programmers therefore do not need to worry that creating too many jobs at once will cause a thread explosion that will starve the program of resources.</p>
</blockquote>
https://oleb.net/2022/attributedstring-codable/
AttributedString’s Codable format and what it has to do with Unicode
2022-04-27T13:28:03Z
2022-04-27T13:28:03Z
Ole Begemann
<p>Here’s a simple <a href="https://developer.apple.com/documentation/foundation/attributedstring"><code>AttributedString</code></a> with some formatting:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">import</span> <span class="kt">Foundation</span>
<span class="k">let</span> <span class="nv">str</span> <span class="o">=</span> <span class="k">try!</span> <span class="kt">AttributedString</span><span class="p">(</span>
<span class="nv">markdown</span><span class="p">:</span> <span class="s">"Café **Sol**"</span><span class="p">,</span>
<span class="nv">options</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">interpretedSyntax</span><span class="p">:</span> <span class="o">.</span><span class="n">inlineOnly</span><span class="p">)</span>
<span class="p">)</span>
</code></pre></div>
<p><code>AttributedString</code> is <a href="https://developer.apple.com/documentation/swift/codable"><code>Codable</code></a>. If your task was to design the encoding format for an attributed string, what would you come up with? Something like this seems reasonable (in JSON with comments):</p>
<div class="highlight"><pre class="highlight json-doc"><code><span class="p">{</span><span class="w">
</span><span class="nl">"text"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Café Sol"</span><span class="p">,</span><span class="w">
</span><span class="nl">"runs"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="c1">// start..<end in Character offsets</span><span class="w">
</span><span class="nl">"range"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">5</span><span class="p">,</span><span class="w"> </span><span class="mi">8</span><span class="p">],</span><span class="w">
</span><span class="nl">"attrs"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"strong"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>
<p>This stores the text alongside an array of runs of formatting attributes. Each run consists of a character range and an attribute dictionary.</p>
<h1 id="unicode-is-complicated">Unicode is complicated</h1>
<p>But this format is bad and can break in various ways. The problem is that the character offsets that define the runs aren’t guaranteed to be stable. The definition of what constitutes a <a href="https://developer.apple.com/documentation/swift/character"><code>Character</code></a>, i.e. a user-perceived character, or a <a href="https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries">Unicode grapheme cluster</a>, can and does change in new Unicode versions. If we decoded an attributed string that had been serialized</p>
<ul>
<li>on a different OS version (before Swift 5.6, Swift used the OS’s Unicode library for determining character boundaries),</li>
<li>or by code compiled with a different Swift version (since Swift 5.6, <a href="https://github.com/apple/swift/issues/52935">Swift uses its own grapheme breaking algorithm</a> that will be updated alongside the Unicode standard)<sup id="fnref:unicodeversion" role="doc-noteref"><a href="https://oleb.net/2022/attributedstring-codable/#fn:unicodeversion" class="footnote" rel="footnote">1</a></sup>, the character ranges might no longer represent the original intent, or even become invalid.</li>
</ul>
<h2 id="normalization-forms">Normalization forms</h2>
<p>So let’s use UTF-8 byte offsets for the ranges, I hear you say. This avoids the first issue but still isn’t safe, because some characters, such as the é in the example string, have more than one representation in Unicode: it can be either the standalone character é (<a href="https://oleb.net/2022/attributedstring-codable/codepoints.net/U+00E9">Latin small letter e with acute</a>) or the combination of e + ◌́ (<a href="https://codepoints.net/U+0301">Combining acute accent</a>). The Unicode standard calls these variants <a href="https://unicode.org/reports/tr15/">normalization forms</a>.<sup id="fnref:normalize" role="doc-noteref"><a href="https://oleb.net/2022/attributedstring-codable/#fn:normalize" class="footnote" rel="footnote">2</a></sup> The first form needs 2 bytes in UTF-8, whereas the second uses 3 bytes, so subsequent ranges would be off by one if the string and the ranges used different normalization forms.</p>
<p>Now in theory, the string itself and the ranges should use the same normalization form upon serialization, avoiding the problem. But this is almost impossible to guarantee if the serialized data passes through other systems that may (inadvertently or not) change the Unicode normalization of the strings that pass through them.</p>
<p>A safer option would be to store the text not as a string but as a blob of UTF-8 bytes, because serialization/networking/storage layers generally don’t mess with binary data. But even then you’d have to be careful in the encoding and decoding code to apply the formatting attributes before any normalization takes place. Depending on how your programming language handles Unicode, this may not be so easy.</p>
<h1 id="foundations-solution">Foundation’s solution</h1>
<p>The people on the Foundation team know all this, of course, and chose a better encoding format for <code>Attributed String</code>. Let’s take a look.<sup id="fnref:decode" role="doc-noteref"><a href="https://oleb.net/2022/attributedstring-codable/#fn:decode" class="footnote" rel="footnote">3</a></sup></p>
<div class="highlight"><pre class="highlight swift"><code><span class="k">let</span> <span class="nv">encoder</span> <span class="o">=</span> <span class="kt">JSONEncoder</span><span class="p">()</span>
<span class="n">encoder</span><span class="o">.</span><span class="n">outputFormatting</span> <span class="o">=</span> <span class="p">[</span><span class="o">.</span><span class="n">prettyPrinted</span><span class="p">,</span> <span class="o">.</span><span class="n">sortedKeys</span><span class="p">]</span>
<span class="k">let</span> <span class="nv">jsonData</span> <span class="o">=</span> <span class="k">try</span> <span class="n">encoder</span><span class="o">.</span><span class="nf">encode</span><span class="p">(</span><span class="n">str</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">json</span> <span class="o">=</span> <span class="kt">String</span><span class="p">(</span><span class="nv">decoding</span><span class="p">:</span> <span class="n">jsonData</span><span class="p">,</span> <span class="nv">as</span><span class="p">:</span> <span class="kt">UTF8</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>
</code></pre></div>
<p>This is how our sample string is encoded:</p>
<div class="highlight"><pre class="highlight json"><code><span class="p">[</span><span class="w">
</span><span class="s2">"Café "</span><span class="p">,</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="s2">"Sol"</span><span class="p">,</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"NSInlinePresentationIntent"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div>
<p>This is an array of runs, where each run consists of a text segment and a dictionary of formatting attributes. The important point is that the formatting attributes are directly associated with the text segments they belong to, not indirectly via brittle byte or character offsets. (This encoding format is also more space-efficient and possibly better represents the in-memory layout of <code>AttributedString</code>, but that’s beside the point for this discussion.)</p>
<p>There’s still a (smaller) potential problem here if the <a href="https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries">character boundary rules</a> change for code points that span two adjacent text segments: the last character of run N and the first character of run N+1 might suddenly form a single character (grapheme cluster) in a new Unicode version. In that case, the decoding code will have to decide which formatting attributes to apply to this new character. But this is a much smaller issue because it only affects the characters in question. Unlike our original example, where an off-by-one error in run N would affect all subsequent runs, all other runs are untouched.</p>
<p>Related forum discussion: <a href="https://forums.swift.org/t/why-is-character-not-codable/56178/2">Itai Ferber on why <code>Character</code> isn’t <code>Codable</code></a>.</p>
<h1 id="storing-string-offsets-is-a-bad-idea">Storing string offsets is a bad idea</h1>
<p>We can extract a general lesson out of this: Don’t store string indices or offsets if possible. They aren’t stable over time or across runtime environments.</p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:unicodeversion" role="doc-endnote">
<p>On Apple platforms, the Swift standard library ships as part of the OS so I’d guess that the standard library’s grapheme breaking algorithm will be based on the same Unicode version that ships with the corresponding OS version. This is effectively no change in behavior compared to the pre-Swift 5.6 world (where the OS’s ICU library determined the Unicode version).</p>
<p>On non-ABI-stable platforms (e.g. Linux and Windows), the Unicode version used by your program is determined by the version of the Swift compiler your program is compiled with, if my understanding is correct. <a href="https://oleb.net/2022/attributedstring-codable/#fnref:unicodeversion" class="reversefootnote" role="doc-backlink">↩︎</a></p>
</li>
<li id="fn:normalize" role="doc-endnote">
<p>The Swift standard library doesn’t have APIs for Unicode normalization yet, but you can use the corresponding <code>NSString</code> APIs, which are automatically added to <code>String</code> when you import Foundation:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">import</span> <span class="kt">Foundation</span>
<span class="k">let</span> <span class="nv">precomposed</span> <span class="o">=</span> <span class="s">"é"</span><span class="o">.</span><span class="n">precomposedStringWithCanonicalMapping</span>
<span class="k">let</span> <span class="nv">decomposed</span> <span class="o">=</span> <span class="s">"é"</span><span class="o">.</span><span class="n">decomposedStringWithCanonicalMapping</span>
<span class="n">precomposed</span> <span class="o">==</span> <span class="n">decomposed</span> <span class="c1">// → true</span>
<span class="n">precomposed</span><span class="o">.</span><span class="n">unicodeScalars</span><span class="o">.</span><span class="n">count</span> <span class="c1">// → 1</span>
<span class="n">decomposed</span><span class="o">.</span><span class="n">unicodeScalars</span><span class="o">.</span><span class="n">count</span> <span class="c1">// → 2</span>
<span class="n">precomposed</span><span class="o">.</span><span class="n">utf8</span><span class="o">.</span><span class="n">count</span> <span class="c1">// → 2</span>
<span class="n">decomposed</span><span class="o">.</span><span class="n">utf8</span><span class="o">.</span><span class="n">count</span> <span class="c1">// → 3</span>
</code></pre></div> <p><a href="https://oleb.net/2022/attributedstring-codable/#fnref:normalize" class="reversefootnote" role="doc-backlink">↩︎</a></p>
</li>
<li id="fn:decode" role="doc-endnote">
<p>By the way, I see a lot of code using <a href="https://developer.apple.com/documentation/swift/string/3126734-init"><code>String(jsonData, encoding: .utf8)!</code></a> to create a string from UTF-8 data. <a href="https://developer.apple.com/documentation/swift/string/2907004-init"><code>String(decoding: jsonData, as: UTF8.self)</code></a> saves you a force-unwrap and is arguably “cleaner” because it doesn’t depend on Foundation. Since it never fails, it’ll insert <a href="https://en.wikipedia.org/wiki/Specials_(Unicode_block)#Replacement_character">replacement characters</a> into the string if it encounters invalid byte sequences. <a href="https://oleb.net/2022/attributedstring-codable/#fnref:decode" class="reversefootnote" role="doc-backlink">↩︎</a></p>
</li>
</ol>
</div>
https://oleb.net/2022/heterogeneous-dictionary/
A heterogeneous dictionary with strong types in Swift
2022-04-19T15:52:08Z
2022-04-20T17:54:06Z
Ole Begemann
<p>The <a href="https://developer.apple.com/documentation/swiftui/environment">environment in SwiftUI</a> is sort of like a global dictionary but with stronger types: each key (represented by a key path) can have its own specific value type. For example, the <a href="https://developer.apple.com/documentation/swiftui/environmentvalues/isenabled"><code>\.isEnabled</code></a> key stores a boolean value, whereas the <a href="https://developer.apple.com/documentation/swiftui/environmentvalues/font"><code>\.font</code></a> key stores an <code>Optional<Font></code>.</p>
<p>I wrote a custom dictionary type that can do the same thing. The <code>HeterogeneousDictionary</code> struct I show in this article stores mixed key-value pairs where each key defines the type of value it stores. The public API is fully type-safe, no casting required.</p>
<h1 id="usage">Usage</h1>
<p>I’ll start with an example of the finished API. Here’s a dictionary for storing text formatting attributes:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">import</span> <span class="kt">AppKit</span>
<span class="k">var</span> <span class="nv">dict</span> <span class="o">=</span> <span class="kt">HeterogeneousDictionary</span><span class="o"><</span><span class="kt">TextAttributes</span><span class="o">></span><span class="p">()</span>
<span class="n">dict</span><span class="p">[</span><span class="kt">ForegroundColor</span><span class="o">.</span><span class="k">self</span><span class="p">]</span> <span class="c1">// → nil</span>
<span class="c1">// The value type of this key is NSColor</span>
<span class="n">dict</span><span class="p">[</span><span class="kt">ForegroundColor</span><span class="o">.</span><span class="k">self</span><span class="p">]</span> <span class="o">=</span> <span class="kt">NSColor</span><span class="o">.</span><span class="n">systemRed</span>
<span class="n">dict</span><span class="p">[</span><span class="kt">ForegroundColor</span><span class="o">.</span><span class="k">self</span><span class="p">]</span> <span class="c1">// → NSColor.systemRed</span>
<span class="n">dict</span><span class="p">[</span><span class="kt">FontSize</span><span class="o">.</span><span class="k">self</span><span class="p">]</span> <span class="c1">// → nil</span>
<span class="c1">// The value type of this key is Double</span>
<span class="n">dict</span><span class="p">[</span><span class="kt">FontSize</span><span class="o">.</span><span class="k">self</span><span class="p">]</span> <span class="o">=</span> <span class="mi">24</span>
<span class="n">dict</span><span class="p">[</span><span class="kt">FontSize</span><span class="o">.</span><span class="k">self</span><span class="p">]</span> <span class="c1">// → 24 (type: Optional<Double>)</span>
</code></pre></div>
<p>We also need some boilerplate to define the set of keys and their associated value types. The code to do this for three keys (font, font size, foreground color) looks like this:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="c1">// The domain (aka "keyspace")</span>
<span class="kd">enum</span> <span class="kt">TextAttributes</span> <span class="p">{}</span>
<span class="kd">struct</span> <span class="kt">FontSize</span><span class="p">:</span> <span class="kt">HeterogeneousDictionaryKey</span> <span class="p">{</span>
<span class="kd">typealias</span> <span class="kt">Domain</span> <span class="o">=</span> <span class="kt">TextAttributes</span>
<span class="kd">typealias</span> <span class="kt">Value</span> <span class="o">=</span> <span class="kt">Double</span>
<span class="p">}</span>
<span class="kd">struct</span> <span class="kt">Font</span><span class="p">:</span> <span class="kt">HeterogeneousDictionaryKey</span> <span class="p">{</span>
<span class="kd">typealias</span> <span class="kt">Domain</span> <span class="o">=</span> <span class="kt">TextAttributes</span>
<span class="kd">typealias</span> <span class="kt">Value</span> <span class="o">=</span> <span class="kt">NSFont</span>
<span class="p">}</span>
<span class="kd">struct</span> <span class="kt">ForegroundColor</span><span class="p">:</span> <span class="kt">HeterogeneousDictionaryKey</span> <span class="p">{</span>
<span class="kd">typealias</span> <span class="kt">Domain</span> <span class="o">=</span> <span class="kt">TextAttributes</span>
<span class="kd">typealias</span> <span class="kt">Value</span> <span class="o">=</span> <span class="kt">NSColor</span>
<span class="p">}</span>
</code></pre></div>
<p>Yes, this is fairly long, which is one of the downsides of this approach. At least you only have to write it once per “keyspace”. I’ll walk you through it step by step.</p>
<h1 id="notes-on-the-api">Notes on the API</h1>
<h2 id="using-types-as-keys">Using types as keys</h2>
<p>As you can see in this line, the dictionary keys are types (more precisely, <a href="https://docs.swift.org/swift-book/ReferenceManual/Types.html#ID455">metatype values</a>):</p>
<div class="highlight"><pre class="highlight swift"><code><span class="n">dict</span><span class="p">[</span><span class="kt">FontSize</span><span class="o">.</span><span class="k">self</span><span class="p">]</span> <span class="o">=</span> <span class="mi">24</span>
</code></pre></div>
<p>This is another parallel with the SwiftUI environment, which also uses types as keys (the public environment API uses key paths as keys, but you’ll see the types underneath if you ever define your own environment key).</p>
<p>Why use types as keys? We want to establish a relationship between a key and the type of values it stores, and we want to make this connection known to the type system. The way to do this is by defining a type that sets up this link.</p>
<h1 id="domains-aka-keyspaces">Domains aka “keyspaces”</h1>
<p>A standard <a href="https://developer.apple.com/documentation/swift/dictionary"><code>Dictionary</code></a> is generic over its key and value types. This doesn’t work for our heterogeneous dictionary because we have multiple value types (and we want more type safety than <code>Any</code> provides). Instead, a <code>HeterogeneousDictionary</code> is parameterized with a domain:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="c1">// The domain (aka "keyspace")</span>
<span class="kd">enum</span> <span class="kt">TextAttributes</span> <span class="p">{}</span>
<span class="k">var</span> <span class="nv">dict</span> <span class="o">=</span> <span class="kt">HeterogeneousDictionary</span><span class="o"><</span><span class="kt">TextAttributes</span><span class="o">></span><span class="p">()</span>
</code></pre></div>
<p>The domain is the “keyspace” that defines the set of legal keys for this dictionary. Only keys that belong to the domain can be put into the dictionary. The domain type has no protocol constraints; you can use any type for this.</p>
<h2 id="defining-keys">Defining keys</h2>
<p>A key is a type that conforms to the <code>HeterogeneousDictionaryKey</code> protocol. The protocol has two associated types that define the relationships between the key and its domain and value type:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">protocol</span> <span class="kt">HeterogeneousDictionaryKey</span> <span class="p">{</span>
<span class="c1">/// The "namespace" the key belongs to.</span>
<span class="kd">associatedtype</span> <span class="kt">Domain</span>
<span class="c1">/// The type of values that can be stored</span>
<span class="c1">/// under this key in the dictionary.</span>
<span class="kd">associatedtype</span> <span class="kt">Value</span>
<span class="p">}</span>
</code></pre></div>
<p>You define a key by creating a type and adding the conformance:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">struct</span> <span class="kt">Font</span><span class="p">:</span> <span class="kt">HeterogeneousDictionaryKey</span> <span class="p">{</span>
<span class="kd">typealias</span> <span class="kt">Domain</span> <span class="o">=</span> <span class="kt">TextAttributes</span>
<span class="kd">typealias</span> <span class="kt">Value</span> <span class="o">=</span> <span class="kt">NSFont</span>
<span class="p">}</span>
</code></pre></div>
<h1 id="implementation-notes">Implementation notes</h1>
<p>A minimal implementation of the dictionary type is quite short:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">struct</span> <span class="kt">HeterogeneousDictionary</span><span class="o"><</span><span class="kt">Domain</span><span class="o">></span> <span class="p">{</span>
<span class="kd">private</span> <span class="k">var</span> <span class="nv">storage</span><span class="p">:</span> <span class="p">[</span><span class="kt">ObjectIdentifier</span><span class="p">:</span> <span class="kt">Any</span><span class="p">]</span> <span class="o">=</span> <span class="p">[:]</span>
<span class="k">var</span> <span class="nv">count</span><span class="p">:</span> <span class="kt">Int</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">storage</span><span class="o">.</span><span class="n">count</span> <span class="p">}</span>
<span class="kd">subscript</span><span class="o"><</span><span class="kt">Key</span><span class="o">></span><span class="p">(</span><span class="nv">key</span><span class="p">:</span> <span class="kt">Key</span><span class="o">.</span><span class="k">Type</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Key</span><span class="o">.</span><span class="kt">Value</span><span class="p">?</span>
<span class="k">where</span> <span class="kt">Key</span><span class="p">:</span> <span class="kt">HeterogeneousDictionaryKey</span><span class="p">,</span> <span class="kt">Key</span><span class="o">.</span><span class="kt">Domain</span> <span class="o">==</span> <span class="kt">Domain</span>
<span class="p">{</span>
<span class="k">get</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">storage</span><span class="p">[</span><span class="kt">ObjectIdentifier</span><span class="p">(</span><span class="n">key</span><span class="p">)]</span> <span class="k">as!</span> <span class="kt">Key</span><span class="o">.</span><span class="kt">Value</span><span class="p">?</span> <span class="p">}</span>
<span class="k">set</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">storage</span><span class="p">[</span><span class="kt">ObjectIdentifier</span><span class="p">(</span><span class="n">key</span><span class="p">)]</span> <span class="o">=</span> <span class="n">newValue</span> <span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<h2 id="internal-storage">Internal storage</h2>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">private</span> <span class="k">var</span> <span class="nv">storage</span><span class="p">:</span> <span class="p">[</span><span class="kt">ObjectIdentifier</span><span class="p">:</span> <span class="kt">Any</span><span class="p">]</span> <span class="o">=</span> <span class="p">[:]</span>
</code></pre></div>
<p>Internally, <code>HeterogeneousDictionary</code> uses a dictionary of type <code>[ObjectIdentifier: Any]</code> for storage. We can’t use a metatype such as <code>Font.self</code> directly as a dictionary key because metatypes aren’t <a href="https://developer.apple.com/documentation/swift/hashable">hashable</a>. But we can use the metatype’s <a href="https://developer.apple.com/documentation/swift/objectidentifier"><code>ObjectIdentifier</code></a>, which is essentially the address of the type’s representation in memory.</p>
<h2 id="subscript">Subscript</h2>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">subscript</span><span class="o"><</span><span class="kt">Key</span><span class="o">></span><span class="p">(</span><span class="nv">key</span><span class="p">:</span> <span class="kt">Key</span><span class="o">.</span><span class="k">Type</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Key</span><span class="o">.</span><span class="kt">Value</span><span class="p">?</span>
<span class="k">where</span> <span class="kt">Key</span><span class="p">:</span> <span class="kt">HeterogeneousDictionaryKey</span><span class="p">,</span> <span class="kt">Key</span><span class="o">.</span><span class="kt">Domain</span> <span class="o">==</span> <span class="kt">Domain</span>
<span class="p">{</span>
<span class="k">get</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">storage</span><span class="p">[</span><span class="kt">ObjectIdentifier</span><span class="p">(</span><span class="n">key</span><span class="p">)]</span> <span class="k">as!</span> <span class="kt">Key</span><span class="o">.</span><span class="kt">Value</span><span class="p">?</span> <span class="p">}</span>
<span class="k">set</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">storage</span><span class="p">[</span><span class="kt">ObjectIdentifier</span><span class="p">(</span><span class="n">key</span><span class="p">)]</span> <span class="o">=</span> <span class="n">newValue</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>The subscript implementation constrains its arguments to keys in the same domain as the dictionary’s domain. This ensures that you can’t subscript a dictionary for text attributes with some other unrelated key. If you find this too restrictive, you could also remove all references to the <code>Domain</code> type from the code; it would still work.</p>
<h1 id="using-key-paths-as-keys">Using key paths as keys</h1>
<p>Types as keys don’t have the best syntax. I think you’ll agree that <code>dict[FontSize.self]</code> doesn’t read as nice as <code>dict[\.fontSize]</code>, so I looked into providing a convenience API based on key paths.</p>
<p>My preferred solution would be if users could define static helper properties on the domain type, which the dictionary subscript would then accept as key paths, like so:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">extension</span> <span class="kt">TextAttributes</span> <span class="p">{</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">fontSize</span><span class="p">:</span> <span class="kt">FontSize</span><span class="o">.</span><span class="k">Type</span> <span class="p">{</span> <span class="kt">FontSize</span><span class="o">.</span><span class="k">self</span> <span class="p">}</span>
<span class="c1">// Same for font and foregroundColor</span>
<span class="p">}</span>
</code></pre></div>
<p>Sadly, this doesn’t work because Swift 5.6 doesn’t (yet?) support key paths to static properties (<a href="https://forums.swift.org/t/key-path-cannot-refer-to-static-member/28055">relevant forum thread</a>).</p>
<p>We have to introduce a separate helper type that acts as a namespace for these helper properties. Since the dictionary type can create an instance of the helper type, it can access the non-static helper properties. This doesn’t feel as clean to me, but it works. I called the helper type <code>HeterogeneousDictionaryValues</code> as a parallel with <a href="https://developer.apple.com/documentation/swiftui/environmentvalues"><code>EnvironmentValues</code></a>, which serves the same purpose in SwiftUI.</p>
<p>The code for this is included <a href="https://gist.github.com/ole/0473d8e063762ebfb1a403d069fdddaf">in the Gist</a>.</p>
<h1 id="drawbacks">Drawbacks</h1>
<p>Is the <code>HeterogeneousDictionary</code> type useful? I’m not sure. I wrote this mostly as an exercise and haven’t used it yet in a real project. In most cases, if you need a heterogeneous record with full type safety, it’s probably easier to just write a new struct where each property is optional — the boilerplate for defining the dictionary keys is certainly longer and harder to read.</p>
<p>For representing <em>partial values</em>, i.e. struct-like records where some but not all properties have values, take a look at these two approaches from 2018:</p>
<ul>
<li><a href="https://iankeen.tech/2018/06/05/type-safe-temporary-models/">Ian Keen, Type-safe temporary models (2018-06-05)</a></li>
<li>
<a href="https://josephduffy.co.uk/posts/partial-in-swift">Joseph Duffy, Partial in Swift (2018-07-10)</a>, also available <a href="https://github.com/JosephDuffy/Partial">as a library</a>
</li>
</ul>
<p>These use a similar storage approach (a dictionary of <code>Any</code> values with custom accessors to make it type-safe), but they use an existing struct as the domain/keyspace, combined with <a href="https://developer.apple.com/documentation/swift/partialkeypath">partial key paths</a> into that struct as the keys. I honestly think that this is the better design for most situations.</p>
<p>Aside from the boilerplate, here are a few more weaknesses of <code>HeterogeneousDictionary</code>:</p>
<ul>
<li>Storage is inefficient because values are boxed in <code>Any</code> containers</li>
<li>Accessing values is inefficient: every access requires unboxing</li>
<li>
<code>HeterogeneousDictionary</code> can’t easily conform to <code>Sequence</code> and <code>Collection</code> because these protocols require a uniform element type</li>
</ul>
<h1 id="the-code">The code</h1>
<p>The full code is available <a href="https://gist.github.com/ole/0473d8e063762ebfb1a403d069fdddaf">in a Gist</a>.</p>
https://oleb.net/2022/advanced-swift-5/
Advanced Swift, fifth edition
2022-03-28T14:03:30Z
2022-03-28T14:03:30Z
Ole Begemann
<div class="figure-container">
<figure style="max-width: 1024px;" class="no-border">
<a href="https://www.objc.io/books/advanced-swift/">
<img src="https://oleb.net/media/advanced-swift-5.0-banner-2048px.png" alt="">
</a>
</figure>
</div>
<p>We released the fifth edition of our book <a href="https://www.objc.io/books/advanced-swift/">Advanced Swift</a> a few days ago. You can buy the ebook <a href="https://www.objc.io/books/advanced-swift/">on the objc.io site</a>. The hardcover print edition is printed and sold by Amazon (<a href="https://www.amazon.com/dp/B09VFTFB6C">amazon.com</a>, <a href="https://www.amazon.co.uk/dp/B09VFTFB6C">amazon.co.uk</a>, <a href="https://www.amazon.de/dp/B09VFTFB6C">amazon.de</a>).</p>
<p>Highlights of the new edition:</p>
<ul>
<li>Fully updated for Swift 5.6</li>
<li>A new Concurrency chapter covering async/await, structured concurrency, and actors</li>
<li>New content on property wrappers, result builders, protocols, and generics</li>
<li>The print edition is now a hardcover (for the same price)</li>
<li>Free update for owners of the ebook</li>
</ul>
<h2 id="a-growing-book-for-a-growing-language">A growing book for a growing language</h2>
<p>Updating the book always turns out to be more work than I expect. Swift has grown substantially since our <a href="https://oleb.net/2019/advanced-swift-4/">last release</a> (for Swift 5.0), and the size of the book reflects this. The fifth edition is 76 % longer than the first edition from 2016. This time, we barely stayed under 1 million characters:</p>
<div class="figure-container">
<figure style="max-width: 1024px;" class="no-border">
<a href="https://oleb.net/media/advanced-swift-5.0-character-count-2766px.png">
<img src="https://oleb.net/media/advanced-swift-5.0-character-count-2766px.png" alt="Bar chart of the character count growth of the first five editions of Advanced Swift, from 537k (first edition) to 947k characters (fifth edition)">
</a>
<figcaption>
Character counts of <em>Advanced Swift</em> editions from 2016–2022.
</figcaption>
</figure>
</div>
<p>Many thanks to our editor, <a href="https://www.natalye.com/">Natalye</a>, for reading all this and improving our Dutch/German dialect of English.</p>
<h2 id="hardcover">Hardcover</h2>
<p>For the first time, the print edition comes in hardcover (for the same price). Being able to offer this makes me very happy. The hardcover book looks much better and is more likely to stay open when laid flat on a table.</p>
<p>We also increased the page size from 15×23 cm (6×9 in) to 18×25 cm (7×10 in) to keep the page count manageable (Amazon’s print on demand service limits hardcover books to 550 pages).</p>
<div class="figure-container">
<figure style="max-width: 1024px;" class="no-border">
<a href="https://oleb.net/media/advanced-swift-5.0-hardcover-IMG_2063-2048px.jpg">
<img src="https://oleb.net/media/advanced-swift-5.0-hardcover-IMG_2063-2048px.jpg" alt="Photo of the Advanced Swift hardcover">
</a>
</figure>
</div>
<p>I hope you enjoy the new edition. If you decide to buy the book or if you bought it in the past, thank you very much! And if you’re willing to write a review <a href="https://www.amazon.com/dp/B09VFTFB6C">on Amazon</a>, we’d appreciate it.</p>
https://oleb.net/2022/sync-functions-cancellation/
Synchronous functions can support cancellation too
2022-02-01T22:59:43Z
2022-02-02T13:04:01Z
Ole Begemann
<p>Cancellation is a Swift concurrency feature, but this doesn’t mean it’s only available in async functions. Synchronous functions can also support cancellation, and by doing so they’ll become better concurrency citizens when called from async code.</p>
<h1 id="motivating-example-jsondecoder">Motivating example: JSONDecoder</h1>
<p>Supporting cancellation makes sense for functions that can block for significant amounts of time (say, more than a few milliseconds). Take JSON decoding as an example. Suppose we wrote an async function that performs a network request and decodes the downloaded JSON data:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">import</span> <span class="kt">Foundation</span>
<span class="kd">func</span> <span class="n">loadJSON</span><span class="o"><</span><span class="kt">T</span><span class="p">:</span> <span class="kt">Decodable</span><span class="o">></span><span class="p">(</span><span class="n">_</span> <span class="nv">type</span><span class="p">:</span> <span class="kt">T</span><span class="o">.</span><span class="k">Type</span><span class="p">,</span> <span class="n">from</span> <span class="nv">url</span><span class="p">:</span> <span class="kt">URL</span><span class="p">)</span> <span class="k">async</span> <span class="k">throws</span> <span class="o">-></span> <span class="kt">T</span> <span class="p">{</span>
<span class="k">let</span> <span class="p">(</span><span class="nv">data</span><span class="p">,</span> <span class="nv">_</span><span class="p">)</span> <span class="o">=</span> <span class="k">try</span> <span class="k">await</span> <span class="kt">URLSession</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">data</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="n">url</span><span class="p">)</span>
<span class="k">return</span> <span class="k">try</span> <span class="kt">JSONDecoder</span><span class="p">()</span><span class="o">.</span><span class="nf">decode</span><span class="p">(</span><span class="n">type</span><span class="p">,</span> <span class="nv">from</span><span class="p">:</span> <span class="n">data</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div>
<p>The <a href="https://developer.apple.com/documentation/foundation/jsondecoder/2895189-decode"><code>JSONDecoder.decode</code></a> call is synchronous: it will block its thread until it completes. And if the download is large, decoding may take hundreds of milliseconds or even longer.</p>
<h1 id="avoid-blocking-if-possible">Avoid blocking if possible</h1>
<p>In general, async code should avoid calling blocking APIs if possible. Instead, async functions are expected to suspend regularly to give waiting tasks a chance to run. But <code>JSONDecoder</code> doesn’t have an async API (yet?), and I’m not even sure it can provide one that works with the existing Codable protocols, so let’s work with what we have. And if you think about it, it’s not totally unreasonable for <code>JSONDecoder</code> to block. After all, it is performing CPU-intensive work (assuming the data it’s working on doesn’t have to be paged in), and this work has to happen on <em>some</em> thread.</p>
<p>Async/await works best for I/O-bound functions that spend most of their time waiting for the disk or the network. If an I/O-bound function suspends, the runtime can give the function’s thread to another task that can make more productive use of the CPU.</p>
<h1 id="responding-to-cancellation">Responding to cancellation</h1>
<p>Cancellation is a cooperative process. Canceling a task only sets a flag in the task’s metadata. It’s up to individual functions to periodically check for cancellation and abort if necessary. If a function doesn’t respond promptly to cancellation or outright ignores the cancellation flag, the program may appear to the user to be stalling.</p>
<p>Now, if the task is canceled while <code>JSONDecoder.decode</code> is running, our <code>loadJSON</code> function <em>can’t</em> react properly because it can’t interrupt the decoding process. To fix this, the <code>decode</code> method would have to perform its own periodic cancellation checks, using the usual APIs, <a href="https://developer.apple.com/documentation/swift/task/3814832-iscancelled"><code>Task.isCancelled</code></a> or <a href="https://developer.apple.com/documentation/swift/task/3814826-checkcancellation"><code>Task.checkCancellation()</code></a>. These can be called from anywhere, including synchronous code.</p>
<h1 id="internals">Internals</h1>
<p>How does this work? How can synchronous code access task-specific metadata? Here’s <a href="https://github.com/apple/swift/blob/a33eaf6f0d249c1a8ee5671718476c449e6037dc/stdlib/public/Concurrency/TaskCancellation.swift#L64-L68">the code for <code>Task.isCancelled</code></a> in the standard library:</p>
<div class="highlight"><pre class="highlight swift"><code><span class="kd">extension</span> <span class="kt">Task</span> <span class="k">where</span> <span class="kt">Success</span> <span class="o">==</span> <span class="kt">Never</span><span class="p">,</span> <span class="kt">Failure</span> <span class="o">==</span> <span class="kt">Never</span> <span class="p">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="k">var</span> <span class="nv">isCancelled</span><span class="p">:</span> <span class="kt">Bool</span> <span class="p">{</span>
<span class="n">withUnsafeCurrentTask</span> <span class="p">{</span> <span class="n">task</span> <span class="k">in</span>
<span class="n">task</span><span class="p">?</span><span class="o">.</span><span class="n">isCancelled</span> <span class="p">??</span> <span class="kc">false</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>This calls <a href="https://developer.apple.com/documentation/swift/3815003-withunsafecurrenttask"><code>withUnsafeCurrentTask</code></a> to get a handle to the current task. When the runtime schedules a task to run on a particular thread, it stores a pointer to the task object in that thread’s <a href="https://en.wikipedia.org/wiki/Thread-local_storage">thread-local storage</a>, where any code running on that thread – sync or async – can access it.</p>
<p>If <code>task == nil</code>, there is no current task, i.e. we haven’t been called (directly or indirectly) from an async function. In this case, cancellation doesn’t apply, so we can return <code>false</code>.</p>
<p>If we do have a task handle, we ask the task for its <code>isCancelled</code> flag and return that. Reading the flag is an atomic (thread-safe) operation because other threads may be writing to it concurrently.</p>
<h1 id="conclusion">Conclusion</h1>
<p>I hope we’ll see cancellation support in the Foundation encoders and decoders in the future. If you have written synchronous functions that can potentially block their thread for a significant amount of time, consider adding periodic cancellation checks. It’s a quick way to make your code work better with the concurrency system, and you don’t even have to change your API to do it.</p>
<div class="update">
<p><strong>Update February 2, 2022:</strong> <a href="https://twitter.com/UINT_MIN/status/1488659180802088962">Jordan Rose argues</a> that cancellation support for synchronous functions should be opt-in because it introduces a failure mode that’s hard to reason about locally as the “source“ of the failure (the async context) may be several levels removed from the call site. Definitely something to consider!</p>
</div>
https://oleb.net/2022/cancellation-forms/
Cancellation can come in many forms
2022-01-31T18:20:07Z
2022-01-31T18:20:07Z
Ole Begemann
<p>In Swift’s concurrency model, <strong>cancellation is cooperative</strong>. To be a good concurrency citizen, code must periodically check if the current task has been cancelled, and react accordingly.</p>
<p>You can check for cancellation by calling <a href="https://developer.apple.com/documentation/swift/task/3814832-iscancelled"><code>Task.isCancelled</code></a> or with <a href="https://developer.apple.com/documentation/swift/task/3814826-checkcancellation"><code>try Task.checkCancellation()</code></a> — the latter will exit by throwing a <a href="https://developer.apple.com/documentation/swift/cancellationerror"><code>CancellationError</code></a> if the task has been cancelled.</p>
<p>By convention, <strong>functions should react to cancellation by throwing a <code>CancellationError</code></strong>. But this convention isn’t enforced, so callers must be aware that cancellation can manifest itself in other forms. Here are some other ways how functions might respond to cancellation:</p>
<ul>
<li>
<p><strong>Throw a different error.</strong> For example, the async networking APIs in Foundation, such as <a href="https://developer.apple.com/documentation/foundation/urlsession/3767353-data"><code>URLSession.data(from: URL)</code></a>, throw a <a href="https://developer.apple.com/documentation/foundation/urlerror"><code>URLError</code></a> with the code <a href="https://developer.apple.com/documentation/foundation/urlerror/2293052-cancelled"><code>URLError.Code.cancelled</code></a> on cancellation. It’d be nice if <code>URLSession</code> translated this error to <code>CancellationError</code>, but it doesn’t.</p>
</li>
<li>
<p><strong>Return a partial result.</strong> A function that has completed part of its work when cancellation occurs may choose to return a partial result rather than throwing the work away and aborting. In fact, this may be the best choice for a non-throwing function. But note that this behavior can be extremely surprising to callers, so be sure to document it clearly.</p>
</li>
<li>
<p><strong>Do nothing.</strong> Functions are supposed to react promptly to cancellation, but callers must assume the worst. Even if cancelled, a function might run to completion and finish normally. Or it might eventually respond to cancellation by aborting, but not promptly because it doesn’t perform its cancellation checks often enough.</p>
</li>
</ul>
<p>So as the caller of a function, you can’t really rely on specific cancellation behavior unless you know how the callee is implemented. Code that wants to know if its task has been cancelled should itself call <code>Task.isCancelled</code>, rather than counting on catching a <code>CancellationError</code> from a callee.</p>