<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content"><channel><title>FIVE STARS</title><description>Exploring iOS, SwiftUI &amp; much more.</description><link>https://www.fivestars.blog</link><language>en</language><lastBuildDate>Mon, 9 May 2022 10:50:19 +0000</lastBuildDate><pubDate>Mon, 9 May 2022 10:50:19 +0000</pubDate><ttl>250</ttl><atom:link href="https://www.fivestars.blog/feed.rss" rel="self" type="application/rss+xml"/><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-lessons</guid><title>SwiftUI Lessons</title><description></description><link>https://www.fivestars.blog/articles/swiftui-lessons</link><pubDate>Mon, 7 Feb 2022 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p><a href="http://speakerdeck.com/zntfdr/swiftui-lessons"><img src="https://camo.githubusercontent.com/218476f7636a128dcfa4a74daca32854cf1c1c6fe1b4981fb5db1bac624f33ed/68747470733a2f2f66696c65732e737065616b65726465636b2e636f6d2f70726573656e746174696f6e732f66393066323639356466626234623766623639366639363866316437363534312f736c6964655f302e6a7067" alt="slides"/></a></p><p>I recently had the opportunity to give a talk at <a href="https://iosconf.sg">iOS Conf SG 2022</a>:<br>in this talk, called SwiftUI Lessons, I don't share the usual content you can find on this website. Instead, I talk about my experience in transitioning to SwiftUI, coming from a UIKit developer's perspective.</p><p>The talk is addressed to both new and seasoned Apple platforms developers:</p><ul><li><a href="https://www.youtube.com/watch?v=vUcXYGI4XRs&list=PLED4k3CZkY9R9mhRW5V74gS9cVTp28CbK&index=10">video</a></li><li><a href="http://speakerdeck.com/zntfdr/swiftui-lessons">slides</a> (<a href="https://github.com/zntfdr/talks/blob/5a8cf2f4a377c2fa0564097338cbba2b1ae71966/2022%20SwiftUI%20Lessons/swiftui_lessons.pdf">pdf</a>)</li><li><a href="https://github.com/zntfdr/talks">slides source</a></li></ul><p>If you know somebody making the jump into SwiftUI, feel free to send them this talk their way!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/xcode-13-3-beta-1</guid><title>New SwiftUI documentation in Xcode 13.3 beta 1</title><description>Everything new in SwiftUI in Xcode 13.3b1</description><link>https://www.fivestars.blog/articles/xcode-13-3-beta-1</link><pubDate>Sun, 30 Jan 2022 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Xcode 13.3b1 is the first Xcode release of 2022!</p><p>This build comes with a preview of Swift 5.6 (see main changes <a href="https://github.com/apple/swift/blob/main/CHANGELOG.md#swift-56 ">here</a>), meaning that Swift Package Manager gains build tool/command plugins! See the associated Swift Evolution discussions here: <a href="https://github.com/apple/swift-evolution/blob/21211e61c9b8a773945c788e9acd99099102c4c0/proposals/0303-swiftpm-extensible-build-tools.md">SE-0303</a>, <a href="https://github.com/apple/swift-evolution/blob/21211e61c9b8a773945c788e9acd99099102c4c0/proposals/0325-swiftpm-additional-plugin-apis.md">SE-0325</a>, <a href="https://github.com/apple/swift-evolution/blob/21211e61c9b8a773945c788e9acd99099102c4c0/proposals/0332-swiftpm-command-plugins.md">SE-0332</a>.</p><p>There is also plenty of developer's <em>quality of life</em> improvements, particularly in the Source Editor front. I recommend giving the <a href="https://developer.apple.com/documentation/xcode-release-notes/xcode-13_3-release-notes ">official release notes</a> a read.</p><p>Enough about Xcode; let's see what's new in SwiftUI!</p><p>Everything new in this Xcode version comes in the form of new and improved documentation; here, you can find small summaries of the changes. Each section links to the official (and updated) documentation.</p><h2>Monospaced examples</h2><p><a href="https://developer.apple.com/documentation/swiftui/font/monospaceddigit()"><code>Font/monospacedDigit()</code></a>, <a href="https://developer.apple.com/documentation/swiftui/text/monospaceddigit()"><code>Text/monospacedDigit()</code></a>, and <a href="https://developer.apple.com/documentation/swiftui/font/monospaced()"><code>Font/monospaced()</code></a> now come with examples and a longer explanation on how they work.</p><pre><code><span class="type">From Font</span>/<span class="call">monospacedDigit</span>() documentation:
<span class="comment">/// This modifier only affects numeric characters, and leaves all other
/// characters unchanged. If the base font doesn't support fixed-width,
/// or _monospace_ digits, the font remains unchanged.</span>
</code></pre><p>In particular, <a href="https://developer.apple.com/documentation/swiftui/font/monospaced()"><code>Font</code>'s <code>monospaced()</code></a> documentation suggests using <code>AttributedString</code> for advanced uses when mixing monospaced and normal text. The documentation also reminds us of <code>AttributedString</code>'s markdown support, where we automatically get the monospaced style when we wrap text within `backticks`.</p><h2>Padding cross-reference and modifier order</h2><p>All padding modifiers (<a href="https://developer.apple.com/documentation/swiftui/view/padding(_:_:)"><code>padding(_:_:)</code></a>, <a href="https://developer.apple.com/documentation/swiftui/text/padding(_:)-5wi61"><code>padding(_:)</code></a>, <a href="https://developer.apple.com/documentation/swiftui/text/padding(_:)-9f33x"><code>padding(_:)</code></a>) have received new documentation with examples showcasing what's the difference between each modifier.</p><p>Just before each example image, the documentation warns about the modifier order, which is helpful for people just learning about SwiftUI.</p><p>At the end of each <kbd>Discussion</kbd> chapter, the documentation refers to all other padding modifiers alternatives, a nice touch for discoverability.</p><pre><code><span class="type">From View</span>/<span class="call">padding</span>(_:<span class="keyword">_</span>:) documentation:
<span class="comment">/// To control the amount of padding independently for each edge, use
/// ``View/padding(_:)``. To pad all outside edges of a view by a
/// specified amount, use ``View/padding(_:)``.</span>
</code></pre><h2>Scene padding</h2><p>If you have ever wondered what the new <a href="https://developer.apple.com/documentation/swiftui/containerrelativeshape/scenepadding(_:)"><code>scenePadding(_:)</code></a> modifier is about, and what's the difference with the "old" padding modifiers, you're not alone (FB9487801). The first official word came out with a new <a href="https://developer.apple.com/documentation/watchkit/supporting_multiple_watch_sizes">WatchKit article</a> after the reveal of the Apple Watch Series 7.</p><img src="https://www.fivestars.blog/assets/posts/xcode-13-3-beta-1/scene-padding.png"/><p>Thanks to Xcode 13.3b1, developers no longer have to find said article to discover the new modifier functionality: the new <a href="https://developer.apple.com/documentation/swiftui/containerrelativeshape/scenepadding(_:)"><code>scenePadding(_:)</code></a> documentation comes with a clear discussion explaining what this modifier is for.</p><p>Interestingly, as of today, this modifier behaves like other padding modifiers in all other platforms. The SwiftUI team could have made this modifier available only for WatchOS, but they chose not to.</p><h2>Picker and tag enhancements</h2><p>The complete <a href="https://developer.apple.com/documentation/swiftui/picker"><code>Picker</code></a> and <a href="https://developer.apple.com/documentation/swiftui/form/tag(_:)"><code>View/tag(_:)</code></a> documentation has improved with more straightforward descriptions and easier-to-understand examples. I find the new documentation/examples much clearer, especially the one with a <code>Picker</code> with an associated optional state (found in the <a href="https://developer.apple.com/documentation/swiftui/form/tag(_:)">tag</a> documentation).</p><p><a href="https://developer.apple.com/documentation/swiftui/picker"><code>Picker</code></a>'s documentation also gains a new section explaining when explicit <code>.tag(_:)</code>s are necessary when using <code>ForEach</code>.</p><pre><code><span class="comment">/// Other examples of when the views in a picker's ``ForEach`` need an explicit
/// tag modifier include when you:
/// * Select over the cases of an enumeration that conforms to the 
///   `Identifiable` protocol by using anything besides `Self` as the `id` 
///   parameter type. For example, a string enumeration might use the case's 
///   `rawValue` string as the `id`. That identifier type doesn't match the 
///   selection type, which is the type of the enumeration itself.
/// * Use an optional value for the `selection` input parameter. For that to
///   work, you need to explicitly cast the tag modifier's input as `Optional` 
///   to match.</span>
</code></pre><h2>Clearer <a href="https://developer.apple.com/documentation/swiftui/view/luminancetoalpha()"><code>luminanceToAlpha()</code></a></h2><img src="https://www.fivestars.blog/assets/posts/xcode-13-3-beta-1/luminance.png"/><p>Lastly, we have another win for clarity with the new <a href="https://developer.apple.com/documentation/swiftui/view/luminancetoalpha()"><code>luminanceToAlpha()</code></a> documentation, which now provides an improved example and references to the homonymous effect in <a href="https://www.w3.org/TR/SVG2/">SVG 2's specification</a>.</p><h2>Conclusions</h2><p>Did you find anything else interesting in Xcode 13.3 beta 1? Please <a href="https://twitter.com/zntfdr">let me know</a>!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/xcode-13-2-beta-2</guid><title>What's new in Xcode 13.2 beta 2</title><description>View.onSubmit(of:_:) and focus API back-port might be coming next?</description><link>https://www.fivestars.blog/articles/xcode-13-2-beta-2</link><pubDate>Wed, 17 Nov 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>This second beta brought us a <a href="https://developer.apple.com/documentation/xcode-release-notes/xcode-13_2-release-notes">very cool new build mode for the Swift compiler</a>. Enable it via command line:</p><pre><code>defaults write com.<span class="property">apple</span>.<span class="property">dt</span>.<span class="type">XCBuild EnableSwiftBuildSystemIntegration</span> <span class="number">1</span>
</code></pre><p>It's a very light update for SwiftUI. Let's see what's new.</p><h2>TextField and SecureField</h2><p>Both <code>TextField</code> and <code>SecureField</code> come with many initializers: instead of having parameters with default values, those views now have different overloads, either requiring or omitting such parameters.</p><p>For example we went from:</p><pre><code><span class="keyword">extension</span> <span class="type">TextField</span> <span class="keyword">where</span> <span class="type">Label</span> == <span class="type">Text</span> {
  <span class="keyword">public init</span>&lt;S&gt;(<span class="keyword">_</span> title: <span class="type">S</span>, text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;, prompt: <span class="type">Text</span>? = <span class="keyword">nil</span>) <span class="keyword">where</span> <span class="type">S</span> : <span class="type">StringProtocol</span>
}
</code></pre><p>to:</p><pre><code><span class="keyword">extension</span> <span class="type">TextField</span> <span class="keyword">where</span> <span class="type">Label</span> == <span class="type">Text</span> {
  <span class="keyword">public init</span>&lt;S&gt;(<span class="keyword">_</span> title: <span class="type">S</span>, text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;, prompt: <span class="type">Text</span>?) <span class="keyword">where</span> <span class="type">S</span> : <span class="type">StringProtocol</span>
  <span class="keyword">public init</span>&lt;S&gt;(<span class="keyword">_</span> title: <span class="type">S</span>, text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;) <span class="keyword">where</span> <span class="type">S</span> : <span class="type">StringProtocol</span>
}
</code></pre><p>This change spans all <code>TextField</code>/<code>SecureField</code> overloads, is completely seamless for third-party developers, and is back-ported all the way to iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0.</p><p>This back-port is possible thanks to <code>@_alwaysEmitIntoClient</code>, <a href="https://www.fivestars.blog/articles/alwaysEmitIntoClient/">which we covered here</a>.</p><p>For example, if we take a look at SwiftUI's Swift Interface, we will see the implementation of the second initializer (from above):</p><pre><code><span class="keyword">extension</span> <span class="type">TextField</span> <span class="keyword">where</span> <span class="type">Label</span> == <span class="type">Text</span> {
  <span class="keyword">@_alwaysEmitIntoClient @_disfavoredOverload 
  public init</span>&lt;S&gt;(<span class="keyword">_</span> title: <span class="type">S</span>, text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;) <span class="keyword">where</span> <span class="type">S</span> : <span class="type">StringProtocol</span> {
    <span class="keyword">self</span>.<span class="keyword">init</span>(title, text: text, onCommit: {})
  }
}
</code></pre><p>...while the first initializer just dropped the default value:</p><pre><code><span class="comment">// From:</span>
<span class="keyword">public init</span>&lt;S&gt;(<span class="keyword">_</span> title: <span class="type">S</span>, text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;, prompt: <span class="type">Text</span>? = <span class="keyword">nil</span>) <span class="keyword">where</span> <span class="type">S</span> : <span class="type">StringProtocol</span>

<span class="comment">// To:</span>
<span class="keyword">public init</span>&lt;S&gt;(<span class="keyword">_</span> title: <span class="type">S</span>, text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;, prompt: <span class="type">Text</span>?) <span class="keyword">where</span> <span class="type">S</span> : <span class="type">StringProtocol</span>
</code></pre><p>All these new initializers, which are compatible with earlier OSes, come with the same documentation as the original initializers with the "extra" parameter, complete with mentions of:</p><ul><li><code>prompt</code> parameter</li><li>Focus API</li><li><code>View.onSubmit</code> view modifier</li></ul><p>It's most likely an oversight, but it'd be great if this were a hint that a back-port of those features is on the way.</p><h2>Conclusions</h2><p>Did you find anything else interesting in Xcode 13.2 beta 2? Please <a href="http://twitter.com/zntfdr">let me know</a>!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/custom-environment-values-cheatsheet</guid><title>Custom SwiftUI Environment Values Cheatsheet</title><description>A collection of the most common custom types we might define into the environment.</description><link>https://www.fivestars.blog/articles/custom-environment-values-cheatsheet</link><pubDate>Tue, 2 Nov 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>In <a href="https://www.fivestars.blog/articles/how-to-define-environment-values/">How to define custom SwiftUI environment values</a> we covered all the theory behind creating custom environment values. In this last entry of the Five Stars SwiftUI Environment series, let's have a quick tour of the most common custom value types we might define.</p><blockquote><p>This is the sixth and last entry of the Five Stars SwiftUI Environment series. It's recommended to read the first few entries before proceeding: <a href="https://www.fivestars.blog/articles/swiftui-environment-propagation/">1</a>, <a href="https://www.fivestars.blog/articles/swiftui-environment-propagation-2/">2</a>, <a href="https://www.fivestars.blog/articles/swiftui-environment-propagation-3/">3</a>, <a href="https://www.fivestars.blog/articles/environmentvalues/">4</a>, <a href="https://www.fivestars.blog/articles/how-to-define-environment-values/">5</a>.</p></blockquote><h2>Struct, Enums, and primitives</h2><p>SwiftUI environment values have been built specifically for these types, we've seen multiple examples of declaring a primitive environment value:</p><pre><code><span class="keyword">struct</span> FSNumberKey: <span class="type">EnvironmentKey</span> {
  <span class="keyword">static let</span> defaultValue: <span class="type">Int</span> = <span class="number">5</span>
}

<span class="keyword">extension</span> <span class="type">EnvironmentValues</span> {
  <span class="keyword">public var</span> fsNumber: <span class="type">Int</span> {
    <span class="keyword">get</span> { <span class="keyword">self</span>[<span class="type">FSNumberKey</span>.<span class="keyword">self</span>] } 
    <span class="keyword">set</span> { <span class="keyword">self</span>[<span class="type">FSNumberKey</span>.<span class="keyword">self</span>] = newValue }
  }
}
</code></pre><p>Replace <code>Int</code> with any other primitive, <code>struct</code>, or <code>enum</code> definition, and it will work the same way.</p><pre><code><span class="comment">// Read:</span>

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">fsNumber</span>) <span class="keyword">var</span> number: <span class="type">Int</span> <span class="comment">// 👈🏻</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Text</span>(<span class="string">"</span>\(number)<span class="string">"</span>) <span class="comment">// 👈🏻</span> 
  }
}

<span class="comment">// Set:</span>

<span class="call">someView</span>()
  .<span class="call">environment</span>(\.<span class="property">fsNumber</span>, <span class="number">10</span>)
</code></pre><blockquote><p>about <a href="https://www.fivestars.blog/articles/swiftui-environment-values/">90% of the built-in environment values</a> fall under this category.</p></blockquote><h2>Bindings</h2><p>The <code>@Environment</code> property wrapper gives us a read-only value, thus it makes sense to think about environment values as something set by a parent and read by a child.</p><p>However, we can use bindings to allow children to modify environment values, with the new changes bubbling up to their ancestors.</p><p>Take the following definition:</p><pre><code><span class="keyword">struct</span> FSBoolBindingKey: <span class="type">EnvironmentKey</span> {
  <span class="keyword">static var</span> defaultValue: <span class="type">Binding</span>&lt;<span class="type">Bool</span>&gt; = .<span class="call">constant</span>(<span class="keyword">false</span>)
}

<span class="keyword">extension</span> <span class="type">EnvironmentValues</span> {
  <span class="keyword">var</span> fsBoolBinding: <span class="type">Binding</span>&lt;<span class="type">Bool</span>&gt; {
    <span class="keyword">get</span> { <span class="keyword">self</span>[<span class="type">FSBoolBindingKey</span>.<span class="keyword">self</span>] }
    <span class="keyword">set</span> { <span class="keyword">self</span>[<span class="type">FSBoolBindingKey</span>.<span class="keyword">self</span>] = newValue }
  }
}
</code></pre><p>This is very similar to any other environment value type, but it lets child views both read and modify the value:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> myBool = <span class="keyword">false

  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">Text</span>(myBool ? <span class="string">"true"</span> : <span class="string">"false"</span>)
      <span class="type">NestedView</span>()
        .<span class="call">environment</span>(\.<span class="property">myBoolBinding</span>, $myBool) <span class="comment">// 👈🏻 we inject myBool binding into the environment</span>
    }
  }
}

<span class="keyword">struct</span> NestedView: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">myBoolBinding</span>) <span class="keyword">@Binding var</span> myBool: <span class="type">Bool</span> <span class="comment">// 👈🏻 we read the binding from the environment</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Toggle</span>(isOn: _myBool.<span class="property">wrappedValue</span>, label: <span class="type">EmptyView</span>.<span class="keyword">init</span>) <span class="comment">// 👈🏻 we read/write the environment value!</span>
  }
}
</code></pre><p>Here's another we way we could have defined the <code>NestedView</code>:</p><pre><code><span class="keyword">struct</span> NestedView: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">myBoolBinding</span>) <span class="keyword">var</span> myBool: <span class="type">Binding</span>&lt;<span class="type">Bool</span>&gt; <span class="comment">// 👈🏻 different type</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Toggle</span>(isOn: myBool, label: <span class="type">EmptyView</span>.<span class="keyword">init</span>) <span class="comment">// 👈🏻 different isOn parameter</span>
  }
}
</code></pre><p>This is equivalent to passing a binding as a parameter of a view, but via environment.</p><blockquote><p>SwiftUI built-in binding environment values: <a href="https://developer.apple.com/documentation/swiftui/environmentvalues/presentationmode"><code>presentationMode</code></a>.</p></blockquote><h3>Optional bindings</h3><p>In the previous <code>FSBoolBindingKey</code> definition, we've set a default value of <code>.constant(false)</code>, which works great as long as we remember to set the binding in the environment before it's used:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> myBool = <span class="keyword">false

  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    ...
    <span class="type">NestedView</span>()
      .<span class="call">environment</span>(\.<span class="property">myBoolBinding</span>, $myBool) <span class="comment">// 👈🏻</span> 
    ...
  }
}
</code></pre><p>If we forget to set this value into the environment, <code>NestedView</code> (or any other view) won't be able to modify the <code>myBoolBinding</code> environment value.</p><p>Depending on our use case, this might be what we want. Alternatively, similarly to what we've seen in <a href="https://www.fivestars.blog/articles/optional-binding/">How to add optional <code>@Bindings</code> to SwiftUI views</a>, we could define <code>FSBoolBindingKey</code> and <code>fsBoolBinding</code> with an associated optional binding:</p><pre><code><span class="keyword">struct</span> FSBoolBindingKey: <span class="type">EnvironmentKey</span> {
  <span class="keyword">static var</span> defaultValue: <span class="type">Binding</span>&lt;<span class="type">Bool</span>&gt;?
}

<span class="keyword">extension</span> <span class="type">EnvironmentValues</span> {
  <span class="keyword">var</span> fsBoolBinding: <span class="type">Binding</span>&lt;<span class="type">Bool</span>&gt;? {
    <span class="keyword">get</span> { <span class="keyword">self</span>[<span class="type">FSBoolBindingKey</span>.<span class="keyword">self</span>] }
    <span class="keyword">set</span> { <span class="keyword">self</span>[<span class="type">FSBoolBindingKey</span>.<span class="keyword">self</span>] = newValue }
  }
}
</code></pre><p>At this point, views using this value will have to deal with a binding that might or might not be there. In the following example, <code>NestedView</code> will use the environment value when present and, alternatively, will use a private state (that doesn't affect the environment):</p><pre><code><span class="keyword">struct</span> NestedView: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">fsBoolBinding</span>) <span class="keyword">var</span> myBool
  <span class="keyword">@State private var</span> privateBool = <span class="keyword">false</span> <span class="comment">// 👈🏻</span> 

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Toggle</span>(isOn: myBool ?? $privateBool, label: <span class="type">EmptyView</span>.<span class="keyword">init</span>) <span class="comment">// 👈🏻</span> 
  }
}
</code></pre><blockquote><p>SwiftUI built-in optional binding environment values: <a href="https://developer.apple.com/documentation/swiftui/environmentvalues/editmode"><code>editMode</code></a>.</p></blockquote><h2>Actions</h2><p>This year we've seen the introduction of a new environment-actions pattern in SwiftUI:<br>instead of views directly accepting closures to trigger in specific scenarios, it's now common to set closures into the environment, which are then picked up and triggered by relevant/associated views.</p><p>An example is <a href="https://www.fivestars.blog/articles/onsubmit/">the new <code>onSubmit</code> modifier</a>:</p><pre><code><span class="type">Form</span> {
  <span class="type">TextField</span>(...)
  <span class="type">TextField</span>(...)
}
.<span class="call">onSubmit</span> {
  <span class="call">print</span>(<span class="string">"Form submitted"</span>)
}
<span class="comment">// 👆🏻 this closure is set into the environment and picked up by TextField, SecureField, and TextEditor.</span>
</code></pre><p>To define a new action, we first define a struct accepting a closure (depending on our case, this closure may accept/return values):</p><pre><code><span class="keyword">struct</span> FSAction {
  <span class="keyword">var</span> action: () -&gt; <span class="type">Void</span>

  <span class="keyword">init</span>(action: <span class="keyword">@escaping</span> () -&gt; <span class="type">Void</span> = { }) {
    <span class="keyword">self</span>.<span class="property">action</span> = action
  }
}
</code></pre><p>We might have extra logic in this struct that we probably don't want to expose to other developers. Hence we set our <code>action</code> as <code>private</code> and, instead, we make the struct type callable via Swift's special <code>callAsFunction</code> method:</p><pre><code><span class="keyword">struct</span> FSAction {
  <span class="keyword">private var</span> action: () -&gt; <span class="type">Void</span> <span class="comment">// 👈🏻 private</span>

  <span class="keyword">func</span> callAsFunction() { <span class="comment">// 👈🏻</span> 
    <span class="call">action</span>()
  }

  <span class="keyword">init</span>(action: <span class="keyword">@escaping</span> () -&gt; <span class="type">Void</span> = { }) {
    <span class="keyword">self</span>.<span class="property">action</span> = action
  }
}
</code></pre><p>Next, we create the usual key and extend environment values:</p><pre><code><span class="keyword">struct</span> FSActionKey: <span class="type">EnvironmentKey</span> {
  <span class="keyword">static var</span> defaultValue: <span class="type">FSAction</span> = <span class="type">FSAction</span>()
}

<span class="keyword">extension</span> <span class="type">EnvironmentValues</span> {
  <span class="keyword">var</span> fsAction: <span class="type">FSAction</span> {
    <span class="keyword">get</span> { <span class="keyword">self</span>[<span class="type">FSActionKey</span>.<span class="keyword">self</span>] }
    <span class="keyword">set</span> { <span class="keyword">self</span>[<span class="type">FSActionKey</span>.<span class="keyword">self</span>] = newValue }
  }
}
</code></pre><p>...and now we're ready to use this new action:</p><pre><code><span class="comment">// Read:</span>

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">fsAction</span>) <span class="keyword">var</span> action: <span class="type">FSAction</span> <span class="comment">// 👈🏻</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(<span class="string">"Tap me"</span>) { <span class="call">action</span>() } <span class="comment">// 👈🏻</span> 
  }
}

<span class="comment">// Set:</span>

<span class="call">someView</span>()
  .<span class="call">environment</span>(\.<span class="property">fsAction</span>, <span class="type">FSAction</span> { <span class="comment">// 👈🏻</span> 
    ...
  })
</code></pre><p>Thanks to <code>FSAction</code>'s <code>callAsFunction</code> we don't need to reach for the <code>FSAction.action</code> property (and we can't, as it's <code>private</code>). Instead, we call the function directly on the <code>FSAction</code> instance. This helps hide any implementation detail of our <code>FSAction</code> struct.</p><blockquote><p>SwiftUI built-in actions: <a href="https://developer.apple.com/documentation/swiftui/environmentvalues/dismissSearch"><code>dismissSearch</code></a> (<a href="https://developer.apple.com/documentation/swiftui/dismisssearchaction"><code>DismissSearchAction</code></a>), <a href="https://developer.apple.com/documentation/swiftui/environmentvalues/openURL"><code>openURL</code></a> (<a href="https://developer.apple.com/documentation/swiftui/openurlaction"><code>OpenURLAction</code></a>), <a href="https://developer.apple.com/documentation/swiftui/environmentvalues/refresh"><code>refresh</code></a> (<a href="https://developer.apple.com/documentation/swiftui/refreshaction"><code>RefreshAction</code></a>). <a href="https://developer.apple.com/documentation/swiftui/environmentvalues/resetFocus"><code>resetFocus</code></a> (<a href="https://developer.apple.com/documentation/swiftui/resetfocusaction"><code>ResetFocusAction</code></a>), <a href="https://developer.apple.com/documentation/swiftui/environmentvalues/dismiss"><code>dismiss</code></a> (<a href="https://developer.apple.com/documentation/swiftui/dismissaction"><code>DismissAction</code></a>).</p></blockquote><h3>Closures</h3><p>The action pattern that we just introduced has an associated <code>struct</code> type that really is a wrapper for a closure. This was true for our simple case, but, in reality, our <code>FSAction</code> struct may contain implementation details that are just not exposed.</p><p>If we want our environment value to be <em>just</em> a closure and nothing else, we can skip the action struct definition, and use a closure directly:</p><pre><code><span class="keyword">struct</span> ClosureKey: <span class="type">EnvironmentKey</span> {
  <span class="keyword">static let</span> defaultValue: () -&gt; <span class="type">Void</span> = { }
}

<span class="keyword">extension</span> <span class="type">EnvironmentValues</span> {
  <span class="keyword">public var</span> fsAction: () -&gt; <span class="type">Void</span> {
    <span class="keyword">get</span> { <span class="keyword">self</span>[<span class="type">ClosureKey</span>.<span class="keyword">self</span>] }
    <span class="keyword">set</span> { <span class="keyword">self</span>[<span class="type">ClosureKey</span>.<span class="keyword">self</span>] = newValue }
  }
}
</code></pre><p>Which we can then use this way:</p><pre><code><span class="comment">// Read:</span>

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">fsAction</span>) <span class="keyword">var</span> action: () -&gt; <span class="type">Void</span> <span class="comment">// 👈🏻</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(<span class="string">"Tap me"</span>) { <span class="call">action</span>() } <span class="comment">// 👈🏻</span> 
  }
}

<span class="comment">// Set:</span>

<span class="call">someView</span>()
  .<span class="call">environment</span>(\.<span class="property">fsAction</span>) { <span class="comment">// 👈🏻</span> 
    ...
  }
</code></pre><p>Unlike SwiftUI views, <a href="https://www.fivestars.blog/articles/impossible-swiftui-views/">which must be value types</a>, environment values can also be reference types.</p><blockquote><p>A kind reminder that <a href="https://docs.swift.org/swift-book/LanguageGuide/Closures.html#ID104">Swift closures are reference types</a>.</p></blockquote><h2>Classes</h2><p>Speaking of reference types, we can also define environment values with an associated class type.</p><p>There are important implications when using a class rather than a struct:</p><ul><li>we can alter a class instance, and the exact change will be reflected anywhere else that same instance is referenced in the view hierarchy.</li><li>views do not observe changes within classes defined in <code>EnvironmentValues</code>, regardless of whether the class is marked <code>ObservableObject</code>. If we're trying to do something similar to this, we should use environment objects instead.</li></ul><p>Example:</p><pre><code><span class="keyword">public class</span> FSClass {
  <span class="keyword">var</span> x: <span class="type">Int</span>

  <span class="keyword">init</span>(x: <span class="type">Int</span> = <span class="number">5</span>) {
    <span class="keyword">self</span>.<span class="property">x</span> = x
  }
}

<span class="keyword">private struct</span> FSClassKey: <span class="type">EnvironmentKey</span> {
  <span class="keyword">static let</span> defaultValue = <span class="type">FSClass</span>()
}

<span class="keyword">extension</span> <span class="type">EnvironmentValues</span> {
  <span class="keyword">public var</span> fsClass: <span class="type">FSClass</span> {
    <span class="keyword">get</span> { <span class="keyword">self</span>[<span class="type">FSClassKey</span>.<span class="keyword">self</span>] }
    <span class="keyword">set</span> { <span class="keyword">self</span>[<span class="type">FSClassKey</span>.<span class="keyword">self</span>] = newValue }
  }
}
</code></pre><p>Which we can then use like any other environment value:</p><pre><code><span class="comment">// Read:</span>

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">fsClass</span>) <span class="keyword">private var</span> fsClass <span class="comment">// 👈🏻</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">Text</span>(<span class="string">"</span>\(fsClass.<span class="property">x</span>)<span class="string">"</span>) <span class="comment">// 👈🏻</span>

      <span class="type">Button</span>(<span class="string">"change"</span>) {
        fsClass.<span class="property">x</span> = <span class="type">Int</span>.<span class="call">random</span>(in: <span class="number">1</span>...<span class="number">99</span>) 
        <span class="comment">// 👆🏻 fsClass is a class, we can modify its properties</span>
      }
    }
  }
}

<span class="comment">// Set:</span>

<span class="call">someView</span>()
  .<span class="call">environment</span>(\.<span class="property">fsClass</span>, <span class="type">FSClass</span>(x: <span class="number">1</span>))
</code></pre><p>There are very few reasons why we'd ever define a class-type environment value, but it's good to know that it's supported.</p><blockquote><p>SwiftUI built-in class environment values: <a href="https://developer.apple.com/documentation/swiftui/environmentvalues/managedObjectContext"><code>managedObjectContext</code></a> (<a href="https://developer.apple.com/documentation/coredata/nsmanagedobjectcontext"><code>NSManagedObjectContext</code></a>) and <a href="https://developer.apple.com/documentation/swiftui/environmentvalues/undoManager"><code>undoManager</code></a> (<a href="https://developer.apple.com/documentation/foundation/undomanager"><code>UndoManager</code></a>).</p></blockquote><h2>Conclusions</h2><p>We've now completed the Five Stars SwiftUI environment series, thank you for reading, and I hope you've found these articles helpful!</p><p>What else would you like me to cover next? Let me know via <a href="mailto:hello@fivestars.blog">email</a> or <a href="http://twitter.com/zntfdr">Twitter</a>!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/xcode-13-2-beta-1</guid><title>What's new in Xcode 13.2 beta 1</title><description>Large Content Viewer and a lot of documentation updates.</description><link>https://www.fivestars.blog/articles/xcode-13-2-beta-1</link><pubDate>Thu, 28 Oct 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Xcode 13.2 will definitively be remembered as the release that brought us Swift concurrency backward compatibility. Despite this, we have some very cool SwiftUI updates. Let's take a look!</p><h2>Large Content Viewer</h2><video autoplay muted loop>
  <source src="https://www.fivestars.blog/assets/posts/xcode-13-2-beta-1/lcv.mp4">
</video><pre><code><span class="type">VStack</span> {
  <span class="type">Button</span>(<span class="string">"Tap me"</span>) { <span class="call">print</span>(<span class="string">"Button Tapped"</span>) }
    .<span class="call">accessibilityShowsLargeContentViewer</span>()
  
  <span class="type">Button</span>(<span class="string">"Tap me 2"</span>) { <span class="call">print</span>(<span class="string">"Button Tapped"</span>) }
    .<span class="call">accessibilityShowsLargeContentViewer</span> {
      <span class="type">Text</span>(<span class="string">"A different text"</span>)
    }
}
</code></pre><p>Large Content Viewer is an accessibility feature that <a href="https://www.fivestars.blog/articles/large-content-viewer/">has been around since iOS 13</a>.<br>In short, when an accessibility dynamic type size is enabled, it allows us to enlarge/highlight the UI element the user is hovering on via a <a href="https://www.fivestars.blog/articles/swiftui-hud/">system HUD</a>.</p><p>With Xcode 13.2, we can now use it with SwiftUI as well.</p><p>Besides the two view modifiers shown in the example, SwiftUI now comes with a new <code>accessibilityLargeContentViewerEnabled</code> environment value, to be used in case we'd need to change some gesture behaviors to accommodate this accessibility feature.</p><blockquote><p>Both view modifiers and environment value are available from iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0.</p></blockquote><h2>Documentation</h2><p>While Xcode 13.2b1 doesn't bring many new SwiftUI features, we have a significant and welcome upgrade in SwiftUI's documentation.</p><p>We have new documentation for:</p><ul><li><a href="https://developer.apple.com/documentation/SwiftUI/SectionedFetchRequest"><code>SectionedFetchRequest</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/sectionedfetchresults"><code>SectionedFetchResults</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/text"><code>Text</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/textfield"><code>TextField</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/togglestyle"><code>ToggleStyle</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/view/togglestyle(_:)"><code>toggleStyle(_:)</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/togglestyleconfiguration"><code>ToggleStyleConfiguration</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/togglestyle/automatic"><code>ToggleStyle.automatic</code></a></li><li>All recent <a href="https://developer.apple.com/documentation/swiftui/view/background(_:in:fillstyle:)-20tq5"><code>background</code></a> and <a href="https://developer.apple.com/documentation/swiftui/view/overlay(_:in:fillstyle:)"><code>overlay</code></a> view modifiers</li></ul><p>Let's highlight a few important changes.</p><h3><code>TextField</code>'s <code>prompt</code> vs. <code>title</code></h3><p><a href="https://developer.apple.com/documentation/swiftui/textfield">The updated <code>TextField</code> documentation</a> has an entire section explaining why we have both a title/label and a prompt parameter, and the use of both:</p><pre><code><span class="comment">/// Each text field style determines where and when the text field uses a prompt and label.
/// For example, a form on macOS always places the label at the leading edge of the field 
/// and uses a prompt, when available, as placeholder text within the field itself. In the 
/// same context on iOS, the text field uses either the prompt or label as placeholder 
/// text, depending on whether the initializer provided a prompt.</span>
</code></pre><h3>(Toggle) Styles defaults and contextual defaults</h3><img src="https://www.fivestars.blog/assets/posts/xcode-13-2-beta-1/toggleStyle.png"/><p>SwiftUI's styling is <a href="https://www.fivestars.blog/articles/adaptive-swiftui-views/">highly adaptive</a> and can change widely based on the context. Until now, it was anyone's guess which style was adopted in which scenarios (and answered only via experimentation).</p><p>After this update, we <a href="https://developer.apple.com/documentation/swiftui/togglestyle/automatic">no longer have to guess</a>, as the documentation clearly states how our toggles will appear in different scenarios.</p><p>Looking forward to seeing all other styles get the same treatment.</p><h3>Wording: from "primitive" to "built-in"</h3><img src="https://www.fivestars.blog/assets/posts/xcode-13-2-beta-1/wording.png"/><p>When referring to baked-in SwiftUI definitions for views, styles, commands, etc., the official wording has been "primitive".</p><p>However, the term "primitive" is not always correct, as, sometimes, even baked-in definitions are composed of other SwiftUI components. To address this, the documentation has changed the wording from "primitive" to "built-in".</p><h3>Bolder soft deprecation</h3><img src="https://www.fivestars.blog/assets/posts/xcode-13-2-beta-1/dont.png"/><p>Despite SwiftUI having a clear direction on which syntax to use, most old definitions are still available and were soft deprecated with gentle messages like "You can also use X". New in Xcode 13.2, these messages are bolder and use imperative words instructing developers on what to use.</p><h2>Bug fixes</h2><p>From Xcode 13.2, <code>List</code>s and <code>Form</code>s respect the safe areas insets declared <a href="https://www.fivestars.blog/articles/safe-area-insets/">via <code>safeAreaInset(edge:content:)</code> view modifier</a>.</p><h2>Conclusions</h2><p>Did you find anything else interesting in Xcode 13.2 beta 1? Please <a href="https://twitter.com/zntfdr">let me know</a>!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/how-to-define-environment-values</guid><title>How to define custom SwiftUI environment values</title><description>How we can extend EnvironmentValues with our custom values</description><link>https://www.fivestars.blog/articles/how-to-define-environment-values</link><pubDate>Tue, 26 Oct 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Suppose we had to summarize in one sentence <a href="https://www.fivestars.blog/articles/environmentvalues/">what <code>EnvironmentValues</code> is</a>.<br>In that case, we'd probably come up with something like: it's a dictionary of key-value pairs passed down through the view hierarchy.</p><blockquote><p>SwiftUI's official definition is "A collection of environment values propagated through a view hierarchy", but it oversimplifies and hides a bit too much for Five Stars' standards.</p></blockquote><p>SwiftUI defines <a href="https://www.fivestars.blog/articles/swiftui-environment-values/">over 50 public environment values</a>, and, as we've seen <a href="https://www.fivestars.blog/articles/environmentvalues/">in a previous entry</a>, there are many more private.</p><p><code>EnvironmentValues</code> doesn't stop there: we can also create our own environment values. Let's explore how.</p><blockquote><p>This is the fifth entry of the Five Stars SwiftUI's Environment series. It's recommended to read the first few entries before proceeding: <a href="https://www.fivestars.blog/articles/swiftui-environment-propagation/">1</a>, <a href="https://www.fivestars.blog/articles/swiftui-environment-propagation-2/">2</a>, <a href="https://www.fivestars.blog/articles/swiftui-environment-propagation-3/">3</a>, <a href="https://www.fivestars.blog/articles/environmentvalues/">4</a>.</p></blockquote><p>Creating a new environment value takes two steps:</p><ol><li>define a new <code>EnvironmentKey</code></li><li>extend <code>EnvironmentValues</code> with our new value</li></ol><h3>1. Define a new <code>EnvironmentKey</code></h3><p>Here's SwiftUI's <code>EnvironmentKey</code> definition:</p><pre><code><span class="keyword">public protocol</span> EnvironmentKey {
  <span class="keyword">associatedtype</span> Value
  <span class="keyword">static var</span> defaultValue: <span class="type">Self</span>.<span class="type">Value</span> { <span class="keyword">get</span> }
}
</code></pre><p>This key has three main roles:</p><ol><li>it defines the type of the associated environment value (via <code>associatedtype</code>)</li><li>it helps SwiftUI identify the storage of the environment value</li><li>it defines the environment default value, used when the environment value has not been explicitly set</li></ol><p>This first step comes down to define a new <code>struct</code> conforming to <code>EnvironmentKey</code>, for example:</p><pre><code><span class="keyword">struct</span> FSNumberKey: <span class="type">EnvironmentKey</span> {
  <span class="keyword">static let</span> defaultValue: <span class="type">Int</span> = <span class="number">5</span>
}
</code></pre><p>Thanks to this definition, we have created a new environment value:</p><ol><li>…of type <code>Int</code> (as per <code>defaultValue</code> type)</li><li>…whose storage is identified by <code>FSNumberKey</code></li><li>…with default value <code>5</code></li></ol><p><code>EnvironmentValues</code> is now able to get and set any environment value that uses this <code>FSNumberKey</code> as its storage.</p><h3>2. Extend <code>EnvironmentValues</code> with our new value</h3><p>If the environment was just a place to store values, we would only need the first step. However, if we take a look at the <code>@Environment</code> property wrapper definition, we'd see the following:</p><pre><code><span class="keyword">@propertyWrapper public struct</span> Environment&lt;Value&gt;: <span class="type">DynamicProperty</span> {
  <span class="keyword">public init</span>(<span class="keyword">_</span> keyPath: <span class="type">KeyPath</span>&lt;<span class="type">EnvironmentValues</span>, <span class="type">Value</span>&gt;)
  <span class="keyword">public var</span> wrappedValue: <span class="type">Value</span> { <span class="keyword">get</span> }
}
</code></pre><p>Similarly, if we take a look at the <code>environment(_:,_:)</code> view modifier definition, we'd see:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">public func</span> environment&lt;V&gt;(<span class="keyword">_</span> keyPath: <span class="type">WritableKeyPath</span>&lt;<span class="type">EnvironmentValues</span>, <span class="type">V</span>&gt;, <span class="keyword">_</span> value: <span class="type">V</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span>
}
</code></pre><p>In both cases, we don't use the associated <code>EnvironmentKey</code> to access or write an environment value. Instead, both <code>@Environment</code> and the <code>environment(_:,_:)</code> view modifier ask for a <code>KeyPath</code> to a <code>EnvironmentValues</code> property.</p><p>This seems like an unnecessary step at first, but it unlocks all sorts of extra functionality.</p><p>Continuing with our <code>FSNumberKey</code> example, the most basic <code>EnvironmentValues</code> extension that we could make is:</p><pre><code><span class="keyword">extension</span> <span class="type">EnvironmentValues</span> {
  <span class="keyword">public var</span> fsNumber: <span class="type">Int</span> {
    <span class="keyword">get</span> {
      <span class="keyword">self</span>[<span class="type">FSNumberKey</span>.<span class="keyword">self</span>]
    } <span class="keyword">set</span> {
      <span class="keyword">self</span>[<span class="type">FSNumberKey</span>.<span class="keyword">self</span>] = newValue
    }
  }
}
</code></pre><p>Thanks to this extension, we can now set and read the <code>FSNumberKey</code>'s value via <code>EnvironmentValues</code>'s new <code>fsNumber</code> definition.</p><p>Setting <code>FSNumberKey</code>:</p><pre><code><span class="type">VStack</span> {
  <span class="type">ViewA</span>()
    .<span class="call">environment</span>(\.<span class="property">fsNumber</span>, <span class="number">1</span>) <span class="comment">// 👈🏻</span>
  <span class="type">ViewB</span>()
}
</code></pre><p>Reading <code>FSNumberKey</code>:</p><pre><code><span class="keyword">struct</span> ViewA: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">fsNumber</span>) <span class="keyword">private var</span> fsNumber: <span class="type">Int</span>  <span class="comment">// 👈🏻</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Text</span>(<span class="string">"</span>\(fsNumber)<span class="string">"</span>)
  }
}
</code></pre><h2>Unlocking <code>EnvironmentValues</code> potential</h2><p>Every time we set/access <code>FSNumberKey</code> via <code>fsNumber</code>, we are not directly accessing the underlying <code>EnvironmentValues</code> value, but we are executing <code>fsNumber</code>'s getter and setter closures. We can add more functionality based on our environment value needs. Let's see some examples.</p><h3>Non-negative value</h3><p>Let's say for example that we'd like to prevent <code>FSNumberKey</code> to ever have a negative value. Instead of adding extra logic in each view that sets this value, we can update our <code>EnvironmentValues</code> extension with the following:</p><pre><code><span class="keyword">extension</span> <span class="type">EnvironmentValues</span> {
  <span class="keyword">public var</span> fsNumber: <span class="type">Int</span> {
    <span class="keyword">get</span> { <span class="keyword">self</span>[<span class="type">FSNumberKey</span>.<span class="keyword">self</span>] }
    <span class="keyword">set</span> {
      <span class="keyword">self</span>[<span class="type">FSNumberKey</span>.<span class="keyword">self</span>] = <span class="call">max</span>(<span class="number">0</span>, newValue)
    }
  }
}
</code></pre><p>Regardless of the value we set via <code>environment(_:,_:)</code> view modifier, we will now always have a value equal or greater than zero.</p><pre><code><span class="type">VStack</span> {
  <span class="type">ViewA</span>() <span class="comment">// 👈🏻 ViewA will see fsNumber with value 0</span>
    .<span class="call">environment</span>(\.<span class="property">fsNumber</span>, -<span class="number">5</span>) <span class="comment">// 👈🏻 negative value, not allowed! Set to zero instead.</span>
  <span class="type">ViewB</span>()
}
</code></pre><p>Depending on our needs, we can even prevent to write to <code>EnvironmentValues</code> entirely and use the last valid assigned value instead:</p><pre><code><span class="keyword">extension</span> <span class="type">EnvironmentValues</span> {
  <span class="keyword">public var</span> fsNumber: <span class="type">Int</span> {
    <span class="keyword">get</span> { <span class="keyword">self</span>[<span class="type">FSNumberKey</span>.<span class="keyword">self</span>] }
    <span class="keyword">set</span> {
      <span class="keyword">if</span> newValue &gt;= <span class="number">0</span> {
        <span class="keyword">self</span>[<span class="type">FSNumberKey</span>.<span class="keyword">self</span>] = newValue <span class="comment">// 👈🏻 write only if newValue &gt; 0.</span>
      }
    }
  }
}
</code></pre><pre><code><span class="type">VStack</span> {
  <span class="type">ViewA</span>() <span class="comment">// 👈🏻 ViewA will see fsNumber with default value</span>
    .<span class="call">environment</span>(\.<span class="property">fsNumber</span>, -<span class="number">5</span>) <span class="comment">// 👈🏻 negative value, not allowed! This assignment is ignored.</span>
  <span class="type">ViewB</span>()
}
</code></pre><h3>Sharing the storage</h3><p>Most of the time, we will create a specific <code>EnvironmentKey</code> for each <code>EnvironmentValues</code> extension. However this is not enforced in any way. Consider the following example:</p><pre><code><span class="keyword">struct</span> FSSharedKey: <span class="type">EnvironmentKey</span> {
  <span class="keyword">static let</span> defaultValue: <span class="type">Int</span> = <span class="number">5</span>
}

<span class="keyword">extension</span> <span class="type">EnvironmentValues</span> {
  <span class="keyword">public var</span> fsSharedStorage: <span class="type">Int</span> {
    <span class="keyword">get</span> { <span class="keyword">self</span>[<span class="type">FSSharedKey</span>.<span class="keyword">self</span>] }
    <span class="keyword">set</span> { <span class="keyword">self</span>[<span class="type">FSSharedKey</span>.<span class="keyword">self</span>] = newValue }
  }

  <span class="keyword">public var</span> fsSharedStorage2: <span class="type">Int</span> {
    <span class="keyword">get</span> { <span class="keyword">self</span>[<span class="type">FSSharedKey</span>.<span class="keyword">self</span>] }
    <span class="keyword">set</span> { <span class="keyword">self</span>[<span class="type">FSSharedKey</span>.<span class="keyword">self</span>] = newValue }
  }
}
</code></pre><p>We can now use <code>fsSharedStorage</code> and <code>fsSharedStorage2</code> interchangeably:</p><pre><code><span class="type">ViewA</span>() <span class="comment">// 👈🏻 both fsSharedStorage and fsSharedStorage will be 6</span>
  .<span class="call">environment</span>(\.<span class="property">fsSharedStorage2</span>, <span class="number">6</span>)
</code></pre><h3>Read-only environment values</h3><p>Since <code>@Environment</code> requires only a <code>KeyPath&lt;EnvironmentValues, Value&gt;</code> and not a <code>WritableKeyPath&lt;EnvironmentValues, Value&gt;</code>, we could also create read-only environment values.</p><p>We have two main ways to implement such read-only behavior:</p><ol><li>the "traditional" way</li><li>the think outside the box way</li></ol><h4>1. The "traditional" way</h4><p>The traditional way would be to define a key that then we only read:</p><pre><code><span class="keyword">struct</span> FSReadOnlyNumberKey: <span class="type">EnvironmentKey</span> {
  <span class="keyword">static let</span> defaultValue: <span class="type">Int</span> = <span class="number">5</span>
}

<span class="keyword">extension</span> <span class="type">EnvironmentValues</span> {
  <span class="keyword">public var</span> fsReadOnlyNumber: <span class="type">Int</span> {
    <span class="keyword">get</span> {
      <span class="keyword">return self</span>[<span class="type">FSNumberKey</span>.<span class="keyword">self</span>]
    }
  }
}
</code></pre><p>Since there's no setter in <code>fsReadOnlyNumber</code>, we can only read this value and the associated <code>\.fsReadOnlyNumber</code> keypath is not a <code>WritableKeyPath</code>. Attempting to write into this value would cause a compiler error:</p><pre><code><span class="type">ViewA</span>()
  .<span class="call">environment</span>(\.<span class="property">fsReadOnlyValue</span>, <span class="number">6</span>) <span class="comment">// 🛑 Key path value type 'WritableKeyPath&lt;EnvironmentValues, Int&gt;' cannot be converted to contextual type 'KeyPath&lt;EnvironmentValues, Int&gt;'</span>
</code></pre><p>While this works great, as we've seen above, any <code>EnvironmentKey</code> definition can be shared among various <code>EnvironmentValues</code> extensions. Besides setting our read-only key as private, there's not much to prevent it from being written by malicious extensions.</p><h4>2. The think outside the box way</h4><p>To prevent our environment value from being written elsewhere, we can get rid of its storage altogether. We skip the key definition and write an <code>EnvironmentValues</code> extension that returns a value directly:</p><pre><code><span class="keyword">extension</span> <span class="type">EnvironmentValues</span> {
  <span class="keyword">public var</span> fsReadOnlyNumber: <span class="type">Int</span> {
    <span class="keyword">get</span> {
      <span class="keyword">return</span> <span class="number">5</span> <span class="comment">// 👈🏻 truly read-only environment value</span>
    }
  }
}
</code></pre><p>This value will still be accessed via <code>@Environment</code> but it no longer relies on the <code>EnvironmentValues</code> storage.</p><blockquote><p>Usage example: an app has a standard horizontal padding that never changes. Instead of using the same magic number throughout the codebase, we could define so via the environment. If we'd like the value to be dynamic in the future, we'd only need to change the <code>EnvironmentValues</code> extension implementation.</p></blockquote><h2>Conclusions</h2><p>When it comes to passing down data through the view hierarchy, SwiftUI's environment offers a powerful and "simple" way that has no equivalent in previous imperative UI frameworks such as UIKit and AppKit.</p><p>What uses do you have for SwiftUI's Environment? Have you seen anything that stands out?<br>Please let me know via <a href="mailto:hello@fivestars.blog">email</a> or <a href="https://twitter.com/zntfdr">Twitter</a>!</p><blockquote><p><a href="https://www.fivestars.blog/feed.rss">Stay tuned</a> for the final entry on SwiftUI's environment next week!</p></blockquote>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/environmentvalues</guid><title>EnvironmentValues</title><description>A dive into SwiftUI's EnvironmentValues, one key part of SwiftUI's environment propagation.</description><link>https://www.fivestars.blog/articles/environmentvalues</link><pubDate>Tue, 19 Oct 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Everything surrounding SwiftUI's Environment values comes from <a href="https://developer.apple.com/documentation/swiftui/environmentvalues"><code>EnvironmentValues</code></a>.</p><p><code>EnvironmentValues</code> is a struct defined within the SwiftUI SDK containing all the current modified environment values. Let's take a deeper look.</p><blockquote><p>This is the fourth entry of the Five Stars SwiftUI's Environment series. It's recommended to read the first three entries before proceeding: <a href="https://www.fivestars.blog/articles/swiftui-environment-propagation/">1</a>, <a href="https://www.fivestars.blog/articles/swiftui-environment-propagation-2/">2</a>, <a href="https://www.fivestars.blog/articles/swiftui-environment-propagation-3/">3</a>.</p></blockquote><h2><code>EnvironmentValues</code></h2><p>This is <code>EnvironmentValues</code>'s entire definition:</p><pre><code><span class="keyword">public struct</span> EnvironmentValues {
  <span class="keyword">public init</span>()
  <span class="keyword">public subscript</span>&lt;K: <span class="type">EnvironmentKey</span>&gt;(key: <span class="type">K</span>.<span class="type">Type</span>) -&gt; <span class="type">K</span>.<span class="type">Value</span>
}
</code></pre><blockquote><p>Debugging properties have been omitted for readability's sake.</p></blockquote><p>While not immediately apparent, it's important to note that the subscript is read-write, thus allowing both reading and modifying environment values.<br>The implementation would look like:</p><pre><code><span class="keyword">public struct</span> EnvironmentValues {
  <span class="keyword">public init</span>() { 
    ...
  }

  <span class="keyword">public subscript</span>&lt;K: <span class="type">EnvironmentKey</span>&gt;(key: <span class="type">K</span>.<span class="type">Type</span>) -&gt; <span class="type">K</span>.<span class="type">Value</span> {
    <span class="keyword">get</span> {
      ...
    }
    <span class="call">set</span>(newValue) { <span class="comment">// 👈🏻</span>
      ...
    }
  }
}
</code></pre><h3><code>EnvironmentKey</code></h3><p><code>EnvironmentValues</code> relies on a second definition, <code>EnvironmentKey</code>, which is a generic protocol defined as:</p><pre><code><span class="keyword">public protocol</span> EnvironmentKey {
  <span class="keyword">associatedtype</span> Value
  <span class="keyword">static var</span> defaultValue: <span class="type">Self</span>.<span class="type">Value</span> { <span class="keyword">get</span> }
}
</code></pre><p>This <code>EnvironmentKey</code>:</p><ul><li>lets us define the type of each environment value (via <code>associatedtype</code>)</li><li>helps SwiftUI identify the <strong>storage</strong> for any environment value</li><li>allows us to assign a default value for each key (more on this later)</li></ul><h3>A dictionary</h3><p>We can think of <code>EnvironmentValues</code> as a Swift dictionary wrapper, where:</p><ul><li>the dictionary type is along the lines of <code>[EnvironmentKey: Any]</code></li><li>each <code>EnvironmentKey</code> key will have an associated value of type <code>EnvironmentKey.Value</code></li><li>the dictionary will only contain all the values that have been explicitly set for that specific view (and its ancestors)</li></ul><p>Every view has its own, separate, <code>EnvironmentValues</code> instance. When we say that environment values are propagated through the view hierarchy, what really happens behind the scenes is that (a copy of) this dictionary is passed down from parent to child.</p><blockquote><p>This <code>EnvironmentValues</code> "dictionary" is not really a Swift dictionary but a private <code>PropertyList</code> type. Regardless, it helps thinking about it as a dictionary.</p></blockquote><h2>Example</h2><p>Let's make an example where we focus only on the environment values that we explicitly set.<br>Consider the following view:</p><pre><code><span class="type">VStack</span> {
  <span class="type">ViewA</span>()
    .<span class="call">foregroundColor</span>(.<span class="dotAccess">red</span>) <span class="comment">// 🔴</span>
  <span class="type">ViewB</span>()
}
</code></pre><p>If we generate a high-level view hierarchy, we will have:</p><pre><code><span class="type">VStack</span>
├── <span class="type">ViewA</span>
└── <span class="type">ViewB</span>
</code></pre><p>Since we don't set any environment value on <code>VStack</code>, its <code>EnvironmentValues</code> dictionary will be empty, <code>[:]</code>.</p><p>We also don't set any environment value on <code>ViewB</code>; <code>ViewB</code> will inherit the same dictionary as <code>VStack</code>, <code>[:]</code>.</p><p>On the other hand, we set the <code>foregroundColor</code> on <code>ViewA</code>:<br><code>ViewA</code> will inherit <code>VStack</code>'s dictionary, <code>[:]</code>, and add its <code>foregroundColor</code> on top, ending up with a dictionary similar to <code>[ForegroundColorKey: Color.red]</code>.</p><pre><code><span class="type">VStack</span>      <span class="comment">// Environment dictionary → [:]</span>
├── <span class="type">ViewA</span>   <span class="comment">// Environment dictionary → [ForegroundColorKey: 🔴], VStack + foregroundColor modifier</span>
└── <span class="type">ViewB</span>   <span class="comment">// Environment dictionary → [:], inherited from VStack</span>
</code></pre><p>Since every environment value comes with a default value, if a view tries to get a value that has not been explicitly set, <code>EnvironmentValues</code> will return the <code>EnvironmentKey.defaultValue</code> for the associated key.</p><p>All the above is true for simple environment keys/values: we will see more advanced use cases in the following article of the series.</p><h2>Dumping <code>EnvironmentValues</code>'s dictionary</h2><p>In the previous chapter, we ignored values that we hadn't explicitly set ourselves. However, a <em>real</em> <code>EnvironmentValues</code> dictionary has plenty of values coming from system settings, device characteristics, and all other values set on ancestor views.</p><p>Even when we don't set environment values ourselves, any view has an associated <code>EnvironmentValues</code> dictionary with 40+ environment values.<br>These values are not necessarily part of the public API, but are values that SwiftUI uses to determine each view's context, thus deciding how/what to draw.</p><p>For a sneak peek into the associated dictionary of any view, <a href="https://twitter.com/chriseidhof">Chris Eidhof</a> has a handy <a href="https://www.objc.io/blog/2019/10/29/swiftui-environment/"><code>DumpingEnvironment</code> code snippet</a>:</p><pre><code><span class="keyword">struct</span> DumpingEnvironment&lt;V: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="keyword">self</span>) <span class="keyword">var</span> env
  <span class="keyword">let</span> content: <span class="type">V</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">dump</span>(env)
    <span class="keyword">return</span> content
  }
}
</code></pre><p><code>DumpingEnvironment</code> wraps any view we're interested in, and prints in the debugger the associated <code>EnvironmentValues</code> instance (via <a href="https://developer.apple.com/documentation/swift/1539127-dump">Swift's <code>dump(_:name:indent:maxDepth:maxItems:)</code></a>).</p><p>Usage example:</p><pre><code><span class="type">DumpingEnvironment</span>(content: <span class="type">ContentView</span>())
</code></pre><p>Note that <code>DumpingEnvironment</code> is a <code>View</code> with its entire environment as a dependency. Make sure to use this only for debugging purposes.</p><h2>Conclusions</h2><p>As app developers, we don't directly interact with <code>EnvironmentValues</code> often. Instead, most interactions are done via proxies such as the <code>@Environment</code> property wrapper and the <code>environment(_:,_:)</code> view modifier.</p><p>In the next article, we will tie all of them together to get the bigger picture:<br>make sure to <a href="https://www.fivestars.blog/feed.rss">subscribe via feed RSS</a> or <a href="https://twitter.com/FiveStarsBlog">follow <code>@FiveStarsBlog</code> on Twitter</a>.</p><p>Thank you for reading!</p><blockquote><p>Questions? Feedback? Feel free to reach out via <a href="mailto:hello@fivestars.blog">email</a> or <a href="https://twitter.com/zntfdr">Twitter</a>!</p></blockquote>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-environment-propagation-3</guid><title>UIKit/AppKit and SwiftUI's Environment propagation</title><description>We continue our SwiftUI's environment exploration with advanced use cases and how it interacts with UIKit.</description><link>https://www.fivestars.blog/articles/swiftui-environment-propagation-3</link><pubDate>Wed, 13 Oct 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>In <a href="https://www.fivestars.blog/articles/swiftui-environment-propagation/">previous</a> <a href="https://www.fivestars.blog/articles/swiftui-environment-propagation-2/">articles</a> of the SwiftUI environment series, we've seen how the environment propagates across views and various presentations.</p><p>As long as a view is a child of another, regardless of the connection between the two (via presentation or containment), the view will inherit the environment from their parent (plus any modification applied in-between).</p><p>This propagation happens even while many SwiftUI views are wrappers of their UIKit/AppKit counterparts (e.g., <code>TabView</code> and <code>ScrollView</code>).<br>This behavior is not limited to just SwiftUI wrappers: we can build our AppKit/UIKit SwiftUI wrappers, and the environment will be auto-magically propagated for us. In this short article, let's see an example of this.</p><p>In any app, it's reasonable to want to mix SwiftUI with previous UI frameworks. An example could be using SwiftUI as the content of <code>UITableViewCell</code>s.</p><p>In order to do this we'll use <a href="https://twitter.com/noahsark769">Noah Gilmore</a>'s <a href="https://noahgilmore.com/blog/swiftui-self-sizing-cells/"><code>HostingCell&lt;Content: View&gt;: UITableViewCell</code></a> definition:</p><pre><code><span class="keyword">final class</span> HostingCell&lt;Content: <span class="type">View</span>&gt;: <span class="type">UITableViewCell</span> {
  <span class="keyword">private let</span> hostingController = <span class="type">UIHostingController</span>&lt;<span class="type">Content</span>?&gt;(rootView: <span class="keyword">nil</span>)

  <span class="keyword">override init</span>(style: <span class="type">UITableViewCell</span>.<span class="type">CellStyle</span>, reuseIdentifier: <span class="type">String</span>?) {
    <span class="keyword">super</span>.<span class="keyword">init</span>(style: style, reuseIdentifier: reuseIdentifier)
    hostingController.<span class="property">view</span>.<span class="property">backgroundColor</span> = .<span class="dotAccess">clear</span>
  }

  <span class="keyword">required init</span>?(coder aDecoder: <span class="type">NSCoder</span>) {
    <span class="call">fatalError</span>(<span class="string">"init(coder:) has not been implemented"</span>)
  }

  <span class="keyword">func</span> set(rootView: <span class="type">Content</span>, parentController: <span class="type">UIViewController</span>) {
    <span class="keyword">self</span>.<span class="property">hostingController</span>.<span class="property">rootView</span> = rootView
    <span class="keyword">self</span>.<span class="property">hostingController</span>.<span class="property">view</span>.<span class="call">invalidateIntrinsicContentSize</span>()

    <span class="keyword">let</span> requiresControllerMove = hostingController.<span class="property">parent</span> != parentController
    <span class="keyword">if</span> requiresControllerMove {
      parentController.<span class="call">addChild</span>(hostingController)
    }

    <span class="keyword">if</span> !<span class="keyword">self</span>.<span class="property">contentView</span>.<span class="property">subviews</span>.<span class="call">contains</span>(hostingController.<span class="property">view</span>) {
      <span class="keyword">self</span>.<span class="property">contentView</span>.<span class="call">addSubview</span>(hostingController.<span class="property">view</span>)
      hostingController.<span class="property">view</span>.<span class="property">translatesAutoresizingMaskIntoConstraints</span> = <span class="keyword">false</span>
      contentView.<span class="call">addConstraints</span>([
        hostingController.<span class="property">view</span>.<span class="property">leadingAnchor</span>.<span class="call">constraint</span>(equalTo: contentView.<span class="property">leadingAnchor</span>),
        hostingController.<span class="property">view</span>.<span class="property">trailingAnchor</span>.<span class="call">constraint</span>(equalTo: contentView.<span class="property">trailingAnchor</span>),
        hostingController.<span class="property">view</span>.<span class="property">topAnchor</span>.<span class="call">constraint</span>(equalTo: contentView.<span class="property">topAnchor</span>),
        hostingController.<span class="property">view</span>.<span class="property">bottomAnchor</span>.<span class="call">constraint</span>(equalTo: contentView.<span class="property">bottomAnchor</span>)
      ])
    }

    <span class="keyword">if</span> requiresControllerMove {
      hostingController.<span class="call">didMove</span>(toParent: parentController)
    }
  }
}
</code></pre><blockquote><p>Check out <a href="https://twitter.com/noahsark769">Noah</a>'s <a href="https://noahgilmore.com/blog/swiftui-self-sizing-cells/"><code>Self-Sizing UITableView Cells with SwiftUI</code></a> article for more details on this.</p></blockquote><p>With <code>HostingCell</code> we can now embed SwiftUI views as cells in a <code>UITableViewController</code>'s <code>tableView</code>.</p><p>Let's define a simple cell:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-environment-propagation-3/cell.png"/><pre><code><span class="keyword">struct</span> CellView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">HStack</span> {
      <span class="type">Text</span>(<span class="string">"SwiftUI Text"</span>)
      <span class="type">Spacer</span>()
    }
    .<span class="call">padding</span>()
  }
}
</code></pre><p>Next, let's define a <code>UITableViewController</code> using this new cell:</p><video autoplay muted loop>
  <source src="https://www.fivestars.blog/assets/posts/swiftui-environment-propagation-3/table.mp4">
</video><pre><code><span class="keyword">final class</span> TableViewController: <span class="type">UITableViewController</span> {
  <span class="keyword">override init</span>(style: <span class="type">UITableView</span>.<span class="type">Style</span>) {
    <span class="keyword">super</span>.<span class="keyword">init</span>(style: style)
    tableView.<span class="call">register</span>(<span class="type">HostingCell</span>&lt;<span class="type">CellView</span>&gt;.<span class="keyword">self</span>, forCellReuseIdentifier: <span class="string">"HostingCell&lt;CellView&gt;"</span>)
    tableView.<span class="property">separatorStyle</span> = .<span class="dotAccess">none</span>
  }

  <span class="keyword">required init</span>?(coder: <span class="type">NSCoder</span>) {
    <span class="call">fatalError</span>(<span class="string">"init(coder:) has not been implemented"</span>)
  }

  <span class="keyword">override func</span> numberOfSections(in tableView: <span class="type">UITableView</span>) -&gt; <span class="type">Int</span> {
    <span class="number">1</span>
  }

  <span class="keyword">override func</span> tableView(<span class="keyword">_</span> tableView: <span class="type">UITableView</span>, numberOfRowsInSection section: <span class="type">Int</span>) -&gt; <span class="type">Int</span> {
    <span class="number">300</span>
  }

  <span class="keyword">override func</span> tableView(<span class="keyword">_</span> tableView: <span class="type">UITableView</span>, cellForRowAt indexPath: <span class="type">IndexPath</span>) -&gt; <span class="type">UITableViewCell</span> {
    <span class="keyword">let</span> cell = tableView.<span class="call">dequeueReusableCell</span>(withIdentifier: <span class="string">"HostingCell&lt;CellView&gt;"</span>, for: indexPath) <span class="keyword">as</span>! <span class="type">HostingCell</span>&lt;<span class="type">CellView</span>&gt;
    cell.<span class="call">set</span>(rootView: <span class="type">CellView</span>(), parentController: <span class="keyword">self</span>)
    <span class="keyword">return</span> cell
  }
}
</code></pre><p>We now have a working UIKit view hosting SwiftUI views. Let's wrap this new <code>TableViewController</code> into a SwiftUI view, we don't need to do anything beside wrapping:</p><pre><code><span class="keyword">struct</span> TableViewWrapper: <span class="type">UIViewControllerRepresentable</span> {
  <span class="keyword">func</span> makeUIViewController(context: <span class="type">Context</span>) -&gt; <span class="keyword">some</span> <span class="type">UIViewController</span> {
    <span class="type">TableViewController</span>(style: .<span class="dotAccess">plain</span>)
  }

  <span class="keyword">func</span> updateUIViewController(<span class="keyword">_</span> uiViewController: <span class="type">UIViewControllerType</span>, context: <span class="type">Context</span>) {
  }
}
</code></pre><p>Let's recap what we have:</p><ul><li><code>CellView</code>, a simple SwiftUI view</li><li><code>HostingCell</code>, a generic <code>UITableViewCell</code> accepting SwiftUI views</li><li><code>TableViewController</code>, a <code>UIViewController</code> displaying multiple <code>HostingCell</code> as part of its <code>tableView</code> content</li><li><code>TableViewWrapper</code>, a SwiftUI view wrapping <code>TableViewController</code></li></ul><p>Despite having UIKit views/controllers in between, <code>CellView</code> will inherit <code>TableViewWrapper</code>'s environment.</p><p>To prove this, let's define a view that will set <code>TableViewWrapper</code>'s foreground color:</p><video autoplay muted loop>
  <source src="https://www.fivestars.blog/assets/posts/swiftui-environment-propagation-3/contentview.mp4">
</video><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> color: <span class="type">Color</span> = .<span class="dotAccess">black</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">ColorPicker</span>(<span class="string">"Choose foreground color"</span>, selection: $color)
        .<span class="call">pickerStyle</span>(.<span class="dotAccess">inline</span>)
        .<span class="call">font</span>(.<span class="dotAccess">title</span>)
        .<span class="call">padding</span>(.<span class="dotAccess">horizontal</span>)

      <span class="type">TableViewWrapper</span>()
        .<span class="call">foregroundColor</span>(color) <span class="comment">// 👈🏻</span>
    }
    .<span class="call">ignoresSafeArea</span>(edges: .<span class="dotAccess">bottom</span>)
  }
}
</code></pre><p>Note that we didn't have to do any extra work to make this possible. We never had to reach for the environment ourselves, nor use <code>@Environment</code>:<br><code>CellView</code>'s <code>Text</code> will automatically inherit and use the foreground color set on <code>TableViewWrapper</code>.</p><blockquote><p>This propagation works both for environment values and objects.</p></blockquote><h2>Conclusions</h2><p>While SwiftUI might be the recommended framework for any future project, the SwiftUI team has worked hard to ensure excellent integration with previous UI frameworks. Despite having to go through multiple UIKit layers, this seamless environment propagation is yet another example of this.</p><p>Thank you for reading!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-environment-propagation-2</guid><title>Advanced Environment propagation</title><description>We continue our SwiftUI's environment exploration with more advanced scenarios.</description><link>https://www.fivestars.blog/articles/swiftui-environment-propagation-2</link><pubDate>Tue, 5 Oct 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>In <a href="https://www.fivestars.blog/articles/swiftui-environment-propagation/">the first article</a> of the SwiftUI environment series, we covered the foundations of this core SwiftUI component. Next, let's dive into more advanced use cases, which are a widespread source of ambiguity.</p><h2>View presentation and environment propagation</h2><p>Everything we've seen so far could be summarized as: children views inherit the environment from their parent (plus additional changes). Things get tricky when we add view presentation into the mix.</p><p>That is, navigating to other views or presenting sheets. Before diving into these, we need to step back and introduce one more topic: UIKit's view controller hierarchy.</p><h3>The View Controller Hierarchy</h3><p>Like the view hierarchy we covered we covered <a href="https://www.fivestars.blog/articles/swiftui-environment-propagation/">last time</a>, we can also create a hierarchy among <code>UIViewController</code>s.</p><p>We can split the view controllers parent-child relationships into two categories, contained and presenting.</p><h4>Contained parent-child relationship</h4><p>A given view controller might contain several child view controllers dedicated to different sections of the screen. When they do so, these controllers are called Container View Controllers, and are considered the parents of the child view controllers.</p><p>We can build our own container view controllers, and UIKit comes with a few pre-built ones, for example, <a href="https://developer.apple.com/documentation/uikit/uinavigationcontroller"><code>UINavigationController</code></a> and <a href="https://developer.apple.com/documentation/uikit/uisplitviewcontroller"><code>UISplitViewController</code></a>.</p><blockquote><p>It's essential to know and understand the relationship among its children.</p></blockquote><p>Let's imagine that we have a navigation controller and have pushed two screens into the navigation stack. At this point, the navigation controller's <code>viewControllers</code> stack has three view controllers in total. this is the current hierarchy:</p><pre><code><span class="type">UINavigationController</span>
├── <span class="type">RootViewController</span>
├── <span class="type">FirstPushedViewController</span>
└── <span class="type">SecondPushedViewController</span>
</code></pre><p>Despite a view controller pushing the next into the navigation, all view controllers in the navigation stack are siblings, and all share the same parent view controller, the <code>UINavigationController</code>.</p><p>Next, if we take a look at an <a href="https://developer.apple.com/documentation/uikit/uitabbarcontroller"><code>UITabBarController</code></a>, we will see the same pattern again:</p><pre><code><span class="type">UITabBarController</span>
├── <span class="type">FirstTabViewController</span>
├── <span class="type">SecondTabViewController</span>
└── ...
</code></pre><p>Note also that any container view controller can have one or more container view controllers as its children.</p><p>We use:</p><ul><li><code>UIViewController</code>'s <code>parentViewController: UIViewController?</code> property to get the parent of a <code>UIViewController</code></li><li>each container <a href="https://developer.apple.com/documentation/uikit/uinavigationcontroller/1621873-viewcontrollers"><code>viewControllers: [UIViewController]</code></a> property to get its children</li></ul><blockquote><p>We can print the view controller hierarchy at any time in <code>lldb</code> via<code>po UIWindow.value(forKeyPath: "keyWindow.rootViewController._printHierarchy")!</code></p></blockquote><h4>Presenting parent-child relationship</h4><p>Instead of containing children, this relationship represents the connection between one view controller and the one presenting modally.</p><p>This relationship is established when we call <code>present(_:animated:completion:)</code> from a view controller to present another one.</p><p>In this case, the 1-1 relationship has the presenting view controller as the parent of the presented view controller:</p><pre><code><span class="type">PresentingViewController</span>
└── <span class="type">PresentedViewController</span>
</code></pre><p>In other words, unlike navigation, a controller presented modally will always have its presenting view controller as its parent. Here's an example of hierarchy where a modal view controller present another view controller modally, which then present another view controller modally:</p><pre><code><span class="type">NonModalViewController</span>
└── <span class="type">FirstModalViewController</span>
    └── <span class="type">SecondModalViewController</span>
        └── <span class="type">ThirdModalViewController</span>
</code></pre><p>We use:</p><ul><li><code>UIViewController</code>'s <code>presentingViewController: UIViewController?</code> property to get the view controller that is presenting our view controller</li><li><code>UIViewController</code>'s <code>presentedViewController: UIViewController?</code> property to get the view controller presented by our view controller</li></ul><h3>SwiftUI's View Controller Hierarchy</h3><p>Moving back to SwiftUI, <code>UIViewController</code>s are just gone (for argument's sake, let's forget about <code>UIHostingController</code>). This is because:</p><ul><li>container view controllers are now just <code>View</code>s. For example <code>UINavigationController</code> is <code>NavigationView</code>, and <code>UITabBarController</code> is <code>TabView</code>:</li></ul><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TabView</span> {
      <span class="type">FirstTabView</span>()
        .<span class="call">tabItem</span> { <span class="type">Label</span>(<span class="string">"One"</span>, systemImage: <span class="string">"1.circle.fill"</span>) }
      <span class="type">SecondTabView</span>()
        .<span class="call">tabItem</span> { <span class="type">Label</span>(<span class="string">"Two"</span>, systemImage: <span class="string">"2.circle.fill"</span>) }
      ...
    }
  }
}
</code></pre><ul><li>view presentations are controlled via bindings and view modifiers like <code>sheet(isPresented:onDismiss:content:)</code>:</li></ul><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">@State var</span> showingSheet = <span class="keyword">false
  
  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(<span class="string">"Show sheet"</span>) {
      showingSheet.<span class="call">toggle</span>()
    }
    .<span class="call">sheet</span>(isPresented: $showingSheet) {
      <span class="type">FSSheetView</span>()
    }
  }
}
</code></pre><p>Despite this, the view controller hierarchy has migrated 1-1 to SwiftUI. For example, these are the two view hierarchies for the last two examples:</p><ul><li><code>TabBar</code> example:</li></ul><pre><code><span class="type">FSView</span>
└── <span class="type">TabView</span>
    ├── <span class="type">FirstTabView</span>
    ├── <span class="type">SecondTabView</span>
    └── ...
</code></pre><ul><li>Sheet example (when the sheet is presented):</li></ul><pre><code><span class="type">FSView</span>
└── <span class="type">FSSheetView</span>
</code></pre><h2>SwiftUI Environment Propagation and View Controller Hierarchy</h2><p>Since SwiftUI doesn't have <code>UIView</code>s or <code>UIViewController</code>s, UIKit's view and view controller hierarchies are merged into a single hierarchy in SwiftUI.</p><p>SwiftUI's environment is propagated across views and view controllers without any behavior change. All the logic we've covered in the <a href="https://www.fivestars.blog/articles/swiftui-environment-propagation/">first article in the series</a> still counts when dealing with view presentations.</p><p>In conclusion, understanding UIKit's view controller hierarchy enables us to understand advanced SwiftUI's environment propagation.</p><p>Let's now take a few examples to consolidate/confirm what we have uncovered. We will continue to use <code>foregroundColor</code> as our environment value.</p><h3><code>TabView</code></h3><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TabView</span> {
      <span class="type">FirstTabView</span>()
        .<span class="call">tabItem</span> { <span class="type">Label</span>(<span class="string">"One"</span>, systemImage: <span class="string">"1.circle.fill"</span>) }
      <span class="type">SecondTabView</span>()
        .<span class="call">tabItem</span> { <span class="type">Label</span>(<span class="string">"Two"</span>, systemImage: <span class="string">"2.circle.fill"</span>) }
      ...
    }
  }
}
</code></pre><pre><code><span class="type">FSView</span>
└── <span class="type">TabView</span>
    ├── <span class="type">FirstTabView</span>
    ├── <span class="type">SecondTabView</span>
    └── ...
</code></pre><p>If we set the foreground color to a specific tab, only that tab view and its descendants will inherit the color:</p><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TabView</span> {
      <span class="type">FirstTabView</span>()
        .<span class="call">tabItem</span> { ... }
      <span class="type">SecondTabView</span>()
        .<span class="call">foregroundColor</span>(.<span class="dotAccess">red</span>) <span class="comment">// 🔴</span>
        .<span class="call">tabItem</span> { ... }
      ...
    }
  }
}
</code></pre><pre><code><span class="type">FSView</span>
└── <span class="type">TabView</span>
    ├── <span class="type">FirstTabView</span>
    ├── <span class="type">SecondTabView</span> 🔴
    └── ...
</code></pre><p>All <code>SecondTabView</code> siblings won't be affected by any change made into <code>SecondTabView</code>'s environment, as only the tab parent, <code>TabView</code>, can affect <code>SecondTabView</code>'s siblings environment.</p><p>If we'd like to set the same foreground color to all tab views, we have two ways:</p><ol><li>set it to each tab</li></ol><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TabView</span> {
      <span class="type">FirstTabView</span>()
        .<span class="call">foregroundColor</span>(.<span class="dotAccess">red</span>) <span class="comment">// 🔴</span>
        .<span class="call">tabItem</span> { ... }
      <span class="type">SecondTabView</span>()
        .<span class="call">foregroundColor</span>(.<span class="dotAccess">red</span>) <span class="comment">// 🔴</span>
        .<span class="call">tabItem</span> { ... }
      ...
        .<span class="call">foregroundColor</span>(.<span class="dotAccess">red</span>) <span class="comment">// 🔴</span>
    }
  }
}
</code></pre><pre><code><span class="type">FSView</span>
└── <span class="type">TabView</span>
    ├── <span class="type">FirstTabView</span> 🔴
    ├── <span class="type">SecondTabView</span> 🔴
    └── ... 🔴
</code></pre><ol start="2"><li>set it on the <code>TabView</code> (or a <code>TabView</code>'s ancestor)</li></ol><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TabView</span> {
      <span class="type">FirstTabView</span>()
        .<span class="call">tabItem</span> { ... }
      <span class="type">SecondTabView</span>()
        .<span class="call">tabItem</span> { ... }
      ...
    }
    .<span class="call">foregroundColor</span>(.<span class="dotAccess">red</span>) <span class="comment">// 🔴</span>
  }
}
</code></pre><pre><code><span class="type">FSView</span>
└── <span class="type">TabView</span> 🔴
    ├── <span class="type">FirstTabView</span> 🔴
    ├── <span class="type">SecondTabView</span> 🔴
    └── ... 🔴
</code></pre><h3>Sheets</h3><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">@State var</span> showingSheet = <span class="keyword">false
  
  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(<span class="string">"Show sheet"</span>) {
      showingSheet.<span class="call">toggle</span>()
    }
    .<span class="call">sheet</span>(isPresented: $showingSheet) {
      <span class="type">FSSheetView</span>()
    }
  }
}
</code></pre><pre><code><span class="type">FSView</span>
└── <span class="type">FSSheetView</span>
</code></pre><p>When we present sheets, the environment inherited by those sheets is the same as the environment seen by the associated <code>sheet(...)</code> modifier.</p><p>We can consider the <code>sheet</code> modifier as the anchor point of our sheet view, expanding the view controller hierarchy above:</p><pre><code><span class="type">FSView</span>
└── sheet view modifier
    ├── <span class="type">Button</span>
    └── <span class="type">FSSheetView</span>
</code></pre><p>Note how the presented sheet, <code>FSSheetView</code>, and the view where the sheet is applied to, <code>Button</code>, are siblings.</p><p>With this in mind, we can study different scenarios:</p><ul><li>the foreground color is set onto the sheet view modifier</li></ul><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">@State var</span> showingSheet = <span class="keyword">false
  
  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(<span class="string">"Show sheet"</span>) {
      showingSheet.<span class="call">toggle</span>()
    }
    .<span class="call">sheet</span>(isPresented: $showingSheet) {
      <span class="type">FSSheetView</span>()
    }
    .<span class="call">foregroundColor</span>(.<span class="dotAccess">red</span>) <span class="comment">// 🔴</span>
  }
}
</code></pre><pre><code><span class="type">FSView</span>
└── sheet view modifier 🔴
    ├── <span class="type">Button</span> 🔴
    └── <span class="type">FSSheetView</span> 🔴
</code></pre><ul><li>the foreground color is set after the sheet view modifier</li></ul><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">@State var</span> showingSheet = <span class="keyword">false
  
  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(<span class="string">"Show sheet"</span>) {
      showingSheet.<span class="call">toggle</span>()
    }
    .<span class="call">foregroundColor</span>(.<span class="dotAccess">red</span>) <span class="comment">// 🔴</span>
    .<span class="call">sheet</span>(isPresented: $showingSheet) {
      <span class="type">FSSheetView</span>()
    }
  }
}
</code></pre><pre><code><span class="type">FSView</span>
└── sheet view modifier
    ├── <span class="type">Button</span> 🔴
    └── <span class="type">FSSheetView</span>
</code></pre><ul><li>the foreground color is set on the sheet view</li></ul><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">@State var</span> showingSheet = <span class="keyword">false
  
  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(<span class="string">"Show sheet"</span>) {
      showingSheet.<span class="call">toggle</span>()
    }
    .<span class="call">sheet</span>(isPresented: $showingSheet) {
      <span class="type">FSSheetView</span>()
        .<span class="call">foregroundColor</span>(.<span class="dotAccess">red</span>) <span class="comment">// 🔴</span>
    }
  }
}
</code></pre><pre><code><span class="type">FSView</span>
└── sheet view modifier
    ├── <span class="type">Button</span>
    └── <span class="type">FSSheetView</span> 🔴
</code></pre><p>Which scenario we need/want to use is up to our needs. They're all acceptable and very justified to exist.</p><blockquote><p>The same concepts are repeated when we have multiple sheet view modifiers and/or a chain of presented sheets.</p></blockquote><h4>iOS 13 vs iOS 14+</h4><p>Before iOS 14 the environment was <strong>not</strong> propagated to the presented sheet. As we have seen in UIKit's view controller hierarchy, this was unexpected and is considered a bug in SwiftUI's environment behavior.</p><p>If our app targets iOS 13 and later, we need to keep this in mind and manually inject the environment values/objects that we need in our sheets.</p><h3>Navigation</h3><p>Imagine the following view definition:</p><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">FSRootView</span>()
    }
  }
}

<span class="keyword">struct</span> FSRootView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationLink</span>(<span class="string">"tap"</span>, destination: <span class="type">FSFirstPushedView</span>())
  }
}

<span class="keyword">struct</span> FSFirstPushedView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationLink</span>(<span class="string">"tap"</span>, destination: <span class="type">FSSecondPushedView</span>())
  }
}

<span class="keyword">struct</span> FSSecondPushedView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    ...
  }
}
</code></pre><p>As we've seen in UIKit's view controller hierarchy, If we navigate from root to <code>FSSecondPushedView</code>, the resulting hierarchy will be:</p><pre><code><span class="type">FSView</span>
└── <span class="type">NavigationView</span>
    ├── <span class="type">FSRootView</span>
    ├── <span class="type">FSFirstPushedView</span>
    └── <span class="type">FSSecondPushedView</span>
</code></pre><p>All pushed views (and root) are <strong>siblings</strong>, despite being declared in the <code>NavigationLink</code> of another view. In other words, <strong><code>NavigationLink</code>'s <code>destination</code> doesn't share the same environment seen by <code>NavigationLink</code></strong>:</p><pre><code><span class="type">NavigationLink</span>(<span class="string">"tap"</span>, destination: <span class="type">DestinationView</span>())
  .<span class="call">foregroundColor</span>(.<span class="dotAccess">red</span>) <span class="comment">// 👈🏻 doesn't affect DestinationView</span>
</code></pre><p>If we'd like to share environment values between pushing and pushed views, we have two options:</p><ol><li>manually set the values in each <code>NavigationLink</code>'s <code>destination</code>:</li></ol><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">FSRootView</span>()
        .<span class="call">foregroundColor</span>(.<span class="dotAccess">red</span>) <span class="comment">// 🔴</span>
    }
  }
}

<span class="keyword">struct</span> FSRootView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationLink</span>(
      <span class="string">"tap"</span>, 
      destination: <span class="type">FSFirstPushedView</span>().<span class="call">foregroundColor</span>(.<span class="dotAccess">red</span>) <span class="comment">// 🔴</span>
    )
  }
}

<span class="keyword">struct</span> FSFirstPushedView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationLink</span>(
      <span class="string">"tap"</span>, 
      destination: <span class="type">FSSecondPushedView</span>().<span class="call">foregroundColor</span>(.<span class="dotAccess">red</span>) <span class="comment">// 🔴</span>
    )
  }
}

<span class="keyword">struct</span> FSSecondPushedView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    ...
  }
}
</code></pre><pre><code><span class="type">FSView</span>
└── <span class="type">NavigationView</span>
    ├── <span class="type">FSRootView</span> 🔴
    ├── <span class="type">FSFirstPushedView</span> 🔴
    └── <span class="type">FSSecondPushedView</span> 🔴
</code></pre><ol start="2"><li>set the values in <code>NavigationView</code> or another common ancestor:</li></ol><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">FSRootView</span>()
    }
    .<span class="call">foregroundColor</span>(.<span class="dotAccess">red</span>) <span class="comment">// 🔴</span>
  }
}

...
</code></pre><pre><code><span class="type">FSView</span>
└── <span class="type">NavigationView</span> 🔴
    ├── <span class="type">FSRootView</span> 🔴
    ├── <span class="type">FSFirstPushedView</span> 🔴
    └── <span class="type">FSSecondPushedView</span> 🔴
</code></pre><blockquote><p>The latter option is recommended, especially when working with an <a href="https://www.fivestars.blog/articles/app-state/">app-wide state</a> pattern.</p></blockquote><p>Neither <code>.navigationViewStyle(.stack)</code> nor <code>.isDetailLink(false)</code> affect the environment propagation.</p><h2>Conclusions</h2><p>Despite SwiftUI being a massive departure from AppKit/UIKit, there's no way around it: to master SwiftUI, we need to <s>master</s> understand the UI frameworks SwiftUI relies on.</p><p>You've just completed the second entry of Five Stars' SwiftUI Environment series; make sure to <a href="https://www.fivestars.blog/feed.rss">subscribe via feed RSS</a> or <a href="https://twitter.com/FiveStarsBlog">follow <code>@FiveStarsBlog</code> on Twitter</a> to not miss out on upcoming entries and more deep dives.</p><p>Thank you for reading!</p><blockquote><p>Questions? Feedback? Feel free to reach out via <a href="mailto:hello@fivestars.blog">email</a> or <a href="https://twitter.com/zntfdr">Twitter</a>!</p></blockquote><h2>References</h2><ul><li><a href="https://developer.apple.com/library/archive/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/Introduction/Introduction.html#//apple_ref/doc/uid/TP40009503-CH1-SW2">About Windows and Views - Apple Documentation Archive</a></li><li><a href="https://www.wwdcnotes.com/notes/wwdc19/212/">Introducing Multiple Windows on iPad - WWDC Notes</a></li><li><a href="https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/TheViewControllerHierarchy.html#//apple_ref/doc/uid/TP40007457-CH33-SW1">The View Controller Hierarchy - Apple Documentation Archive</a></li></ul>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-environment-propagation</guid><title>The SwiftUI Environment</title><description>What it is, how it's propagated through the view hierarchy, how to read and write values, and much more!</description><link>https://www.fivestars.blog/articles/swiftui-environment-propagation</link><pubDate>Tue, 28 Sep 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Understanding SwiftUI's Environment is a mandatory step on the journey of learning SwiftUI. In this new article series, we will dive into many aspects of this core SwiftUI component.</p><p>Alongside SwiftUI's Environment, we will introduce a few concepts here and there; let's wait no further!</p><h2>View Hierarchy</h2><p>A View Hierarchy establishes the relationship among all views in any given window.</p><p>This hierarchy is connected by many parent-child relationships. Given any view:</p><ul><li>the view containing that view is known as its parent view (a.k.a. the superview)</li><li>the views within that view are known as its children views (a.k.a. subviews)</li></ul><p>Every view in the hierarchy has:</p><ul><li>(up to) one parent/superview</li><li>zero or more children/subviews</li></ul><blockquote><p>In UIKit, we can access both superview and subviews of any <code>UIView</code> via its <a href="https://developer.apple.com/documentation/uikit/uiview/1622474-superview"><code>superview: UIView?</code></a> and <a href="https://developer.apple.com/documentation/uikit/uiview/1622614-subviews"><code>subviews: [UIView]</code></a> properties.</p></blockquote><p>Views that share the same parent/superview are called siblings, every view can have zero or more siblings.</p><h3>A tree structure</h3><p>If we zoom out, a view hierarchy can be seen as an inverted tree structure, where:</p><ul><li>each node is a <code>UIView</code>/<code>View</code></li><li>every link is a parent-child relationship</li></ul><p>The top-most node, also known as the root of our tree, is <code>UIWindow</code>'s <a href="https://developer.apple.com/documentation/uikit/uiwindow/1621581-rootviewcontroller"><code>rootViewController</code></a>'s <a href="https://developer.apple.com/documentation/uikit/uiviewcontroller/1621460-view"><code>view</code></a>.</p><p>Everything we display on screen will have this root view as its ancestor.</p><blockquote><p>We can print the view hierarchy at any time in <code>lldb</code> via<code>po UIWindow.value(forKeyPath: "keyWindow.rootViewController.view.recursiveDescription")!</code></p></blockquote><h3>A SwiftUI Example</h3><p>So far, we've been talking primarily in UIKit terms: the same definitions apply to SwiftUI.</p><p>Let's define a small SwiftUI view:</p><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">HStack</span> {
        <span class="type">ViewA</span>()
        <span class="type">ViewB</span>()
      }
      <span class="type">ViewC</span>()
    }
  }
}
</code></pre><p>Everything we define in our view <code>body</code> is a descendant of <code>FSView</code>. This is the complete <code>FSView</code> view hierarchy:</p><pre><code><span class="type">FSView</span>
└── <span class="type">VStack</span>
    ├── <span class="type">HStack</span>
    │   ├── <span class="type">ViewA</span>
    │   └── <span class="type">ViewB</span>
    └── <span class="type">ViewC</span>
</code></pre><p>Where:</p><ul><li><code>FSView</code> is the root of our view hierarchy</li><li><code>VStack</code> is the only child of <code>FSView</code></li><li><code>HStack</code> and <code>ViewC</code> are siblings and <code>VStack</code>'s children</li><li><code>ViewA</code> and <code>ViewB</code> are siblings and <code>HStack</code>'s children</li></ul><p>If we define our app <a href="https://developer.apple.com/documentation/swiftui/windowgroup"><code>WindowGroup</code></a> with <code>FSView</code>, the view hierarchy above is our complete view hierarchy.</p><pre><code><span class="keyword">@main
struct</span> FSApp: <span class="type">App</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">Scene</span> {
    <span class="type">WindowGroup</span> {
      <span class="type">FSView</span>() <span class="comment">// 👈🏻 SwiftUI view hierarchy starts here.</span>
    }
  }
}
</code></pre><blockquote><p>We can think of SwiftUI's <code>WindowGroup</code> definition as equivalent to UIKit's <code>UIWindow.rootViewController.view</code></p></blockquote><h2>SwiftUI Environment</h2><p>SwiftUI environment is a collection of values and objects propagated through the view hierarchy.</p><p>A lot of what we do in SwiftUI affects the environment. A few examples of environment values/objects:</p><ul><li><a href="https://www.fivestars.blog/articles/button-styles/">every</a> <a href="https://www.fivestars.blog/articles/how-to-customize-textfields/">view</a> <a href="https://www.fivestars.blog/articles/custom-view-styles/">style</a></li><li>accent, tint, foreground colors</li><li><a href="https://www.fivestars.blog/articles/onsubmit/"><code>onSubmit(of:_:)</code>'s <code>TriggerSubmitAction</code></a>, <a href="https://www.fivestars.blog/articles/openurl-openurlaction/"><code>openURL</code>'s action</a></li><li><a href="https://www.fivestars.blog/articles/app-delegate-scene-delegate-swiftui/">the app AppDelegate and SceneDelegate</a></li><li><a href="https://www.fivestars.blog/articles/swiftui-environment-values/">...and much more</a></li></ul><h3>Values vs Objects</h3><p>When we talk about environment values, we refer to all definitions under <a href="https://developer.apple.com/documentation/swiftui/environmentvalues"><code>EnvironmentValues</code></a>:<br>these are mainly primitives such as an enum (e.g., the <a href="https://developer.apple.com/documentation/swiftui/environmentvalues/colorscheme"><code>colorScheme</code></a> value for dark and light mode) and other value-type instances, such as booleans (e.g., <a href="https://developer.apple.com/documentation/swiftui/environmentvalues/isenabled"><code>isEnabled</code></a>) and numbers (e.g., <code>Text</code>'s <a href="https://developer.apple.com/documentation/swiftui/environmentvalues/linelimit"><code>lineLimit</code></a>).</p><p>On the contrary, environment objects are classes conforming to <code>ObservableObject</code>. These are the same <code>ObservableObject</code>s we define and use via <code>@StateObject</code> or <code>@ObservedObject</code>, with the difference that, instead of passing them down via view initializers, we can fetch them via environment.</p><h3>Setting an environment value/object</h3><p>There are mainly two ways to set values/objects into the environment:</p><ol><li>Directly, via the <a href="https://developer.apple.com/documentation/swiftui/view/environment(_:_:)"><code>environment(_:_:)</code></a> and <a href="https://developer.apple.com/documentation/swiftui/view/environmentobject(_:)"><code>environmentObject(_:)</code></a> view modifiers:</li></ol><pre><code><span class="type">FSView</span>()
  .<span class="call">environment</span>(\.<span class="property">colorScheme</span>, .<span class="dotAccess">dark</span>) <span class="comment">// Setting an environment value</span>
  .<span class="call">environmentObject</span>(appState) <span class="comment">// Setting an environment object</span>
</code></pre><blockquote><p>Learn about SwiftUI's App-wide state <a href="https://www.fivestars.blog/articles/app-state/">here</a>.</p></blockquote><ol start="2"><li>Indirectly, via convenience view modifiers:</li></ol><pre><code><span class="type">FSView</span>()
  .<span class="call">foregroundColor</span>(.<span class="dotAccess">yellow</span>)
  .<span class="call">buttonStyle</span>(.<span class="dotAccess">borderedProminent</span>)
</code></pre><blockquote><p>These are also examples of environment values that are not part of <code>EnvironmentValues</code>'s public API (FB8161189), but we can still set them via these modifiers.</p></blockquote><p>The effect is the same, and it's up to the API designer to decide which way to take.</p><h3>Reading an environment value/object</h3><p>Regardless of how we set them, the only way to read both environment values and objects is via SwiftUI's associated property wrappers:</p><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">@EnvironmentObject var</span> state: <span class="type">AppWideState</span> <span class="comment">// Reading an environment object</span>
  <span class="keyword">@Environment</span>(\.<span class="property">isEnabled</span>) <span class="keyword">private var</span> isEnabled: <span class="type">Bool</span> <span class="comment">// Reading an environment value</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    ...
  }
}
</code></pre><blockquote><p>Learn about SwiftUI's App-wide state <a href="https://www.fivestars.blog/articles/app-state/">here</a>.</p></blockquote><p>Note that accessing to an environment object that is not present in the environment will terminate the app.</p><h2>Environment propagation</h2><p>Environment values/objects are propagated through the view hierarchy. When we set something into the environment, we set it for:</p><ul><li>the view where the change is applied to</li><li>the view's hierarchy descendants</li></ul><p>Let's set the <code>foregroundColor</code> to red (🔴) in our <code>FSView</code> example:</p><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">HStack</span> {
        <span class="type">ViewA</span>()
        <span class="type">ViewB</span>()
      }
      <span class="type">ViewC</span>()
    }
    .<span class="call">foregroundColor</span>(.<span class="dotAccess">red</span>) <span class="comment">// 🔴</span>
  }
}
</code></pre><p>As we're setting it for <code>VStack</code>, both <code>VStack</code> and its descendants will inherit this foreground color value:</p><pre><code><span class="type">FSView</span>
└── <span class="type">VStack</span> 🔴
    ├── <span class="type">HStack</span> 🔴
    │   ├── <span class="type">ViewA</span> 🔴
    │   └── <span class="type">ViewB</span> 🔴
    └── <span class="type">ViewC</span> 🔴
</code></pre><p>An environment value/object is propagated until that same value/object is set again, let's set the <code>foregroundColor</code> to green (🟢) on the <code>HStack</code>:</p><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">HStack</span> {
        <span class="type">ViewA</span>()
        <span class="type">ViewB</span>()
      }
      .<span class="call">foregroundColor</span>(.<span class="dotAccess">green</span>) <span class="comment">// 🟢</span>
      <span class="type">ViewC</span>()
    }
    .<span class="call">foregroundColor</span>(.<span class="dotAccess">red</span>) <span class="comment">// 🔴</span>
  }
}
</code></pre><p>Now only <code>VStack</code> and <code>ViewC</code> will see red for their <code>foregroundColor</code>, while others will see green:</p><pre><code><span class="type">FSView</span>
└── <span class="type">VStack</span> 🔴
    ├── <span class="type">HStack</span> 🟢
    │   ├── <span class="type">ViewA</span> 🟢
    │   └── <span class="type">ViewB</span> 🟢
    └── <span class="type">ViewC</span> 🔴
</code></pre><p>Note how siblings can have different environment values:<br><code>HStack</code> and <code>ViewC</code> are siblings but, for the same environment value, the former sees green, while the latter sees red.</p><h3>What about <code>FSView</code>?</h3><p>So far we have changed the environment to <code>FSView</code>'s <code>body</code>. If we want to change the environment seen by <code>FSView</code>, we will then need to do so before its declaration, for example:</p><pre><code><span class="keyword">@main
struct</span> FSApp: <span class="type">App</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">Scene</span> {
    <span class="type">WindowGroup</span> {
      <span class="type">FSView</span>()
        .<span class="call">foregroundColor</span>(.<span class="dotAccess">purple</span>) <span class="comment">// 🟣</span>
    }
  }
}
</code></pre><p>At this point, <code>FSView</code> inherits purple as its <code>foregroundColor</code>. However, its children will see what has been set for them:</p><pre><code><span class="type">FSView</span> 🟣
└── <span class="type">VStack</span> 🔴
    ├── <span class="type">HStack</span> 🟢
    │   ├── <span class="type">ViewA</span> 🟢
    │   └── <span class="type">ViewB</span> 🟢
    └── <span class="type">ViewC</span> 🔴
</code></pre><p>If we didn't set the <code>foregroundColor</code> in <code>FSView</code>'s <code>body</code>, its descendants would also see purple.</p><h2>Environment default values</h2><p>All environment values (<a href="https://developer.apple.com/documentation/swiftui/environmentvalues"><code>EnvironmentValues</code></a>) come with a default value (<code>nil</code> or something more appropriate): if no value is explicitly set, SwiftUI will use that default value.</p><p>Continuing with <code>foregroundColor</code> as an example, if we don't set it ourselves, SwiftUI will use black in light mode, and white in dark mode. These are the two default values defined by the SwiftUI team for that particular environment value.</p><blockquote><p>This is how/why, by default, <code>Text</code> and all shapes will be rendered with black and white in, respectively, light and dark mode.</p></blockquote><p>On the contrary, there's no default value for environment objects. These are <code>ObservableObject</code> classes that we define and set: SwiftUI doesn't initialize nor hold those objects for us. Except for a few cases like <a href="https://www.fivestars.blog/articles/app-delegate-scene-delegate-swiftui/">app and scene delegates</a>, we are responsible to initialize and inject them into the environment when/where appropriate.</p><h2>Conclusions</h2><p>Whether we realize it or not, the environment is undoubtedly one of the most used aspects of any SwiftUI app.</p><p>SwiftUI views and controls are expected <a href="https://www.fivestars.blog/articles/adaptive-swiftui-views/">to adapt</a> to their context and presentation, and the environment plays a significant role in making this possible.</p><p>In the following article in the series, we will continue our exploration with more advanced uses and some edge cases to be aware of. Make sure to <a href="https://www.fivestars.blog/feed.rss">subscribe via feed RSS</a> or <a href="https://twitter.com/FiveStarsBlog">follow <code>@FiveStarsBlog</code> on Twitter</a>.</p><p>Thank you for reading!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-blend-modes</guid><title>SwiftUI blend modes</title><description>An exploration of all 21 SwiftUI blend modes, what they are, what they do, and more. With examples.</description><link>https://www.fivestars.blog/articles/swiftui-blend-modes</link><pubDate>Tue, 21 Sep 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>When building view layouts, we often stack multiple views on top of each other.</p><p>The default behavior in SwiftUI, and most software, is for the top-most views to obscure/hide the bottom ones.</p><p>This makes sense and is reflected in real life: if we look at a stack of papers/documents from above, we can only see the top-most document, or, perhaps, the top-most few if the paper is very, very thin.</p><p>Going back to the virtual world, we can think about each view as a layer of pixels/points.<br>Each pixel has a set of properties such as color, saturation, opacity, etc. Instead of stacking these pixels on top of each other, we can <em>blend</em> them with rules based on their properties, obtaining different effects/outcomes.</p><p>SwiftUI offers twenty-one blending modes. Let's explore all of them!</p><h2>How to apply blend modes</h2><p>The <a href="https://developer.apple.com/documentation/swiftui/view/blendmode(_:)"><code>blendMode(_:)</code> view modifier</a> is dedicated to blending views, there are mainly two ways we can use it:</p><ul><li>via a <code>ZStack</code>:</li></ul><pre><code><span class="type">ZStack</span> {
  <span class="type">BottomView</span>()
  <span class="type">TopView</span>()
    .<span class="call">blendMode</span>(...)
}
</code></pre><ul><li>via an <code>.overlay</code>:</li></ul><pre><code><span class="type">BottomView</span>()
  .<span class="call">overlay</span> {
    <span class="type">TopView</span>()
      .<span class="call">blendMode</span>(...)
  }
</code></pre><p>In both cases, we apply the modifier on the top view.</p><h2>Blending destination and source</h2><p>When it comes to blending, we have two main objects:</p><ul><li>the <strong>source</strong>, which is the view that comes with the blending effect (e.g., <code>TopView().blendMode(...)</code>)</li><li>the <strong>destination</strong>, which is everything underneath that view</li></ul><p>When blending, based on the mode, we consider the properties of both source and destination.</p><p>Let's overview the effects.</p><h2>Blending modes</h2><blockquote><p>📚 Sample code available <a href="https://github.com/FiveStarsBlog/CodeSamples/tree/main/Blending">here</a>.</p></blockquote><p>Different blending techniques use different pixel properties for their effect. We can group them into seven categories:</p><ol><li>Lighten</li><li>Darken</li><li>Contrast</li><li>Component</li><li>Compositing</li><li>Invert</li><li>Normal</li></ol><blockquote><p>The article's images might present some minor artifacts due to compression.</p></blockquote><h3>Lighten</h3><p>The destination will get brighter by blending the hue of both source and destination. Pure black is the neutral color, while white is considered the brightest color.</p><ul><li><a href="https://developer.apple.com/documentation/swiftui/blendmode/colorDodge"><code>colorDodge</code></a> - (opposite of <code>colorBurn</code>) decreases the contrast between the source and the destination. The brighter the source, the more its color affects the destination layer. Results in saturated midtones and bright highlights.</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-blend-modes/colorDodge.png"/><ul><li><a href="https://developer.apple.com/documentation/swiftui/blendmode/lighten"><code>lighten</code></a> - (opposite of <code>darken</code>) compares and draws the brighter colors of either source or destination (if every point of the source view is darker than the destination, the outcome is just the destination)</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-blend-modes/lighten.png"/><ul><li><a href="https://developer.apple.com/documentation/swiftui/blendmode/screen"><code>screen</code></a> - (can be thought to be the opposite of <code>multiply</code>) inverts the destination colors and blends them with the source, then inverts the result. The resulting colors are lighter than the original colors, with less contrast. Screening with black leaves the color unchanged. Screening with white produces white.</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-blend-modes/screen.png"/><ul><li><a href="https://developer.apple.com/documentation/swiftui/blendmode/plusLighter"><code>plusLighter</code></a> - (a.k.a. Linear Dodge, opposite of <code>plusDarker</code>) brightens each destination color channel to reflect the source color. Blending with black produces no change.</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-blend-modes/plusLighter.png"/><h3>Darken</h3><p>The destination will get darker by blending the hue of both source and destination. Pure white is the neutral color.</p><ul><li><a href="https://developer.apple.com/documentation/swiftui/blendmode/colorBurn"><code>colorBurn</code></a> - (opposite of <code>colorDodge</code>) Intensifies the dark areas in all layers. The effect increases the contrast between the source and destination.</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-blend-modes/colorBurn.png"/><ul><li><a href="https://developer.apple.com/documentation/swiftui/blendmode/darken"><code>darken</code></a> - (opposite of <code>lighten</code>) compares and draws the darker colors of either source and destination (if every point of the source view is lighter than the destination, the outcome is just the destination)</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-blend-modes/darken.png"/><ul><li><a href="https://developer.apple.com/documentation/swiftui/blendmode/multiply"><code>multiply</code></a> - (can be thought as opposite of <code>screen</code>) multiplies each channel colors of both source and destination. Multiplying any color with black produces black. Multiplying any color with white leaves the color unchanged.</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-blend-modes/multiply.png"/><ul><li><a href="https://developer.apple.com/documentation/swiftui/blendmode/plusDarker"><code>plusDarker</code></a> - (a.k.a. Linear Burn, opposite of <code>plusLighter</code>) darkens each source color channel to reflect the destination color. Blending with white produces no change.</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-blend-modes/plusDarker.png"/><h3>Contrast</h3><p>The following blend modes create contrast by both lightening the lighter areas and darkening the darker areas in the destination. The outcome is more vibrant colors.</p><ul><li><a href="https://developer.apple.com/documentation/swiftui/blendmode/overlay"><code>overlay</code></a> - (opposite of <code>hardLight</code>, can be thought as combination of <code>multiply</code> and <code>screen</code>) darker colors in the source intensify the destination colors, while lighter colors in the destination wash out the source. Where the source is light, the destination becomes lighter. Where the source is dark, the destination becomes darker.</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-blend-modes/overlay.png"/><ul><li><a href="https://developer.apple.com/documentation/swiftui/blendmode/softLight"><code>softLight</code></a> - softer version of <code>overlay</code>, Applies a half-strength <code>screen</code> to lighter areas, and half-strength <code>multiply</code> to darker areas of the source.</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-blend-modes/softLight.png"/><ul><li><a href="https://developer.apple.com/documentation/swiftui/blendmode/hardLight"><code>hardLight</code></a> - based on the source color brightness, if it's dark, apply <code>screen</code>, apply <code>multiply</code> otherwise.</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-blend-modes/hardLight.png"/><h3>Component</h3><p>These blend modes output a result based on one property/component of the source, and all others from the destination:</p><ul><li><a href="https://developer.apple.com/documentation/swiftui/blendmode/hue"><code>hue</code></a> - Uses hue from the source, luminosity and saturation from the destination.</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-blend-modes/hue.png"/><ul><li><a href="https://developer.apple.com/documentation/swiftui/blendmode/saturation"><code>saturation</code></a> - Uses saturation from the source, luminosity and hue from the destination.</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-blend-modes/saturation.png"/><ul><li><a href="https://developer.apple.com/documentation/swiftui/blendmode/color"><code>color</code></a> - (opposite of <code>luminosity</code>) Uses luminosity from the destination, saturation and hue from the source.</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-blend-modes/color.png"/><ul><li><a href="https://developer.apple.com/documentation/swiftui/blendmode/luminosity"><code>luminosity</code></a> - (opposite of <code>color</code>) Uses luminosity from the source, saturation and hue from the destination.</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-blend-modes/luminosity.png"/><h3>Compositing</h3><p>These blend modes apply different masks to both source and destination. For more options in SwiftUI, check out the three-part <a href="https://www.fivestars.blog/articles/swiftui-clipping/">SwiftUI</a> <a href="https://www.fivestars.blog/articles/swiftui-masking/">masking</a> <a href="https://www.fivestars.blog/articles/reverse-masks-how-to/">series</a>.</p><ul><li><a href="https://developer.apple.com/documentation/swiftui/blendmode/sourceAtop"><code>sourceAtop</code></a> - the source is placed only where it's over the destination.</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-blend-modes/sourceAtop.png"/><ul><li><a href="https://developer.apple.com/documentation/swiftui/blendmode/destinationOver"><code>destinationOver</code></a> - the destination is placed over the source.</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-blend-modes/destinationOver.png"/><ul><li><a href="https://developer.apple.com/documentation/swiftui/blendmode/destinationOut"><code>destinationOut</code></a> - the destination is shown only where it's not underneath the source (we used this to create a <a href="https://www.fivestars.blog/articles/reverse-masks-how-to/">inverse mask view modifier</a>).</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-blend-modes/destinationOut.png"/><h3>Invert</h3><p>Invert blend modes either invert or cancel out source colors depending on destination colors.</p><ul><li><a href="https://developer.apple.com/documentation/swiftui/blendmode/difference"><code>difference</code></a> - subtracts the darker of the two colors (from destination and source) from the lighter color. If either source or destination is all white, this blend inverts the other view colors.</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-blend-modes/difference.png"/><ul><li><a href="https://developer.apple.com/documentation/swiftui/blendmode/exclusion"><code>exclusion</code></a> - similar to <code>difference</code>, but the outcome has lower contrast</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-blend-modes/exclusion.png"/><h3>Normal</h3><ul><li><a href="https://developer.apple.com/documentation/swiftui/blendmode/normal"><code>normal</code></a> - the default behavior, this is needed as blending modes propagate via SwiftUI's environment.</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-blend-modes/normal.png"/><h2>Applying multiple blend modes</h2><p>When we apply multiple blending modes, it's helpful to think that:</p><ul><li>the source is what we are currently drawing</li><li>the destination is what has been drawn so far</li></ul><p>If we apply multiple blend modes to the same view, only the closest one will take effect:</p><pre><code><span class="type">ZStack</span> {
  <span class="type">Destination</span>()
  <span class="type">Source</span>()
    .<span class="call">blendMode</span>(.<span class="dotAccess">color</span>) <span class="comment">// applied</span>
    .<span class="call">blendMode</span>(.<span class="dotAccess">destinationOut</span>) <span class="comment">// discarded</span>
    .<span class="call">blendMode</span>(.<span class="dotAccess">multiply</span>) <span class="comment">// discarded</span>
}
</code></pre><p>This is consistent with other (photo editing) software. The source view is only one part of the equation. Applying blend modes without a destination is a <a href="https://en.wikipedia.org/wiki/NOP_(code)">no-op</a>.</p><p>Things get more interesting when we apply different blend modes to different layers. Let's make an example:</p><pre><code><span class="type">ZStack</span> {
  <span class="type">BottomView</span>()
  <span class="type">MidView</span>()
    .<span class="call">blendMode</span>(...)
  <span class="type">TopView</span>()
    .<span class="call">blendMode</span>(...)
}
</code></pre><p>Here we will:</p><ol><li>blend <code>BottomView</code> (destination) with <code>MidView</code> (source) using <code>MidView</code>'s blend mode</li><li>blend the <code>BootomView</code> + <code>MidView</code> output (destination) with <code>TopView</code> (source) using <code>TopView</code>'s blend mode</li></ol><p>This is just an example, but the blending possibilities will just grow from here.</p><h2>Wait, it's all <code>CIFilter</code>s?</h2><p>If we dig into <a href="https://www.objc.io/blog/2019/10/29/swiftui-environment/">SwiftUI's Environment</a>, we see that our <code>SwiftUI.BlendMode</code> is translated into a <a href="https://developer.apple.com/documentation/quartzcore/calayer/1410748-compositingfilter"><code>CALayer.compositingFilter</code></a>:</p><pre><code>[...]
- <span class="type">SwiftUI</span>.<span class="type">DisplayList</span>.<span class="type">ViewUpdater</span>.<span class="type">ViewCache</span>
▿ value: <span class="type">SwiftUI</span>.<span class="type">DisplayList</span>.<span class="type">ViewUpdater</span>.<span class="type">ViewInfo</span>
  [...]
  - layer: &lt;
    <span class="type">CALayer</span>: ...; 
    [...]
    compositingFilter = colorBurnBlendMode <span class="comment">// 👈🏻</span>
  &gt; 
[...]
</code></pre><p>This shouldn't come as a surprise, but it's a good reminder that SwiftUI stands on the shoulders of giants.</p><p><code>CIFilter</code> goes well beyond just blending and compositing, Apple has <a href="https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html">excellent documentation, with images, here</a>.</p><blockquote><p>All <code>SwiftUI.BlendMode</code> cases have "No overview available." as documentation (FB9620110).</p></blockquote><h2>Conclusions</h2><p>Blending might not be something we reach out to daily when building our apps, but it's a great tool to have at our disposal, and SwiftUI makes it very simple to use.</p><p>We've seen a few examples in this article, but I invite you to download the <a href="https://github.com/FiveStarsBlog/CodeSamples/tree/main/Blending">sample code</a>, experiment with different views, and see the wide variety of results that you can get.</p><p>Do you use blending in SwiftUI? Please feel free to <a href="http://twitter.com/zntfdr">show me your work on Twitter</a>! Thank you for reading.</p><h2>References</h2><ul><li><a href="https://helpx.adobe.com/photoshop/using/blending-modes.html">Blending mode descriptions - Adobe</a></li><li><a href="https://www.w3.org/TR/compositing-1/">Compositing and Blending Level 1 - W3C</a></li><li><a href="https://doc.arcgis.com/en/arcgis-online/create-maps/use-blend-modes-mv.htm">Use blend modes - ArcGIS Online</a></li><li><a href="https://en.wikipedia.org/wiki/Blend_modes">Blend Modes - Wikipedia</a></li><li><a href="https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html">Core Image Filter Reference - Apple</a></li></ul>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/reverse-masks-how-to</guid><title>How to apply a reverse mask in SwiftUI</title><description>An exploration on how we can achieve masking effects beyond what SwiftUI offers.</description><link>https://www.fivestars.blog/articles/reverse-masks-how-to</link><pubDate>Wed, 15 Sep 2021 00:01:00 +0000</pubDate><content:encoded><![CDATA[<blockquote><p>📚 This article comes with a companion app! <a href="https://github.com/FiveStarsBlog/CodeSamples/tree/main/SwiftUI-Reverse-Mask">Download it here</a>.</p></blockquote><p>In the <a href="https://www.fivestars.blog/articles/swiftui-clipping/">last</a> <a href="https://www.fivestars.blog/articles/swiftui-masking/">two</a> articles, we've covered SwiftUI <a href="https://www.fivestars.blog/articles/swiftui-clipping/">clipping</a> and <a href="https://www.fivestars.blog/articles/swiftui-masking/">masking</a>. In this final article of the series, let's explore what we can do beyond what SwiftUI offers out of the box.</p><blockquote><p>In a hurry? Jump to the last chapter before the conclusions to grab the code you're looking for.</p></blockquote><h2>Reverse Mask (a.k.a. Inverse Mask)</h2><p>Let's start with a simple yellow rectangle, we highlight the view bounds in red for reference:</p><img src="https://www.fivestars.blog/assets/posts/reverse-masks-how-to/plain.png"/><pre><code><span class="type">Color</span>.<span class="property">yellow</span>
  .<span class="call">frame</span>(width: <span class="number">200</span>, height: <span class="number">200</span>)
  .<span class="call">border</span>(.<span class="dotAccess">red</span>)
</code></pre><p>A reverse mask applies the opposite effect of regular masks:<br>it uses the same rule based on the mask opacity, but in reverse. Here's a first comparison:</p><img src="https://www.fivestars.blog/assets/posts/reverse-masks-how-to/star.png"/><p>In both cases, we use the same <code>Star()</code> shape as the mask:</p><ul><li>on the left, where we apply a regular mask, the original yellow rectangle is drawn only where the shape is</li><li>on the right, where we apply a reverse mask, the original yellow rectangle is drawn everywhere <strong>but</strong> where the shape is</li></ul><p>Here are a couple of more examples to <em>render</em> the idea:</p><img src="https://www.fivestars.blog/assets/posts/reverse-masks-how-to/text.png"/><p>Here we use a <code>Text</code> as the mask.</p><img src="https://www.fivestars.blog/assets/posts/reverse-masks-how-to/fade.png"/><p>Here we use a <code>LinearGradient</code> as the mask.</p><h2>A reverse mask modifier</h2><p>Unfortunately, SwiftUI doesn't offer a reverse mask modifier. There are certainly reasons why this is the case, a few might be around answering the following questions:</p><ul><li>does a reverse mask allow content bleeding?</li><li>does a reverse mask alignment refer to the view bound or the content bound?</li></ul><p>There's no wrong answer to both questions, and probably legit use cases for all of them.</p><p>While there's no official API, we have various ways to create our own reverse mask API for our needs. Let's explore how.</p><h2>Blending</h2><p>When we stack views on top of each other, the views at the bottom are hidden by those above. We can leverage this through opacity or playing with views of different sizes, but the idea stays: views at the bottom will always have some parts hidden (or semi-hidden) by views above.</p><p>A powerful technique breaking out of this standard behavior is <strong>blending</strong>, which lets us use different view properties (hue, opacity, luminosity, and more) to compose the final stack appearance.</p><p>We will go through all the various blend modes in a future article: for the moment, let's focus on the destination out blending more, <code>BlendMode.destinationOut</code>.</p><p>With blend modes, the <strong>source</strong> is the top view, while the <strong>destination</strong> is the bottom view.</p><p>With destination out, the final view is the bits of the bottom view (the destination) where it doesn't overlap with the top view (the source).</p><p>Here's an example where the destination is a <code>Rectangle</code>, and the source is a <code>Circle</code>, both of the same size:</p><img src="https://www.fivestars.blog/assets/posts/reverse-masks-how-to/rectanglevscircle1.png"/><pre><code><span class="type">ZStack</span> {
  <span class="type">Rectangle</span>() <span class="comment">// destination</span>
  <span class="type">Circle</span>()    <span class="comment">// source</span>
    .<span class="call">blendMode</span>(.<span class="dotAccess">destinationOut</span>)
}
.<span class="call">compositingGroup</span>()
.<span class="call">border</span>(.<span class="dotAccess">red</span>)
</code></pre><p>If we now invert the two views, the <code>ZStack</code> draws nothing, because the <code>Rectangle</code> (source) overlaps the <code>Circle</code> (destination) entirely:</p><img src="https://www.fivestars.blog/assets/posts/reverse-masks-how-to/rectanglevscircle2.png"/><pre><code><span class="type">ZStack</span> {
  <span class="type">Circle</span>()    <span class="comment">// destination</span>
  <span class="type">Rectangle</span>() <span class="comment">// source</span>
    .<span class="call">blendMode</span>(.<span class="dotAccess">destinationOut</span>)
}
.<span class="call">compositingGroup</span>()
.<span class="call">border</span>(.<span class="dotAccess">red</span>)
</code></pre><p>Similar to the <code>mask(alignment:_:)</code> view modifier, <code>blendMode(.destinationOut)</code> uses each view opacity to determine the final output. Here are the same examples as before, where we replace the <code>Rectangle</code> with a fading gradient:</p><img src="https://www.fivestars.blog/assets/posts/reverse-masks-how-to/fadevscircle1.png"/><pre><code><span class="type">ZStack</span> {
  <span class="type">LinearGradient</span>(
    colors: [.<span class="dotAccess">clear</span>, .<span class="dotAccess">black</span>], 
    startPoint: .<span class="dotAccess">leading</span>, endPoint: .<span class="dotAccess">trailing</span>
  )           <span class="comment">// destination</span>
  <span class="type">Circle</span>()    <span class="comment">// source</span>
    .<span class="call">blendMode</span>(.<span class="dotAccess">destinationOut</span>)
}
.<span class="call">compositingGroup</span>()
.<span class="call">border</span>(.<span class="dotAccess">red</span>)
</code></pre><img src="https://www.fivestars.blog/assets/posts/reverse-masks-how-to/fadevscircle2.png"/><pre><code><span class="type">ZStack</span> {
  <span class="type">Circle</span>()    <span class="comment">// destination</span>
  <span class="type">LinearGradient</span>(
    colors: [.<span class="dotAccess">clear</span>, .<span class="dotAccess">black</span>], 
    startPoint: .<span class="dotAccess">leading</span>, endPoint: .<span class="dotAccess">trailing</span>
  )           <span class="comment">// source</span>
  .<span class="call">blendMode</span>(.<span class="dotAccess">destinationOut</span>)
}
.<span class="call">compositingGroup</span>()
.<span class="call">border</span>(.<span class="dotAccess">red</span>)
</code></pre><p>By now, readers of the <a href="https://www.fivestars.blog/articles/swiftui-clipping/">previous</a> <a href="https://www.fivestars.blog/articles/swiftui-masking/">two</a> articles of this SwiftUI masking series might have already guessed where this is going: we can use this destination out blending technique to create our reverse mask.</p><h2>Building a reverse mask modifier</h2><p>First, we want to keep the same API as the <code>mask(alignment:_:)</code> modifier, this is how it will look like:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">@inlinable
  public func</span> reverseMask&lt;Mask: <span class="type">View</span>&gt;(
    alignment: <span class="type">Alignment</span> = .<span class="dotAccess">center</span>, 
    <span class="keyword">@ViewBuilder _</span> mask: () -&gt; <span class="type">Mask</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span>
}
</code></pre><p>Next, <a href="https://www.fivestars.blog/articles/swiftui-masking/">we know</a> that <code>mask(alignment:_:)</code> works by showing the original view only where the mask has opacity, we want to do the opposite now. Let's start by applying a normal mask:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">@inlinable
  public func</span> reverseMask&lt;Mask: <span class="type">View</span>&gt;(
    alignment: <span class="type">Alignment</span> = .<span class="dotAccess">center</span>,
    <span class="keyword">@ViewBuilder _</span> mask: () -&gt; <span class="type">Mask</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">self</span>.<span class="call">mask</span> {
      <span class="type">Rectangle</span>()
    }
  }
}
</code></pre><p>By passing a <code>Rectangle()</code> in the mask modifier, we have obtained the same effect as <code>.clipped()</code>:<br>we no longer allow content bleeding, but the original content is still visible within the view bounds.</p><p>Next, we want to apply the <code>.destinationOut</code> blend mode with our mask as the source, and the <em>clipping</em> rectangle as the destination:</p><pre><code><span class="comment">// ⚠️ this is not the final code.</span>
<span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">@inlinable
  public func</span> reverseMask&lt;Mask: <span class="type">View</span>&gt;(
    alignment: <span class="type">Alignment</span> = .<span class="dotAccess">center</span>,
    <span class="keyword">@ViewBuilder _</span> mask: () -&gt; <span class="type">Mask</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">self</span>.<span class="call">mask</span> {
      <span class="type">ZStack</span> {
        <span class="type">Rectangle</span>() <span class="comment">// destination</span>
        <span class="call">mask</span>()      <span class="comment">// source</span>
          .<span class="call">blendMode</span>(.<span class="dotAccess">destinationOut</span>)
      }
    }
  }
}
</code></pre><p>Thanks to <code>ZStack</code>, we're applying the same effects covered in the Blending chapter above and then using the outcome as the input for a regular mask, obtaining a reverse mask.</p><p>Lastly, we want to respect the <code>alignment</code> parameter, the best way to do this, while also dealing with masks that could have a bigger size than the view they're applied to, is to apply an overlay to our <code>Rectangle</code>:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">@inlinable
  public func</span> reverseMask&lt;Mask: <span class="type">View</span>&gt;(
    alignment: <span class="type">Alignment</span> = .<span class="dotAccess">center</span>,
    <span class="keyword">@ViewBuilder _</span> mask: () -&gt; <span class="type">Mask</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">self</span>.<span class="call">mask</span> {
      <span class="type">Rectangle</span>()
        .<span class="call">overlay</span>(alignment: alignment) {
          <span class="call">mask</span>()
            .<span class="call">blendMode</span>(.<span class="dotAccess">destinationOut</span>)
        }
    }
  }
}
</code></pre><p>This is exactly how we obtained the examples at the beginning of the article, here are them again, with their code.</p><img src="https://www.fivestars.blog/assets/posts/reverse-masks-how-to/star.png"/><pre><code><span class="type">HStack</span> {
  <span class="type">Color</span>.<span class="property">yellow</span>
    .<span class="call">frame</span>(width: <span class="number">200</span>, height: <span class="number">200</span>)
    .<span class="call">mask</span> {
      <span class="type">Star</span>()
    }
    .<span class="call">border</span>(.<span class="dotAccess">red</span>)

  <span class="type">Color</span>.<span class="property">yellow</span>
    .<span class="call">frame</span>(width: <span class="number">200</span>, height: <span class="number">200</span>)
    .<span class="call">reverseMask</span> {
      <span class="type">Star</span>()
    }
    .<span class="call">border</span>(.<span class="dotAccess">red</span>)
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/reverse-masks-how-to/text.png"/><pre><code><span class="type">HStack</span> {
  <span class="type">Color</span>.<span class="property">yellow</span>
    .<span class="call">frame</span>(width: <span class="number">200</span>, height: <span class="number">200</span>)
    .<span class="call">mask</span> {
      <span class="type">Text</span>(<span class="string">"MASK"</span>)
        .<span class="call">font</span>(.<span class="call">system</span>(size: <span class="number">60</span>).<span class="call">weight</span>(.<span class="dotAccess">black</span>))
    }
    .<span class="call">border</span>(.<span class="dotAccess">red</span>)

  <span class="type">Color</span>.<span class="property">yellow</span>
    .<span class="call">frame</span>(width: <span class="number">200</span>, height: <span class="number">200</span>)
    .<span class="call">reverseMask</span> {
      <span class="type">Text</span>(<span class="string">"MASK"</span>)
        .<span class="call">font</span>(.<span class="call">system</span>(size: <span class="number">60</span>).<span class="call">weight</span>(.<span class="dotAccess">black</span>))
    }
    .<span class="call">border</span>(.<span class="dotAccess">red</span>)
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/reverse-masks-how-to/fade.png"/><pre><code><span class="type">HStack</span> {
  <span class="type">Color</span>.<span class="property">yellow</span>
    .<span class="call">frame</span>(width: <span class="number">200</span>, height: <span class="number">200</span>)
    .<span class="call">mask</span> {
      <span class="type">LinearGradient</span>(
        colors: [.<span class="dotAccess">clear</span>, .<span class="dotAccess">black</span>],
        startPoint: .<span class="dotAccess">leading</span>, endPoint: .<span class="dotAccess">trailing</span>
      )
    }
    .<span class="call">border</span>(.<span class="dotAccess">red</span>)
  
  <span class="type">Color</span>.<span class="property">yellow</span>
    .<span class="call">frame</span>(width: <span class="number">200</span>, height: <span class="number">200</span>)
    .<span class="call">reverseMask</span> {
      <span class="type">LinearGradient</span>(
        colors: [.<span class="dotAccess">clear</span>, .<span class="dotAccess">black</span>],
        startPoint: .<span class="dotAccess">leading</span>, endPoint: .<span class="dotAccess">trailing</span>
      )
    }
    .<span class="call">border</span>(.<span class="dotAccess">red</span>)
}
</code></pre><p>The complete source code can be found <a href="https://github.com/FiveStarsBlog/CodeSamples/tree/main/SwiftUI-Reverse-Mask">here</a>.</p><h2>Conclusions</h2><p>SwiftUI <a href="https://www.fivestars.blog/articles/section-title-index-swiftui/">might</a> <a href="articles/swiftui-share-layout-information/">not</a> <a href="https://www.fivestars.blog/articles/flexible-swiftui/">always</a> <a href="https://www.fivestars.blog/articles/scrollview-offset/">offer</a> <a href="https://www.fivestars.blog/articles/conditional-modifiers/">the</a> <a href="https://www.fivestars.blog/articles/adaptive-swiftui-views/">API</a> <a href="https://www.fivestars.blog/articles/programmatic-navigation/">that</a> <a href="https://www.fivestars.blog/articles/trucated-text/">we</a> <a href="https://www.fivestars.blog/articles/swiftui-introspect/">need</a> out of the box. However, most of the time, the tooling at our disposal can fill in the gap, and allow us to build the perfect API for any occasion.</p><p>This article is the final part of the SwiftUI masking series. Thank you for reading, and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more SwiftUI content!</p><p>Have you ever used reverse masking before? Please let me know!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/xcode-13-rc</guid><title>What's new in Xcode 13 Release Candidate</title><description>A quick look at the only SwiftUI change and a look back at all previous betas</description><link>https://www.fivestars.blog/articles/xcode-13-rc</link><pubDate>Wed, 15 Sep 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Xcode 13 RC has just been released: this build is almost identical with the <a href="https://www.fivestars.blog/articles/xcode-13-beta-5/">previous beta</a>.</p><h2>New accessibility ForEach initializer</h2><pre><code><span class="keyword">@available</span>(iOS <span class="number">15.0</span>, macOS <span class="number">12.0</span>, tvOS <span class="number">15.0</span>, watchOS <span class="number">8.0</span>, *)
<span class="keyword">extension</span> <span class="type">ForEach</span> <span class="keyword">where</span> <span class="type">ID</span> == <span class="type">Data</span>.<span class="type">Element</span>.<span class="type">ID</span>, <span class="type">Content</span> : <span class="type">AccessibilityRotorContent</span>, <span class="type">Data</span>.<span class="type">Element</span> : <span class="type">Identifiable</span> {
  <span class="comment">/// Creates an instance that generates Rotor content by combining, in order,
  /// individual Rotor content for each element in the data given to this
  /// `ForEach`.
  ///
  /// It's important that the `id` of a data element doesn't change unless you
  /// replace the data element with a new data element that has a new
  /// identity.
  ///
  /// - Parameters:
  ///   - data: The identified data that the ``ForEach`` instance uses to
  ///     create views dynamically.
  ///   - content: The result builder that generates Rotor content for each
  ///     data element.</span>
  <span class="keyword">public init</span>(<span class="keyword">_</span> data: <span class="type">Data</span>, <span class="keyword">@AccessibilityRotorContentBuilder</span> content: <span class="keyword">@escaping</span> (<span class="type">Data</span>.<span class="type">Element</span>) -&gt; <span class="type">Content</span>)
}
</code></pre><p>The only difference in the SwiftUI SDK is the exposure of a <a href="https://developer.apple.com/documentation/swiftui/foreach/init(_:content:)-9nhnm?changes=latest_beta"><code>ForEach</code> accessibility initializer</a>, if you haven't worked with accessibility rotors before, <a href="https://twitter.com/mecid">Majid Jabrayilov</a> has a <a href="https://swiftwithmajid.com/2021/09/14/accessibility-rotors-in-swiftui/">great overview</a>.</p><p>This method was present from Xcode 13 beta 2, it was then moved to <code>internal</code> in beta 5, and it has been reverted back to <code>public</code> in this release candidate.</p><h2>A look back to all Xcode 13 betas</h2><p>This is a good time to overview all the changes in previous betas:</p><ul><li><a href="https://www.fivestars.blog/articles/xcode-13-beta-2/">What's new in Xcode 13 beta 2</a>, where we gained a <code>.mini</code> control size, text selection, and more consistent <code>Searchable</code> APIs</li><li><a href="https://www.fivestars.blog/articles/xcode-13-beta-3/">What's new in Xcode 13 beta 3</a>, where we've seen a lot of <a href="https://www.fivestars.blog/articles/embracing-viewbuilder/">view builders API backport(s)</a>, and great styles improvements</li><li><a href="https://www.fivestars.blog/articles/xcode-13-beta-4/">What's new in Xcode 13 beta 4</a>, where we've gained strikethrough and underline <code>AttributedString</code> styles, a new <code>buttonBorderShape(_:)</code> view modifier, and more</li><li><a href="https://www.fivestars.blog/articles/xcode-13-beta-5/">What's new in Xcode 13 beta 5</a>, where we've gained custom links handling, interactive shapes, and more</li></ul><h2>Conclusions</h2><p>Xcode 13rc comes with the "old" macOS SDK (as macOS 12 will be released later this year): it's likely that we will get an Xcode 13.1 beta quite soon, that brings back the new macOS 12 SDKs and, perhaps, a sneak peek to upcoming changes for other SDKs, too.</p><p>Thank you for reading!</p><blockquote><p>This article is part of a series exploring new SwiftUI features. We will cover many more during the rest of the summer: subscribe to Five Stars's <a href="https://www.fivestars.blog/feed.rss">feed RSS</a> or follow <a href="http://twitter.com/fiveStarsBlog">@FiveStarsBlog</a> on Twitter to never miss new content!</p></blockquote>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-masking</guid><title>View masking in SwiftUI</title><description>What SwiftUI offers beyond clip masks.</description><link>https://www.fivestars.blog/articles/swiftui-masking</link><pubDate>Tue, 7 Sep 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<blockquote><p>📚 This article comes with a companion app! <a href="https://github.com/FiveStarsBlog/CodeSamples/tree/main/SwiftUI-Masking">Download it here</a>.</p></blockquote><p>In <a href="https://www.fivestars.blog/articles/swiftui-clipping/">View clipping in SwiftUI</a>, we've explored all the ways we can apply clipping masks to views (if you haven't already, <a href="https://www.fivestars.blog/articles/swiftui-clipping/">read that article first</a>). While powerful, clipping has two significant limitations:</p><ul><li>it requires shapes as masks</li><li>the content is either masked or not; there's no gray area</li></ul><p>Let's explore SwiftUI masking beyond clipping.</p><h2>Mask</h2><p>The last masking view modifier that SwiftUI offers is <code>mask(alignment:_:)</code>:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">@inlinable public func</span> mask&lt;Mask: <span class="type">View</span>&gt;(
    alignment: <span class="type">Alignment</span> = .<span class="dotAccess">center</span>, 
    <span class="keyword">@ViewBuilder _</span> mask: () -&gt; <span class="type">Mask</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span>
}
</code></pre><blockquote><p>This is a new view modifier using <a href="https://www.fivestars.blog/articles/swiftui-patterns-view-builders/">the new view builder pattern</a>. For older OSes, the <code>mask</code> declaration is slightly different, but the same concepts are still applied.</p></blockquote><p>Besides the naming, this modifier declaration is identical to a couple of other view modifiers we're probably very familiar with, <code>overlay(alignment:_:)</code> and <code>background(alignment:_:)</code>:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">@inlinable public func</span> overlay&lt;V: <span class="type">View</span>&gt;(
    alignment: <span class="type">Alignment</span> = .<span class="dotAccess">center</span>, 
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">V</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span>

  <span class="keyword">@inlinable public func</span> background&lt;V: <span class="type">View</span>&gt;(
    alignment: <span class="type">Alignment</span> = .<span class="dotAccess">center</span>, 
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">V</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span>
}
</code></pre><p>This is not a coincidence. <code>mask(alignment:_:)</code> positions its mask exactly like an overlay or a background modifier would:</p><ul><li>the modifier proposes to its mask the natural size of the view it's applied to</li><li>once the mask size is known, it is placed over the view, according to the specified <code>alignment</code></li></ul><h3>Mask alignment</h3><p>The alignment parameter is particularly useful when the mask and the original view have different sizes. In the following example, the mask is 30% the size of the view it's applied to:</p><video autoplay muted loop>
  <source src="https://www.fivestars.blog/assets/posts/swiftui-masking/alignment.mp4">
</video><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">private let</span> alignments: [<span class="type">Alignment</span>] = [
    .<span class="dotAccess">center</span>, .<span class="dotAccess">leading</span>, .<span class="dotAccess">trailing</span>, .<span class="dotAccess">top</span>, .<span class="dotAccess">bottom</span>, .<span class="dotAccess">topLeading</span>, .<span class="dotAccess">topTrailing</span>, .<span class="dotAccess">bottomLeading</span>, .<span class="dotAccess">bottomTrailing</span>
  ]
  <span class="keyword">@State var</span> alignment: <span class="type">Alignment</span> = .<span class="dotAccess">center</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">Color</span>.<span class="property">yellow</span>
        .<span class="call">frame</span>(width: <span class="number">200</span>, height: <span class="number">200</span>)
        .<span class="call">mask</span>(alignment: alignment) {
          <span class="type">Rectangle</span>()
            .<span class="call">frame</span>(width: <span class="number">60</span>, height: <span class="number">60</span>) <span class="comment">// 👈🏻 60 x 60 is smaller than 200x200</span>
        }
        .<span class="call">border</span>(.<span class="dotAccess">red</span>)

      <span class="type">Button</span>(<span class="string">"Random alignment"</span>) {
        <span class="call">withAnimation</span> {
          alignment = alignments.<span class="call">filter</span> { $0 != alignment } .<span class="call">randomElement</span>()!
        }
      }
    }
  }
}
</code></pre><blockquote><p><code>Alignment</code> should be <code>CaseIterable</code> (FB9581717)</p></blockquote><p>The red border shows the bound of the original view for a visual aid: otherwise, we'd see just a small rectangle floating around.</p><h3>Views as masks</h3><p>The real power over clipping modifiers stands with the opportunity to use <em>any</em> <code>View</code>s as masks. What about <code>Text</code>, for example?</p><img src="https://www.fivestars.blog/assets/posts/swiftui-masking/text.png"/><pre><code><span class="type">Color</span>.<span class="property">yellow</span>
  .<span class="call">frame</span>(width: <span class="number">200</span>, height: <span class="number">200</span>)
  .<span class="call">mask</span> {
    <span class="type">Text</span>(<span class="string">"MASK"</span>)
      .<span class="call">fontWeight</span>(.<span class="dotAccess">black</span>)
      .<span class="call">font</span>(.<span class="call">system</span>(size: <span class="number">60</span>))
  }
  .<span class="call">border</span>(<span class="type">Color</span>.<span class="property">red</span>)
</code></pre><p>Unlike shapes, there's no expectation for views to stay within the natural size of the view they're applied to. Therefore masks can allow content bleeding.</p><p>In the following example:</p><ul><li>the view <em>content</em> extends to a 300x300 points rectangle</li><li>the view bound is 200x200 points</li><li>the applied mask exceeds the view bounds, allowing content bleeding</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-masking/bleed.png"/><pre><code><span class="type">Color</span>.<span class="property">yellow</span>
  .<span class="call">frame</span>(width: <span class="number">300</span>, height: <span class="number">300</span>)
  .<span class="call">frame</span>(width: <span class="number">200</span>, height: <span class="number">200</span>)
  .<span class="call">mask</span> {
    <span class="type">Text</span>(<span class="string">"MASK"</span>)
      .<span class="call">fontWeight</span>(.<span class="dotAccess">black</span>)
      .<span class="call">font</span>(.<span class="call">system</span>(size: <span class="number">80</span>))
      .<span class="call">fixedSize</span>() <span class="comment">// 👈🏻 Ignores the proposed 200x200 points size</span>
  }
  .<span class="call">border</span>(<span class="type">Color</span>.<span class="property">red</span>)
</code></pre><h3>Opacity</h3><p><code>mask(alignment:_:)</code> uses the mask opacity to determine what is shown from the original view, for example:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-masking/gradient.png"/><pre><code><span class="type">Color</span>.<span class="property">yellow</span>
  .<span class="call">frame</span>(width: <span class="number">200</span>, height: <span class="number">200</span>)
  .<span class="call">mask</span> {
    <span class="type">LinearGradient</span>(colors: [.<span class="dotAccess">clear</span>, .<span class="dotAccess">black</span>, .<span class="dotAccess">clear</span>], startPoint: .<span class="dotAccess">leading</span>, endPoint: .<span class="dotAccess">trailing</span>)
  }
  .<span class="call">border</span>(<span class="type">Color</span>.<span class="property">red</span>)
</code></pre><p>Here we use a linear gradient with three stops. The gradient color in the middle doesn't matter. It's the color opacity that does:<br>we could replace it with <code>.white</code>, <code>.red</code> etc. The result would be the same.</p><p>While we don't have to use gradients with masks, this fading technique is very popular in iOS.<br>In the following example we take the sample code from <a href="https://www.fivestars.blog/articles/safe-area-insets/">How to control safe area insets in SwiftUI</a> and add a subtle fading effect below the button:</p><table> 
  <thead>
    <tr>
      <th>
        <video autoplay muted loop>
          <source src="https://www.fivestars.blog/assets/posts/swiftui-masking/no-mask.mp4">
        </video>
      </th>
      <th>
        <video autoplay muted loop>
          <source src="https://www.fivestars.blog/assets/posts/swiftui-masking/mask.mp4">
        </video>
      </th>
    </tr>
  </thead>
  <tbody>
    <tr style="text-align: center;">
      <td>No effect/mask</td>
      <td>Fade effect/mask</td>
    </tr>
  </tbody>
</table><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollView</span> {
      ...
    }
    .<span class="call">safeAreaInset</span>(edge: .<span class="dotAccess">bottom</span>) {
      <span class="type">Button</span> { } label: { ... }
        .<span class="call">background</span> {
          <span class="type">Color</span>(uiColor: .<span class="dotAccess">systemBackground</span>)
            .<span class="call">mask</span>(alignment: .<span class="dotAccess">top</span>) {
              <span class="type">VStack</span>(spacing: <span class="number">0</span>) {
                <span class="type">LinearGradient</span>(
                  stops: [
                    <span class="type">Gradient</span>.<span class="type">Stop</span>(color: .<span class="dotAccess">clear</span>, location: .<span class="dotAccess">zero</span>),
                    <span class="type">Gradient</span>.<span class="type">Stop</span>(color: .<span class="dotAccess">black</span>, location: <span class="number">1.0</span>)
                  ],
                  startPoint: .<span class="dotAccess">top</span>,
                  endPoint: .<span class="dotAccess">bottom</span>
                )
                .<span class="call">frame</span>(height: <span class="number">32</span>)
                <span class="type">Color</span>.<span class="property">black</span>
              }
            }
            .<span class="call">padding</span>(.<span class="dotAccess">top</span>, -<span class="number">32</span>)
            .<span class="call">ignoresSafeArea</span>(.<span class="dotAccess">all</span>, edges: .<span class="dotAccess">bottom</span>)
        }
    }
  }
}
</code></pre><h4>Blur</h4><p>Another visually pleasing technique is mixing the fade effect with a blur. To obtain this mix, we need to change just two lines from the example above:</p><table> 
  <thead>
    <tr>
      <th>
        <video autoplay muted loop>
          <source src="https://www.fivestars.blog/assets/posts/swiftui-masking/mask.mp4">
        </video>
      </th>
      <th>
        <video autoplay muted loop>
          <source src="https://www.fivestars.blog/assets/posts/swiftui-masking/blur.mp4">
        </video>
      </th>
    </tr>
  </thead>
  <tbody>
    <tr style="text-align: center;">
      <td>Fade effect</td>
      <td>Fade + Blur effect</td>
    </tr>
  </tbody>
</table><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    ...
      .<span class="call">background</span> {
        <span class="type">Color</span>.<span class="property">clear</span> <span class="comment">// 👈🏻</span>
          .<span class="call">background</span>(.<span class="dotAccess">ultraThinMaterial</span>) <span class="comment">// 👈🏻</span>
          .<span class="call">mask</span>(alignment: .<span class="dotAccess">top</span>) {
            ...
          }
          ...
      }
    }
  }
}
</code></pre><p>The complete source code can be found <a href="https://github.com/FiveStarsBlog/CodeSamples/tree/main/SwiftUI-Masking">here</a>.</p><h2>Conclusions</h2><p>Masking is one of those subtle effects that can help bring our app design to the next level.</p><p>So far, we've covered what SwiftUI offers: in the next and final article of the series, we will explore what we can do beyond the official APIs.</p><p>If you haven't already, this is a very good time to subscribe to <a href="https://www.fivestars.blog/feed.rss">Five Star's RSS feed</a>. Thank you for reading!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-clipping</guid><title>View clipping in SwiftUI</title><description>A quick tour on all the possible ways we can clip views in SwiftUI!</description><link>https://www.fivestars.blog/articles/swiftui-clipping</link><pubDate>Tue, 31 Aug 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<blockquote><p>📚 This article comes with a companion app! <a href="https://github.com/FiveStarsBlog/CodeSamples/tree/main/SwiftUI-Clipping">Download it here</a>.</p></blockquote><p>Masking is a powerful technique that we can use to push our app's design to the next level.</p><p>SwiftUI offers various ways to do just that: let's take a tour where it all begins by exploring clipping in SwiftUI.</p><h2>Clipping Masks</h2><p>Every view comes with a bound frame. This frame is used for composing the overall view hierarchy layout. When it comes to the drawing phase, the view content can exceed its bound frame (also known as <em>bleeding</em>).</p><p>For example, consider the following view:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-clipping/base.png"/><pre><code><span class="type">Text</span>(<span class="string">"Five stars"</span>)
  .<span class="call">background</span>(<span class="type">Color</span>.<span class="property">yellow</span>)
  .<span class="call">font</span>(.<span class="call">system</span>(size: <span class="number">90</span>))
  .<span class="call">border</span>(<span class="type">Color</span>.<span class="property">red</span>)
</code></pre><p>The red border shows the content frame, which, in this case, also coincides with the bound frame.<br>Let's take a look at another example:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-clipping/base-borders.png"/><pre><code><span class="type">Text</span>(<span class="string">"Five stars"</span>)
  .<span class="call">background</span>(<span class="type">Color</span>.<span class="property">yellow</span>)
  .<span class="call">font</span>(.<span class="call">system</span>(size: <span class="number">90</span>))
  .<span class="call">fixedSize</span>()
  .<span class="call">border</span>(<span class="type">Color</span>.<span class="property">blue</span>)
  .<span class="call">frame</span>(width: <span class="number">200</span>, height: <span class="number">100</span>)
  .<span class="call">border</span>(<span class="type">Color</span>.<span class="property">red</span>)
</code></pre><p>Here <code>Text</code> takes as much space as it needs, thanks to the <code>fixedSize()</code> view modifier. However, we also apply another <code>.frame(width: 200, height: 50)</code> view modifier on top of that.</p><p>For the rest of the view hierarchy, this view bound is represented by the red border, while the blue border represents the space the view content takes.</p><p>On the layout phase:</p><ul><li>only the bound frame/red border will be considered</li><li>the content frame/blue border is completely disregarded</li></ul><p>As SwiftUI allows <em>content bleeding</em> by default, the content is drawn even when it extends beyond the view frame edges. To avoid this, we can use clipping:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-clipping/clipped.png"/><pre><code><span class="type">Text</span>(<span class="string">"Five stars"</span>)
  .<span class="call">background</span>(<span class="type">Color</span>.<span class="property">yellow</span>)
  .<span class="call">font</span>(.<span class="call">system</span>(size: <span class="number">90</span>))
  .<span class="call">fixedSize</span>()
  .<span class="call">border</span>(<span class="type">Color</span>.<span class="property">blue</span>)
  .<span class="call">frame</span>(width: <span class="number">200</span>, height: <span class="number">100</span>)
  .<span class="call">border</span>(<span class="type">Color</span>.<span class="property">red</span>)
  .<span class="call">clipped</span>() <span class="comment">// 👈🏻</span>
</code></pre><p>The <code>clipped()</code> view modifier limits the drawing of a view to its bound frame, everything else will be hidden.</p><p>In other words, <code>clipped()</code> applies a mask equivalent to the bound frame "rectangle", thus hiding anything that goes beyond that rectangle.</p><p>SwiftUI comes with two <code>clipped()</code> alternatives: <code>cornerRadius(_:)</code> and <code>clipShape(_:style)</code>.</p><h2>Corner Radius</h2><p><code>cornerRadius(_:)</code> behaves exactly like <code>clipped()</code>, but instead of matching 1:1 the bound frame, it also lets us specify a corner radius to be applied in the final mask:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-clipping/corner-radius.png"/><pre><code><span class="type">Text</span>(<span class="string">"Five stars"</span>)
  .<span class="call">background</span>(<span class="type">Color</span>.<span class="property">yellow</span>)
  .<span class="call">font</span>(.<span class="call">system</span>(size: <span class="number">90</span>))
  .<span class="call">fixedSize</span>()
  .<span class="call">frame</span>(width: <span class="number">200</span>, height: <span class="number">100</span>)
  .<span class="call">cornerRadius</span>(<span class="number">50</span>) <span class="comment">// 👈🏻</span>
</code></pre><p>Using the same thought process from before, <code>cornerRadius(_:)</code> applies a mask equivalent to the view bound frame rectangle, this time with rounded corners.</p><p><code>.clipped()</code> can be seen as a more performant convenience over <code>.cornerRadius(0)</code>.</p><h2>Clip Shape</h2><p>So far we've been playing with just rectangles, <code>clipShape(_:style:)</code> removes this limitation and lets us use any shape as the clipping mask:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-clipping/circle.png"/><pre><code><span class="type">Text</span>(<span class="string">"Five stars"</span>)
  ...
  .<span class="call">clipShape</span>(<span class="type">Circle</span>())
</code></pre><p>Shapes fit as best as they can within the natural size of the view that contains them, a.k.a. the view bound frame.</p><p>We're not limited to the shapes SwiftUI offers. We can also declare our own:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-clipping/star.png"/><pre><code><span class="type">Text</span>(<span class="string">"Five stars"</span>)
  ...
  .<span class="call">clipShape</span>(<span class="type">Star</span>())
</code></pre><p>Similarly to how <code>.clipped()</code> can be seen as a convenience over <code>.cornerRadius(0)</code>, <code>.cornerRadius(x)</code> can be seen as a convenience over <code>.clipShape(RoundedRectangle(cornerRadius: x))</code>.</p><h3>Even-odd rule</h3><p>When defining a <code>Shape</code>, it's acceptable for some of its parts to be drawn over multiple times. We can consider those sections to be "overlapping areas". For example, take this <code>DoubleEllipse</code> <code>Shape</code> definition, which consists of two ellipses overlapping by an arbitrary amount:</p><video autoplay muted loop>
  <source src="https://www.fivestars.blog/assets/posts/swiftui-clipping/double-eclipses.mp4">
</video><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">@State var</span> overlapping: <span class="type">Double</span> = <span class="number">0.1</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">DoubleEllipse</span>(overlapping: overlapping)
        .<span class="call">frame</span>(width: <span class="number">300</span>, height: <span class="number">100</span>)
      <span class="type">HStack</span> {
        <span class="type">Text</span>(<span class="string">"Overlapping"</span>)
        <span class="type">Slider</span>(value: $overlapping, in: <span class="number">0.0</span>...<span class="number">1.0</span>)
      }
    }
  }
}

<span class="keyword">struct</span> DoubleEllipse: <span class="type">Shape</span> {
  <span class="comment">/// 1 = complete overlap
  /// 0 = no overlap</span>
  <span class="keyword">@Clamping</span>(<span class="number">0.0</span>...<span class="number">1.0</span>) <span class="keyword">var</span> overlapping: <span class="type">Double</span> = <span class="number">0</span>

  <span class="keyword">func</span> path(in rect: <span class="type">CGRect</span>) -&gt; <span class="type">Path</span> {
    <span class="keyword">let</span> rectSize = <span class="type">CGSize</span>(width: (rect.<span class="property">width</span> / <span class="number">2</span>) * (<span class="number">1</span> + overlapping), height: rect.<span class="property">height</span>)

    <span class="keyword">var</span> path = <span class="type">Path</span>()
    path.<span class="call">addEllipse</span>(in: <span class="type">CGRect</span>(origin: .<span class="dotAccess">zero</span>, size: rectSize))
    <span class="keyword">let</span> secondEllipseOrigin = <span class="type">CGPoint</span>(x: (rect.<span class="property">width</span> / <span class="number">2</span>) * (<span class="number">1</span> - overlapping), y: rect.<span class="property">origin</span>.<span class="property">y</span>)
    path.<span class="call">addEllipse</span>(in: <span class="type">CGRect</span>(origin: secondEllipseOrigin, size: rectSize))

    <span class="keyword">return</span> path
  }
}
</code></pre><p>By default, SwiftUI draws everything as defined. However, we can also apply a <code>fill(style:)</code> <code>Shape</code> modifier that threats those overlapping areas differently:</p><video autoplay muted loop>
  <source src="https://www.fivestars.blog/assets/posts/swiftui-clipping/double-eclipses-eo.mp4">
</video><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">@State var</span> overlapping: <span class="type">Double</span> = <span class="number">0.1</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">DoubleEllipse</span>(overlapping: overlapping)
        .<span class="call">fill</span>(style: <span class="type">FillStyle</span>(eoFill: <span class="keyword">true</span>, antialiased: <span class="keyword">true</span>)) <span class="comment">// 👈🏻</span>
        .<span class="call">frame</span>(width: <span class="number">300</span>, height: <span class="number">100</span>)
      <span class="type">HStack</span> {
        <span class="type">Text</span>(<span class="string">"Overlapping"</span>)
        <span class="type">Slider</span>(value: $overlapping, in: <span class="number">0.0</span>...<span class="number">1.0</span>)
      }
    }
  }
}
</code></pre><p>The magic is all in the <code>oeFill</code> parameter, where <code>eo</code> stands for <a href="https://en.wikipedia.org/wiki/Even–odd_rule"><strong>E</strong>ven-<strong>o</strong>dd</a> (rule), described as:<br>«A point "insideness" in a shape is determined by drawing a ray from that point to infinity in any direction and counting the number of path segments from the given shape that the ray crosses. If this number is odd, the point is inside; if even, the point is outside.»</p><p>The definition goes beyond just overlapping, but this is most likely how it will be used when masking in SwiftUI.</p><p>The <code>fill(style:)</code> <code>Shape</code> modifier returns <code>some View</code>, meaning that we cannot use it with <code>clipShape(_:style:)</code>, as the latter requires a <code>Shape</code> instance. With that being said, <code>.clipShape(_:style:)</code> second parameter addresses this, letting us pass a <code>FillStyle</code>:</p><video autoplay muted loop>
  <source src="https://www.fivestars.blog/assets/posts/swiftui-clipping/clipshape-even-odd.mp4">
</video><pre><code><span class="type">VStack</span> {
  <span class="type">Text</span>(<span class="string">"Five stars"</span>)
    .<span class="call">background</span>(<span class="type">Color</span>.<span class="property">yellow</span>)
    .<span class="call">font</span>(.<span class="call">system</span>(size: <span class="number">90</span>))
    .<span class="call">clipShape</span>(
      <span class="type">OverlappingEllipses</span>(ellipsesNumber: ellipsesNumber, overlapping: overlapping),
      style: <span class="type">FillStyle</span>(eoFill: <span class="keyword">true</span>, antialiased: <span class="keyword">false</span>) <span class="comment">// 👈🏻</span>
    )
  <span class="type">Stepper</span>(<span class="string">"Ellipses number:"</span>, value: $ellipsesNumber, in: <span class="number">2</span>...<span class="number">16</span>)
  <span class="type">HStack</span> {
    <span class="type">Text</span>(<span class="string">"Overlapping"</span>)
    <span class="type">Slider</span>(value: $overlapping, in: <span class="number">0.0</span>...<span class="number">1.0</span>)
  }
}
</code></pre><h3>Animating clipping masks</h3><p><code>Shapes</code> conform to both <code>View</code> and <code>Animatable</code>, we can declare a <code>var animatableData: CGFloat</code> in our shapes in order to take advantage of this:</p><pre><code><span class="keyword">struct</span> OverlappingEllipses: <span class="type">Shape</span> {
  <span class="keyword">@Clamping</span>(<span class="number">1</span>...<span class="type">Int</span>.<span class="property">max</span>) <span class="keyword">var</span> ellipsesNumber: <span class="type">Int</span> = <span class="number">2</span>
  <span class="keyword">@Clamping</span>(<span class="number">0.0</span>...<span class="number">1.0</span>) <span class="keyword">var</span> overlapping: <span class="type">Double</span> = <span class="number">0</span>

  <span class="keyword">var</span> animatableData: <span class="type">CGFloat</span> { <span class="comment">// 👈🏻</span>
    <span class="keyword">get</span> { overlapping }
    <span class="keyword">set</span> { overlapping = newValue }
  }

  <span class="keyword">func</span> path(in rect: <span class="type">CGRect</span>) -&gt; <span class="type">Path</span> {
    ...
  }
}
</code></pre><p>With this, we can put everything we've covered so far and easily obtain some <em>trippy</em> effects:</p><video autoplay muted loop>
  <source src="https://www.fivestars.blog/assets/posts/swiftui-clipping/clipshape-animation.mp4">
</video><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">@State var</span> overlapping: <span class="type">Double</span> = <span class="number">0</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span>(spacing: <span class="number">16</span>) {
        <span class="type">Text</span>(<span class="string">"Five stars"</span>)
          ...
          .<span class="call">clipShape</span>(
            <span class="type">OverlappingEllipses</span>(ellipsesNumber: <span class="number">8</span>, overlapping: overlapping),
            style: <span class="type">FillStyle</span>(eoFill: <span class="keyword">true</span>, antialiased: <span class="keyword">false</span>)
          )

      <span class="type">Text</span>(<span class="string">"Five stars"</span>)
        ...
        .<span class="call">clipShape</span>(
          <span class="type">OverlappingRectangles</span>(rectanglesNumber: <span class="number">8</span>, overlapping: overlapping),
          style: <span class="type">FillStyle</span>(eoFill: <span class="keyword">true</span>, antialiased: <span class="keyword">false</span>)
        )

      <span class="type">Button</span>(<span class="string">"Show/Hide"</span>) {
        <span class="call">withAnimation</span>(.<span class="call">easeInOut</span>(duration: <span class="number">2</span>)) {
          overlapping = overlapping == <span class="number">1</span> ? <span class="number">0</span> : <span class="number">1</span>
        }
      }
    }
  }
}

</code></pre><p>The complete source code can be found <a href="https://github.com/FiveStarsBlog/CodeSamples/tree/main/SwiftUI-Clipping">here</a>.</p><h2>Conclusions</h2><p>View masking starts simple and gets powerful quickly: in this article, we've seen an overview of view clipping, but this is just the simplest masking method SwiftUI offers. <a href="https://www.fivestars.blog/feed.rss">Stay tuned</a> for the second part!</p><p>Do you use masking in your apps? What effects have you built? <a href="https://twitter.com/zntfdr">Please let me know</a>!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-environment-values</guid><title>Every SwiftUI Environment Value explained</title><description>A tour on the 50+ public environment values.</description><link>https://www.fivestars.blog/articles/swiftui-environment-values</link><pubDate>Tue, 24 Aug 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>SwiftUI's Environment is one of the most important pillars of Apple's declarative UI framework. There are many aspects of this infrastructure: in this article, let's review all environment values that SwiftUI offers.</p><h2>Generic component values</h2><ul><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/controlSize"><code>controlSize</code></a> (iOS, macOS)</li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/controlActiveState"><code>controlActiveState</code></a> (macOS)</li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/isEnabled"><code>isEnabled</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/isFocused"><code>isFocused</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/redactionReasons"><code>redactionReasons</code></a></li></ul><p>First we have a few generic values that are applicable to most views. Ideally, we observe them in <a href="https://www.fivestars.blog/articles/custom-view-styles/">our view styles</a>, and act accordingly.</p><pre><code><span class="keyword">struct</span> FSButtonStyle: <span class="type">ButtonStyle</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">isFocused</span>) <span class="keyword">private var</span> isFocused: <span class="type">Bool</span>
  <span class="keyword">@Environment</span>(\.<span class="property">isEnabled</span>) <span class="keyword">private var</span> isEnabled: <span class="type">Bool</span>
  <span class="keyword">@Environment</span>(\.<span class="property">controlSize</span>) <span class="keyword">private var</span> controlSize: <span class="type">ControlSize</span>

  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    configuration
      .<span class="dotAccess">label</span>
      .<span class="call">scaleEffect</span>(isFocused ? <span class="number">1.2</span> : <span class="number">1</span>)
      .<span class="call">foregroundColor</span>(isEnabled ? .<span class="dotAccess">accentColor</span> : .<span class="dotAccess">gray</span>)
      .<span class="call">padding</span>(controlSize == .<span class="dotAccess">large</span> ? <span class="number">16</span> : <span class="number">0</span>)
  }
}
</code></pre><blockquote><p>Check out <a href="https://www.fivestars.blog/articles/redacted-custom-effects/">How to create custom redacted effects</a> for a dive into <code>redactionReasons</code>.</p></blockquote><h2>Display values</h2><ul><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/calendar"><code>calendar</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/dynamicTypeSize"><code>dynamicTypeSize</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/displayScale"><code>displayScale</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/horizontalSizeClass"><code>horizontalSizeClass</code></a> (iOS)</li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/layoutDirection"><code>layoutDirection</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/locale"><code>locale</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/pixelLength"><code>pixelLength</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/timeZone"><code>timeZone</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/verticalSizeClass"><code>verticalSizeClass</code></a> (iOS)</li></ul><p>The environment exposes important details of, well, the "view environment": such values give us a broader insight into where we're about to display our view, or how we're supposed to do so.</p><p>For example, we can use such values to make make our app layout <a href="https://www.fivestars.blog/articles/adaptive-swiftui-views/">adapt in different situations</a>:</p><pre><code><span class="keyword">struct</span> FSContentView: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">horizontalSizeClass</span>) <span class="keyword">private var</span> horizontalSizeClass: <span class="type">UserInterfaceSizeClass</span>?
  
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">if</span> horizontalSizeClass == .<span class="dotAccess">compact</span> {
      <span class="type">TabNavigation</span>()
    } <span class="keyword">else</span> {
      <span class="type">SidebarNavigation</span>()
    }
  }
}
</code></pre><pre><code><span class="keyword">struct</span> FSContentView: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">dynamicTypeSize</span>) <span class="keyword">private var</span> dynamicTypeSize: <span class="type">DynamicTypeSize</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">if</span> dynamicTypeSize.<span class="property">isAccessibilitySize</span> {
      <span class="type">VStack</span> {
        ...
      }
    } <span class="keyword">else</span> {
      <span class="type">HStack</span> {
        ...
      }
    }
  }
}
</code></pre><p>Most of these values can also be overridden by us. This is particularly useful when used with SwiftUI previews:</p><pre><code><span class="keyword">struct</span> FSContentView_Previews: <span class="type">PreviewProvider</span> {
  <span class="keyword">static var</span> previews: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ForEach</span>(<span class="type">DynamicTypeSize</span>.<span class="property">allCases</span>, id: \.<span class="keyword">self</span>) { dynamicTypeSize <span class="keyword">in</span>
      <span class="type">FSContentView</span>()
        .<span class="call">environment</span>(\.<span class="property">dynamicTypeSize</span>, dynamicTypeSize)
        .<span class="call">previewDisplayName</span>(<span class="string">"</span>\(dynamicTypeSize)<span class="string">"</span>)
    }
    .<span class="call">previewLayout</span>(.<span class="dotAccess">sizeThatFits</span>)
  }
}
</code></pre><h2>View-specific values</h2><p>The majority of environment values are meant to take effect on specific views. An example we're probably familiar with is view styles (<a href="https://www.fivestars.blog/articles/button-styles/"><code>Button</code></a> styles, <a href="https://www.fivestars.blog/articles/label/"><code>Label</code></a> styles, <a href="https://www.fivestars.blog/articles/how-to-customize-textfields/"><code>TextField</code></a> styles, etc.).</p><p>Here we will see <em>interesting</em> decisions/trade-offs that the SwiftUI team took in different situations.<br>The main three options the team had are:</p><ol><li>expose an environment value that can be both read and, optionally, written by third-party developers</li><li>expose an associated view modifier and hide the environment value (e.g., all styles)</li><li>expose both the environment value and the associated view modifier</li></ol><h3>List</h3><ul><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/defaultMinListRowHeight"><code>defaultMinListRowHeight</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/defaultMinListHeaderHeight"><code>defaultMinListHeaderHeight</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/editMode"><code>editMode</code></a> (iOS, tvOS)</li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/headerProminence"><code>headerProminence</code></a></li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-environment-values/list.png"/><pre><code><span class="type">List</span> {
  <span class="type">Section</span> {
    <span class="type">Text</span>(<span class="string">"Very tall row"</span>)
  } header: {
    <span class="type">Text</span>(<span class="string">"Increased prominence header"</span>)
  }
  .<span class="call">headerProminence</span>(.<span class="dotAccess">increased</span>)

  <span class="type">Section</span> {
    <span class="type">Text</span>(<span class="string">"Another very tall row"</span>)
  } header: {
    <span class="type">Text</span>(<span class="string">"Standard prominence header"</span>)
  }
}
<span class="comment">// 👇🏻 sets the minimum row height to 200pt</span>
.<span class="call">environment</span>(\.<span class="property">defaultMinListRowHeight</span>, <span class="number">200</span>)
</code></pre><p>Here we already see two of the three decisions above:</p><ul><li><code>defaultMinListRowHeight</code> and <code>defaultMinListHeaderHeight</code> are exposed as environment values that can be both read and set</li><li><code>headerProminence</code> is also exposed as an environment value, but we can only set it via its associated <code>.headerProminence(_:)</code> modifier (attempting to setting it directly via <code>.environment(\.headerProminence, .increased)</code> won't have any effect, FB9543744)</li></ul><h3>Symbols</h3><ul><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/imageScale"><code>imageScale</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/symbolVariants"><code>symbolVariants</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/symbolRenderingMode"><code>symbolRenderingMode</code></a></li></ul><p>SF Symbols have received <a href="https://www.wwdcnotes.com/notes/wwdc21/10097/">wonderful updates</a> this year thanks to their new variants and modes, these parameters are also available for our views to observe:</p><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">symbolVariants</span>) <span class="keyword">private var</span> symbolVariant: <span class="type">SymbolVariants</span>
  <span class="keyword">@Environment</span>(\.<span class="property">symbolRenderingMode</span>) <span class="keyword">private var</span> symbolRenderingMode: <span class="type">SymbolRenderingMode</span>?

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    ...
  }
}
</code></pre><p>However, most likely, we will use them to apply such options on <code>Label</code> or other places where we use SF Symbols:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-environment-values/symbols.png"/><pre><code><span class="type">VStack</span>(alignment: .<span class="dotAccess">leading</span>) {
  <span class="type">Label</span>(<span class="string">"Ace of Hearts"</span>, systemImage: <span class="string">"suit.heart"</span>)
  <span class="type">Label</span>(<span class="string">"Ace of Spades"</span>, systemImage: <span class="string">"suit.spade"</span>)
  <span class="type">Label</span>(<span class="string">"Ace of Diamonds"</span>, systemImage: <span class="string">"suit.diamond"</span>)
  <span class="type">Label</span>(<span class="string">"Ace of Clubs"</span>, systemImage: <span class="string">"suit.club"</span>)
}
.<span class="call">symbolRenderingMode</span>(.<span class="dotAccess">multicolor</span>)
.<span class="call">symbolVariant</span>(.<span class="dotAccess">fill</span>)
</code></pre><h3>Pickers</h3><ul><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/defaultWheelPickerItemHeight"><code>defaultWheelPickerItemHeight</code></a> (WatchOS)</li></ul><p>WatchOS only, this value lets us read and set the height of a wheel picker.</p><pre><code><span class="type">Picker</span>(<span class="string">"My Favorite Fruit"</span>, selection: $fruit) {
  <span class="type">ForEach</span>(<span class="type">Fruit</span>.<span class="property">allCases</span>, id: \.<span class="keyword">self</span>) {
    <span class="type">Text</span>(<span class="string">"</span>\($0)<span class="string">"</span> <span class="keyword">as</span> <span class="type">String</span>)
  }
}.<span class="call">environment</span>(\.<span class="property">defaultWheelPickerItemHeight</span>, <span class="number">125</span>)
</code></pre><h3>Search</h3><ul><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/isSearching"><code>isSearching</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/dismissSearch"><code>dismissSearch</code></a></li></ul><p>SwiftUI's new search functionality comes with a couple of environment values, which can be used, respectively, for:</p><ul><li>detecting when the user is doing a search</li><li>provide an alternative way to dismiss/stop the current search</li></ul><pre><code><span class="keyword">struct</span> StarredReposList: <span class="type">View</span> {
  <span class="keyword">@StateObject var</span> viewModel = <span class="type">SearchViewModel</span>()
  <span class="keyword">@Environment</span>(\.<span class="property">dismissSearch</span>) <span class="keyword">var</span> dismissSearch
  <span class="keyword">@Environment</span>(\.<span class="property">isSearching</span>) <span class="keyword">var</span> isSearching

  <span class="keyword">let</span> query: <span class="type">String</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">List</span>(viewModel.<span class="property">repos</span>) { repo <span class="keyword">in</span>
      <span class="type">RepoView</span>(repo: repo)
    }
    .<span class="call">overlay</span> {
      <span class="keyword">if</span> isSearching &amp;&amp; !query.<span class="property">isEmpty</span> {
        <span class="type">VStack</span> {
          <span class="type">Button</span>(<span class="string">"Dismiss search"</span>) {
            <span class="call">dismissSearch</span>()
          }
          <span class="type">SearchResultView</span>(query: query)
            .<span class="call">environmentObject</span>(viewModel)
        }
      }
    }
  }
}
</code></pre><blockquote><p>Example from <a href="https://swiftwithmajid.com/2021/06/23/mastering-search-in-swiftui/">Mastering search in SwiftUI</a> by <a href="http://twitter.com/mecid/">Majid Jabrayilov</a>.</p></blockquote><h3>Keyboard shortcuts</h3><ul><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/keyboardShortcut"><code>keyboardShortcut</code></a> (iOS, macOS)</li></ul><img src="https://www.fivestars.blog/assets/posts/swiftui-environment-values/shortcut.png"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">Button</span>(<span class="string">"Tap me or press ⌘F"</span>) {
        <span class="call">print</span>(<span class="string">"tapped or pressed"</span>)
      }
      .<span class="call">keyboardShortcut</span>(<span class="string">"F"</span>, modifiers: [.<span class="dotAccess">command</span>])

      <span class="type">Button</span>(<span class="string">"Tap me or press ⌘S"</span>) {
        <span class="call">print</span>(<span class="string">"tapped or pressed"</span>)
      }
      .<span class="call">keyboardShortcut</span>(<span class="string">"S"</span>, modifiers: [.<span class="dotAccess">command</span>])
    }
    .<span class="call">buttonStyle</span>(<span class="type">FiveStarsButtonStyle</span>())
  }
}

<span class="keyword">private struct</span> FiveStarsButtonStyle: <span class="type">ButtonStyle</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">keyboardShortcut</span>) <span class="keyword">private var</span> shortcut: <span class="type">KeyboardShortcut</span>? <span class="comment">// 👈🏻</span>

  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    configuration.<span class="property">label</span>
      .<span class="call">font</span>(.<span class="dotAccess">body</span>.<span class="call">weight</span>(shortcut == .<span class="keyword">init</span>(<span class="string">"F"</span>) ? .<span class="dotAccess">heavy</span> : .<span class="dotAccess">regular</span>))
  }
}
</code></pre><blockquote><p>Example from <a href="https://www.fivestars.blog/articles/xcode-13-beta-3/">What's new in Xcode 13 beta 3</a>.</p></blockquote><p><code>keyboardShortcut</code> environment value lets us read the assigned keyboard shortcut to any view. We can use this value for example to customize the view appearance.</p><h3>Text</h3><ul><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/font"><code>font</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/legibilityWeight"><code>legibilityWeight</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/multilineTextAlignment"><code>multilineTextAlignment</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/truncationMode"><code>truncationMode</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/lineSpacing"><code>lineSpacing</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/allowsTightening"><code>allowsTightening</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/lineLimit"><code>lineLimit</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/minimumScaleFactor"><code>minimumScaleFactor</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/textCase"><code>textCase</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/disableAutocorrection"><code>disableAutocorrection</code></a></li></ul><p>Rendering and editing text is complicated. There are many parameters to consider at any given time. Apple platforms come with TextKit and Core Text for most advanced needs, which help us fine control various aspects such as <a href="https://www.wwdcnotes.com/notes/wwdc13/220/">text storage, layout management, and text geometry containment</a>.</p><p>SwiftUI exposes none of that. However, text rendering/editing still manages to be SwiftUI's corner with the most number of dedicated view modifiers and environment values.</p><img src="https://www.fivestars.blog/assets/posts/swiftui-environment-values/text.png"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Text</span>(
      <span class="string">"""
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
      Ut vitae erat sit amet risus hendrerit cursus. 
      Aenean pulvinar ligula mauris, eu molestie magna sodales non. 
      In malesuada mattis nibh, in gravida elit.
      """</span>
    )
    .<span class="call">modifier</span>(<span class="type">BenevolentLineLimit</span>())
    .<span class="call">lineLimit</span>(<span class="number">2</span>)
  }
}

<span class="comment">/// Doubles the `lineLimit` number.</span>
<span class="keyword">struct</span> BenevolentLineLimit: <span class="type">ViewModifier</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">lineLimit</span>) <span class="keyword">private var</span> lineLimit: <span class="type">Int</span>?

  <span class="keyword">func</span> body(content: <span class="type">Content</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">let</span> newLineLimit: <span class="type">Int</span>?

    <span class="keyword">if let</span> lineLimit = lineLimit {
      newLineLimit = lineLimit * <span class="number">2</span>
    } <span class="keyword">else</span> {
      newLineLimit = <span class="keyword">nil</span>
    }
    <span class="keyword">return</span> content.<span class="call">lineLimit</span>(newLineLimit)
  }
}
</code></pre><blockquote><p>This is just a sneak peek into text management on Apple platforms. See <a href="https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/">Cocoa Text Architecture Guide</a> for much more.</p></blockquote><h2>Accessibility</h2><ul><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/accessibilityEnabled"><code>accessibilityEnabled</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/accessibilityDifferentiateWithoutColor"><code>accessibilityDifferentiateWithoutColor</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/accessibilityReduceTransparency"><code>accessibilityReduceTransparency</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/accessibilityReduceMotion"><code>accessibilityReduceMotion</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/accessibilityInvertColors"><code>accessibilityInvertColors</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/accessibilityShowButtonShapes"><code>accessibilityShowButtonShapes</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/accessibilityVoiceOverEnabled"><code>accessibilityVoiceOverEnabled</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/accessibilitySwitchControlEnabled"><code>accessibilitySwitchControlEnabled</code></a></li></ul><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">accessibilityReduceTransparency</span>) <span class="keyword">var</span> reduceTransparency

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      ...
    }
    .<span class="call">background</span>(<span class="type">Color</span>.<span class="property">red</span>.<span class="call">opacity</span>(reduceTransparency ? <span class="number">1</span> : <span class="number">0.5</span>))
  }
}
</code></pre><p>Accessibility has always been a main focus in SwiftUI: the number of view modifiers, environment values, documentation, etc., really shows that.</p><p>Unlike most other values, accessibility environment values are (nearly) all read-only.</p><h2>Presentation</h2><ul><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/dismiss"><code>dismiss</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/isPresented"><code>isPresented</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/scenePhase"><code>scenePhase</code></a></li></ul><p>The environment also gives us important values regarding the current presentation state, and even let us take control of the view presentation itself:</p><pre><code><span class="keyword">struct</span> FSSheet: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">dismiss</span>) <span class="keyword">var</span> dismiss

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(<span class="string">"Dismiss Me"</span>) {
      <span class="call">dismiss</span>()
    }
  }
}
</code></pre><h2>Actions</h2><ul><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/openURL"><code>openURL</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/refresh"><code>refresh</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/resetFocus"><code>resetFocus</code></a> (macOS, tvOS, watchOS)</li></ul><p>This year we've seen new actions as environment values, which let us both grab and set closures that are then invoked by other views down the hierarchy:</p><pre><code><span class="keyword">struct</span> FSView {
  <span class="keyword">@Environment</span>(\.<span class="property">openURL</span>) <span class="keyword">var</span> openURL

  <span class="keyword">var</span> body {
    ...
  }

  <span class="keyword">func</span> onOpenURLTap(<span class="keyword">_</span> url: <span class="type">URL</span>, completion: <span class="keyword">@escaping</span> (<span class="keyword">_</span> accepted: <span class="type">Bool</span>) -&gt; <span class="type">Void</span>) {
    <span class="call">openURL</span>(url, completion: completion)
  }
}
</code></pre><blockquote><p>Example from <a href="https://www.fivestars.blog/articles/openurl-openurlaction/">Handling links with SwiftUI's openURL</a>.</p></blockquote><h2>Colors/Themes</h2><ul><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/colorScheme"><code>colorScheme</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/colorSchemeContrast"><code>colorSchemeContrast</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/backgroundMaterial"><code>backgroundMaterial</code></a></li></ul><p>The environment makes it easy to propagate preferences such as colors and themes. SwiftUI comes with a few values of its own:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-environment-values/scheme.png"/><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">colorScheme</span>) <span class="keyword">var</span> scheme

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Label</span>(title, systemImage: systemImage)
  }

  <span class="keyword">var</span> title: <span class="type">LocalizedStringKey</span> {
    <span class="keyword">switch</span> scheme {
      <span class="keyword">case</span> .<span class="dotAccess">dark</span>: <span class="keyword">return</span> <span class="string">"Dark"</span>
      <span class="keyword">case</span> .<span class="dotAccess">light</span>: <span class="keyword">fallthrough
      @unknown default</span>: <span class="keyword">return</span> <span class="string">"Light"</span>
    }
  }

  <span class="keyword">var</span> systemImage: <span class="type">String</span> {
    <span class="keyword">switch</span> scheme {
      <span class="keyword">case</span> .<span class="dotAccess">dark</span>: <span class="keyword">return</span> <span class="string">"moon"</span>
      <span class="keyword">case</span> .<span class="dotAccess">light</span>: <span class="keyword">fallthrough
      @unknown default</span>: <span class="keyword">return</span> <span class="string">"sun.max"</span>
    }
  }
}
</code></pre><h2>One-off</h2><ul><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/managedObjectContext"><code>managedObjectContext</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/environmentvalues/undoManager"><code>undoManager</code></a></li></ul><p>Lastly, we have a couple of environment values that are not strictly associated with any view, but still need to be propagated into and managed within the environment.</p><pre><code><span class="keyword">struct</span> AddItemView: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">managedObjectContext</span>) <span class="keyword">var</span> managedObjectContext
  <span class="keyword">@State var</span> itemTitle: <span class="type">String</span> = <span class="string">""</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    ...
  }

  <span class="keyword">var</span> addButton: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(<span class="string">"Add item"</span>) {
      <span class="keyword">let</span> item = <span class="type">FSItem</span>(context: managedObjectContext)
      item.<span class="property">title</span> = itemTitle
      <span class="keyword">try</span>? selfmanagedObjectContext.<span class="call">save</span>()
      <span class="comment">// to do: navigate back and handle errors</span>
    }
  }
}
</code></pre><blockquote><p>For a great introduction on <code>UndoManager</code>, check out <a href="https://lostmoa.com/blog/HandlingUndoAndRedoInSwiftUI/">Handling Undo &amp; Redo in SwiftUI</a> by <a href="https://twitter.com/hishnash">Matthaus Woolard</a>.</p></blockquote><h2>Conclusions</h2><p>SwiftUI exposes over 50 environment values (without considering deprecated ones). I'd argue that the SwiftUI team has been conservative on what to make public:<br>more values could be exposed as read-only, <code>foregroundColor</code> and <code>tintColor</code> immediately come to mind (FB8161189, FB9552347).</p><p>Do you use or define environment values in your apps? Let me know via <a href="mailto:hello@fivestars.blog">email</a> or <a href="https://twitter.com/zntfdr">Twitter</a>!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/openurl-openurlaction</guid><title>Handling links with SwiftUI's openURL</title><description>A deep dive into the latest environment value!</description><link>https://www.fivestars.blog/articles/openurl-openurlaction</link><pubDate>Tue, 17 Aug 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Attributed strings and <code>Text</code> have received significant upgrades this year. This upgrade has extended even further in the latest Xcode beta, thanks to the opportunity to set the <a href="https://developer.apple.com/documentation/swiftui/environmentvalues/openurl"><code>openURL</code></a> environment value.</p><p>In this article, let's explore everything around <code>openUrl</code> and handling links in SwiftUI views.</p><h2>Introduction</h2><p>Two views handle URLs in SwiftUI: <code>Link</code> and, from this year, <code>Text</code>.</p><h3>Link</h3><p>From iOS 14 (and equivalent in other platforms) SwiftUI apps can declare <code>Link</code>s in views.</p><pre><code><span class="type">Link</span>(<span class="string">"Go to the main blog"</span>, destination: <span class="type">URL</span>(string: <span class="string">"https://fivestars.blog/"</span>)!)
</code></pre><p><code>Link</code>s are buttons in disguise (we can apply button styles), specializing in opening URLs. Their primary purpose is to deep-link <a href="https://fivestars.blog/articles/swiftui-widgetkit/">into apps from widgets</a>, as no logic can run on widgets (as of iOS 15/macOS 12).</p><p>Their use is not limited to widgets. We can use <code>Link</code>s within apps for various situations/needs:</p><img src="https://www.fivestars.blog/assets/posts/openurl-openurlaction/link.png"/><pre><code><span class="comment">// Opens URL in Safari</span>
<span class="type">Link</span>(<span class="string">"Go to the main blog"</span>, destination: <span class="type">URL</span>(string: <span class="string">"https://fivestars.blog/"</span>)!)
  .<span class="call">buttonStyle</span>(.<span class="dotAccess">borderedProminent</span>)

<span class="comment">// Opens Settings.app</span>
<span class="type">Link</span>(<span class="string">"Go to settings"</span>, destination: <span class="type">URL</span>(string: <span class="type">UIApplication</span>.<span class="property">openSettingsURLString</span>)!)
  .<span class="call">buttonStyle</span>(.<span class="dotAccess">bordered</span>)

<span class="comment">// Open third party app</span>
<span class="type">Link</span>(<span class="string">"Go to app"</span>, destination: <span class="type">URL</span>(string: <span class="string">"bangkok-metro://Sukhumvit%20Line%2FAsok"</span>)!)
</code></pre><h3>Text</h3><p><code>Text</code> is one of the SwiftUI views that has received most new functionalities this year, mainly thanks to the new <a href="https://www.wwdcnotes.com/notes/wwdc21/10109/">markdown and <code>AttributedString</code></a> support.</p><p>By using either of these new functionalities, we can now add links to <code>Text</code>:</p><img src="https://www.fivestars.blog/assets/posts/openurl-openurlaction/text.png"/><pre><code><span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
  <span class="type">VStack</span> {
    <span class="type">Text</span>(attributedString)
    <span class="type">Text</span>(<span class="string">"Check out [Five Stars](https://fivestars.blog)"</span>)
  }
}

<span class="keyword">var</span> attributedString: <span class="type">AttributedString</span> {
  <span class="keyword">var</span> attributedText = <span class="type">AttributedString</span>(<span class="string">"Visit website"</span>)
  attributedText.<span class="property">link</span> = <span class="type">URL</span>(string: <span class="string">"https://fivestars.blog"</span>)
  <span class="keyword">return</span> attributedText
}
</code></pre><p>Similar to <code>Link</code>, <code>Text</code> also supports non-<em>http</em> urls.</p><h2><code>openURL</code></h2><p>When the user interacts with a link (in either a <code>Text</code> or <code>Link</code> view), SwiftUI will reach for the view <code>openURL</code> environment value:</p><pre><code><span class="keyword">extension</span> <span class="type">EnvironmentValues</span> {
  <span class="keyword">public var</span> openURL: <span class="type">OpenURLAction</span> { <span class="keyword">get</span> }
}
</code></pre><p>Where the <code>OpenURLAction</code> is defined as following:</p><pre><code><span class="keyword">public struct</span> OpenURLAction {
  <span class="keyword">public func</span> callAsFunction(<span class="keyword">_</span> url: <span class="type">URL</span>)
  <span class="keyword">public func</span> callAsFunction(<span class="keyword">_</span> url: <span class="type">URL</span>, completion: <span class="keyword">@escaping</span> (<span class="keyword">_</span> accepted: <span class="type">Bool</span>) -&gt; <span class="type">Void</span>)
}
</code></pre><p>The view will call <code>openURL</code> with the relevant <code>URL</code>. The system will then receive and handle the URL, which will either open the default device browser and load a web page, or deep-link into another app.</p><p>As <code>openURL</code> is a public environment value, we can also use it ourselves, replacing legacy's <code>UIApplication</code> calls:</p><pre><code><span class="keyword">struct</span> FSView {
  <span class="keyword">@Environment</span>(\.<span class="property">openURL</span>) <span class="keyword">var</span> openURL <span class="comment">// 👈🏻 New way</span>

  <span class="keyword">var</span> body {
    ...
  }

  <span class="keyword">func</span> onOpenURLTap(<span class="keyword">_</span> url: <span class="type">URL</span>, completion: <span class="keyword">@escaping</span> (<span class="keyword">_</span> accepted: <span class="type">Bool</span>) -&gt; <span class="type">Void</span>) {
    <span class="comment">// Old way 👇🏻</span>
    <span class="keyword">if</span> <span class="type">UIApplication</span>.<span class="property">shared</span>.<span class="call">canOpenURL</span>(url) {
      <span class="type">UIApplication</span>.<span class="property">shared</span>.<span class="call">open</span>(url)
      <span class="call">completion</span>(<span class="keyword">true</span>)
    } <span class="keyword">else</span> {
      <span class="call">completion</span>(<span class="keyword">false</span>)
    }

    <span class="comment">// New way 👇🏻</span>
    <span class="call">openURL</span>(url, completion: completion)
  }
}
</code></pre><h2>What's new</h2><p>New in Xcode 13 beta 5, <code>openURL</code> can not only be read, but also set:</p><pre><code><span class="keyword">extension</span> <span class="type">EnvironmentValues</span> {
  <span class="comment">// public var openURL: OpenURLAction { get } 👈🏻 Previous declaration</span>
  <span class="keyword">public var</span> openURL: <span class="type">OpenURLAction</span> <span class="comment">// 👈🏻 New declaration</span>
}
</code></pre><p><code>OpenURLAction</code> has also gained a public initializer:</p><pre><code><span class="keyword">public struct</span> OpenURLAction {
  <span class="keyword">@available</span>(iOS <span class="number">15.0</span>, macOS <span class="number">12.0</span>, tvOS <span class="number">15.0</span>, watchOS <span class="number">8.0</span>, *)
  <span class="keyword">public init</span>(handler: <span class="keyword">@escaping</span> (<span class="type">URL</span>) -&gt; <span class="type">OpenURLAction</span>.<span class="type">Result</span>)
}
</code></pre><p>Which requires a closure returning a new <code>OpenURLAction.Result</code> type, defined as following:</p><pre><code><span class="keyword">public struct</span> Result {
  <span class="keyword">public static let</span> handled: <span class="type">OpenURLAction</span>.<span class="type">Result</span>
  <span class="keyword">public static let</span> discarded: <span class="type">OpenURLAction</span>.<span class="type">Result</span>
  <span class="keyword">public static let</span> systemAction: <span class="type">OpenURLAction</span>.<span class="type">Result</span>
  <span class="keyword">public static func</span> systemAction(<span class="keyword">_</span> url: <span class="type">URL</span>) -&gt; <span class="type">OpenURLAction</span>.<span class="type">Result</span>
}
</code></pre><p>The default behavior stays the same, but this <em>small</em> change enables third-party developers to control the URL handling entirely. Let's have a look at that next.</p><h2>Handling URLs</h2><p>The default behavior is equivalent to setting the following <code>openURL</code> value:</p><pre><code><span class="type">Link</span>(<span class="string">"FIVE STARS"</span>, destination: <span class="type">URL</span>(string: <span class="string">"https://fivestars.blog/"</span>)!)
  .<span class="call">environment</span>(\.<span class="property">openURL</span>, <span class="type">OpenURLAction</span> { url <span class="keyword">in
    return</span> .<span class="dotAccess">systemAction</span>
  })
</code></pre><p><code>OpenURLAction.Result</code> has three more options. Let's have a look at those next.</p><h3><code>.handled</code></h3><p><code>.handled</code> tells SwiftUI that our app logic successfully took care of the url:</p><pre><code><span class="type">Link</span>(<span class="string">"FIVE STARS"</span>, destination: <span class="type">URL</span>(string: <span class="string">"https://fivestars.blog/"</span>)!)
  .<span class="call">environment</span>(\.<span class="property">openURL</span>, <span class="type">OpenURLAction</span> { url <span class="keyword">in</span>
    <span class="comment">// ...</span>
    <span class="keyword">return</span> .<span class="dotAccess">handled</span>
  })
</code></pre><p>Regardless of the URL value, by returning <code>.handled</code>, the system won't open <code>Safari</code> or trigger any deep link.</p><h3><code>.discarded</code></h3><p><code>.discarded</code> works exactly like <code>.handled</code>, but tells SwiftUI that the url couldn't be handled.</p><pre><code><span class="type">Link</span>(<span class="string">"FIVE STARS"</span>, destination: <span class="type">URL</span>(string: <span class="string">"https://fivestars.blog/"</span>)!)
  .<span class="call">environment</span>(\.<span class="property">openURL</span>, <span class="type">OpenURLAction</span> { url <span class="keyword">in</span>
    <span class="comment">// ...</span>
    <span class="keyword">return</span> .<span class="dotAccess">discarded</span>
  })
</code></pre><p>Going back to <code>OpenURLAction</code>'s definition, the difference between <code>.discarded</code> and <code>.handled</code> lays on the value passed within the <code>openURL</code> completion block:</p><pre><code><span class="keyword">public struct</span> OpenURLAction {
  <span class="keyword">public func</span> callAsFunction(<span class="keyword">_</span> url: <span class="type">URL</span>, completion: <span class="keyword">@escaping</span> (<span class="keyword">_</span> accepted: <span class="type">Bool</span>) -&gt; <span class="type">Void</span>)
}
</code></pre><ul><li><code>.handled</code> will call <code>completion</code> with <code>true</code></li><li><code>.discarded</code> will call <code>completion</code> with <code>false</code></li></ul><h3><code>.systemAction(_ url: URL)</code></h3><p>The last option is similar to the default <code>.systemAction</code> behavior, with the difference that we can now forward to a different URL:</p><pre><code><span class="type">Link</span>(<span class="string">"Go to Google"</span>, destination: <span class="type">URL</span>(string: <span class="string">"https://google.com/"</span>)!)
  .<span class="call">environment</span>(\.<span class="property">openURL</span>, <span class="type">OpenURLAction</span> { url <span class="keyword">in</span>
    <span class="comment">// Go to Bing instead 😈</span>
    <span class="keyword">return</span> .<span class="call">systemAction</span>(<span class="type">URL</span>(string: <span class="string">"https://www.bing.com"</span>)!)
  })
</code></pre><h2>Custom actions</h2><p>The hidden power of <code>openURL</code> is within the <code>.handled</code> case:<br>the most common use case has been forwarding users out of the app, but we can now handle different intents.</p><p>For example, imagine a welcome screen within an app, prompting the user to either sign in or sign up:</p><img src="https://www.fivestars.blog/assets/posts/openurl-openurlaction/sign.png"/><pre><code><span class="type">VStack</span> {
  <span class="type">Text</span>(<span class="string">"Welcome to Five Stars!"</span>)
    .<span class="call">font</span>(.<span class="dotAccess">largeTitle</span>)

  <span class="type">Text</span>(<span class="string">"Please [sign in](https://fivestars.blog/sign-in) or [sign up](https://fivestars.blog/create-account) to continue."</span>)
    .<span class="call">font</span>(.<span class="dotAccess">headline</span>)
    .<span class="call">environment</span>(\.<span class="property">openURL</span>, <span class="type">OpenURLAction</span> { url <span class="keyword">in
      switch</span> url.<span class="property">lastPathComponent</span> {
        <span class="keyword">case</span> <span class="string">"sign-in"</span>:
          <span class="comment">// show sign in screen</span>
          <span class="keyword">return</span> .<span class="dotAccess">handled</span>
        <span class="keyword">case</span> <span class="string">"crate-account"</span>:
          <span class="comment">// show sign up screen</span>
          <span class="keyword">return</span> .<span class="dotAccess">handled</span>
        <span class="keyword">default</span>:
          <span class="comment">// Intent not recognized.</span>
          <span class="keyword">return</span> .<span class="dotAccess">discarded</span>
      }
    })
}
</code></pre><p>First, <code>Text</code> has two distinct and interactive parts: this was not possible before the latest SwiftUI iteration.</p><blockquote><p>Despite the new features, I still look forward the day we have a <code>.onTapGesture(_:)</code> <code>Text</code> modifier, FB8917806.</p></blockquote><p>Second, because the URL is now handled within the app, we don't need the whole <code>http</code> declaration.<br>We can simplify the code above with the following:</p><pre><code><span class="type">Text</span>(<span class="string">"Please [sign in](sign-in) or [sign up](create-account) to continue."</span>)
  .<span class="call">font</span>(.<span class="dotAccess">headline</span>)
  .<span class="call">environment</span>(\.<span class="property">openURL</span>, <span class="type">OpenURLAction</span> { url <span class="keyword">in
    switch</span> url.<span class="property">absoluteString</span> {
      <span class="keyword">case</span> <span class="string">"sign-in"</span>:
        <span class="comment">// show sign in page</span>
        <span class="keyword">return</span> .<span class="dotAccess">handled</span>
      <span class="keyword">case</span> <span class="string">"crate-account"</span>:
        <span class="comment">// show sign up page</span>
        <span class="keyword">return</span> .<span class="dotAccess">handled</span>
      <span class="keyword">default</span>:
        <span class="comment">// Intent not recognized.</span>
        <span class="keyword">return</span> .<span class="dotAccess">discarded</span>
    }
  })
</code></pre><h2>Environment value</h2><p>Unlike <code>onSubmit</code>'s <code>onSubmitAction</code> environment value, <a href="https://www.fivestars.blog/articles/onsubmit/">which we covered previously</a>, setting the <code>openURL</code> environment value always replaces the previous one, which means that only the closest closure to our view will be called.</p><p>In the following example, only the closure returning <code>.systemAction</code> will be triggered:</p><pre><code><span class="type">Link</span>(...)
  .<span class="call">environment</span>(\.<span class="property">openURL</span>, <span class="type">OpenURLAction</span> { url <span class="keyword">in
    return</span> .<span class="dotAccess">systemAction</span>
  })
  .<span class="call">environment</span>(\.<span class="property">openURL</span>, <span class="type">OpenURLAction</span> { url <span class="keyword">in
    return</span> .<span class="call">systemAction</span>(<span class="type">URL</span>(string: <span class="string">"https://www.anotherURL.com"</span>)!)
  })
  .<span class="call">environment</span>(\.<span class="property">openURL</span>, <span class="type">OpenURLAction</span> { url <span class="keyword">in
    return</span> .<span class="dotAccess">handled</span>
  })
  .<span class="call">environment</span>(\.<span class="property">openURL</span>, <span class="type">OpenURLAction</span> { url <span class="keyword">in
    return</span> .<span class="dotAccess">discarded</span>
  })
</code></pre><p>As of today, there's no way to tell SwiftUI to trigger the "next" closure instead of stopping after the first one.</p><h3>View Modifier</h3><p>In SwiftUI we already have a <code>onOpenURL(perform:)</code> view modifier, used to handle deep-links in the app. This naming is unfortunate, as it would make sense to have a companion <code>onOpenURL(handler:)</code> modifier for <code>openURL</code>.</p><p>Despite this modifier not being part of the official API, we can still create it ourselves:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">func</span> onOpenURL(handler: <span class="keyword">@escaping</span> (<span class="type">URL</span>) -&gt; <span class="type">OpenURLAction</span>.<span class="type">Result</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">environment</span>(\.<span class="property">openURL</span>, <span class="type">OpenURLAction</span>(handler: handler))
  }
}
</code></pre><p>This extension would, for example, reduce this:</p><pre><code><span class="type">Link</span>(<span class="string">"FIVE STARS"</span>, destination: <span class="type">URL</span>(string: <span class="string">"https://fivestars.blog/"</span>)!)
  .<span class="call">environment</span>(\.<span class="property">openURL</span>, <span class="type">OpenURLAction</span> { url <span class="keyword">in
    return</span> .<span class="dotAccess">systemAction</span>
  })
</code></pre><p>Into this:</p><pre><code><span class="type">Link</span>(<span class="string">"FIVE STARS"</span>, destination: <span class="type">URL</span>(string: <span class="string">"https://fivestars.blog/"</span>)!)
  .<span class="call">onOpenURL</span>(handler: { url <span class="keyword">in
    return</span> .<span class="dotAccess">systemAction</span>
  })
</code></pre><p>...which is less verbose and easier on the eye.</p><h2>Conclusions</h2><p>We're now at the third SwiftUI iteration, and the SwiftUI team is steadily patching all the essential missing features. Are you going to use <code>openURL</code> in your apps? Please let me know via <a href="mailto:hello@fivestars.blog">email</a> or <a href="https://twitter.com/zntfdr">Twitter</a>!</p><blockquote><p>This article is part of a series exploring new SwiftUI features. We will cover many more during the rest of the summer: subscribe to Five Stars's <a href="https://www.fivestars.blog/feed.rss">feed RSS</a> or follow <a href="http://twitter.com/fiveStarsBlog">@FiveStarsBlog</a> on Twitter to never miss new content!</p></blockquote>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/xcode-13-beta-5</guid><title>What's new in Xcode 13 beta 5</title><description>Links handling, ContentShapeKinds, and more!</description><link>https://www.fivestars.blog/articles/xcode-13-beta-5</link><pubDate>Wed, 11 Aug 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Xcode 13 beta 5 has just been released: let's have a look at what's new in SwiftUI!</p><h2>Interactive shapes with <code>ContentShapeKinds</code></h2><pre><code><span class="type">Button</span>(<span class="string">"Hover me"</span>) {}
  .<span class="call">hoverEffect</span>(.<span class="dotAccess">lift</span>)
  .<span class="call">contentShape</span>(.<span class="dotAccess">hoverEffect</span>, <span class="type">Circle</span>())

<span class="type">Text</span>(<span class="string">"Open Menu"</span>)
  .<span class="call">contextMenu</span> {
    <span class="type">Button</span>(...) { ... }
    <span class="type">Button</span>(...) { ... }
  }
  .<span class="call">contentShape</span>(.<span class="dotAccess">contextMenuPreview</span>, <span class="type">Circle</span>())
</code></pre><p>A new <code>contentShape(_:_:eofill:)</code> modifier has been introduced, letting us further define the shape of our interactive elements in special situations:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span>
  <span class="keyword">@inlinable public func</span> contentShape&lt;S: <span class="type">Shape</span>&gt;(
    <span class="keyword">_</span> kind: <span class="type">ContentShapeKinds</span>,
    <span class="keyword">_</span> shape: <span class="type">S</span>,
    eoFill: <span class="type">Bool</span> = <span class="keyword">false</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span>
}
</code></pre><p><code>ContentShapeKinds</code> is a new option set with the following values:</p><pre><code><span class="keyword">@available</span>(iOS <span class="number">15.0</span>, macOS <span class="number">12.0</span>, tvOS <span class="number">15.0</span>, watchOS <span class="number">8.0</span>, *)
<span class="keyword">public struct</span> ContentShapeKinds : <span class="type">OptionSet</span> {
  <span class="keyword">public static let</span> interaction: <span class="type">ContentShapeKinds</span>
  <span class="keyword">public static let</span> dragPreview: <span class="type">ContentShapeKinds</span>
  <span class="keyword">public static let</span> contextMenuPreview: <span class="type">ContentShapeKinds</span>
  <span class="keyword">public static let</span> hoverEffect: <span class="type">ContentShapeKinds</span>
}
</code></pre><blockquote><p>iOS 15b5 simulator crashes when showing a context menu, FB9486120.</p></blockquote><h2>Custom links handling</h2><pre><code><span class="type">VStack</span> {
  <span class="type">Link</span>(<span class="string">"Visit Five Stars"</span>, destination: <span class="type">URL</span>(string: <span class="string">"https://fivestars.blog"</span>)!)
  <span class="type">Text</span>(<span class="string">"Visit [Five Stars](https://fivestars.blog) for more SwiftUI."</span>)
}
.<span class="call">environment</span>(\.<span class="property">openURL</span>, <span class="type">OpenURLAction</span> { url <span class="keyword">in</span>
  <span class="comment">// do something here...</span>
  <span class="keyword">return</span> .<span class="dotAccess">handled</span>
})
</code></pre><p><code>Link</code>s and links within <code>Text</code> can now trigger custom actions. The default (and previous) behavior is for these links to open the associated URL on the device's browser. Now it's up to apps to decide what to do.</p><p>The custom behavior is possible by setting the <code>openURL</code> environment value.</p><p>Here are the possible outcomes we can return when a link is tapped:</p><pre><code><span class="keyword">public struct</span> OpenURLAction.<span class="type">Result</span> {
  <span class="comment">/// The URL was handled (by the app).</span>
  <span class="keyword">public static let</span> handled: <span class="type">OpenURLAction</span>.<span class="type">Result</span>

  <span class="comment">/// Action was ignored.</span>
  <span class="keyword">public static let</span> discarded: <span class="type">OpenURLAction</span>.<span class="type">Result</span>

  <span class="comment">/// The original URL should be handled by the system (default behavior, opens browser with original URL).</span>
  <span class="keyword">public static let</span> systemAction: <span class="type">OpenURLAction</span>.<span class="type">Result</span>

  <span class="comment">/// The given URL should be handled by the system (opens browser with the given URL).</span>
  <span class="keyword">public static func</span> systemAction(<span class="keyword">_</span> url: <span class="type">URL</span>) -&gt; <span class="type">OpenURLAction</span>.<span class="type">Result</span>
}
</code></pre><blockquote><p>Opening a URL via Link view on iOS 15b5 simulator crashes very often, FB9486139.</p></blockquote><h2>More binding options</h2><p>Following <a href="https://github.com/apple/swift-evolution/blob/main/proposals/0293-extend-property-wrappers-to-function-and-closure-parameters.md"><code>SE-0293</code></a> and <code>List</code> new binding capabilities, now also hierarchy <code>List</code>s and <code>OutlineGroup</code>s accept binding parameters:</p><pre><code><span class="keyword">struct</span> FileItem: <span class="type">Identifiable</span> {
  <span class="keyword">var</span> name: <span class="type">String</span>
  <span class="keyword">var</span> children: [<span class="type">FileItem</span>]?

  <span class="keyword">var</span> id: <span class="type">String</span> { name }

  <span class="keyword">static let</span> spmData: [<span class="type">FileItem</span>] = [
    <span class="type">FileItem</span>(name: <span class="string">".gitignore"</span>),
    <span class="type">FileItem</span>(name: <span class="string">"Package.swift"</span>),
    <span class="type">FileItem</span>(name: <span class="string">"README.md"</span>),
    <span class="type">FileItem</span>(name: <span class="string">"Sources"</span>, children: [
      <span class="type">FileItem</span>(name: <span class="string">"fivestars"</span>, children: [
        <span class="type">FileItem</span>(name: <span class="string">"main.swift"</span>)
      ])
    ])
  ]
}

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> data: [<span class="type">FileItem</span>] = <span class="type">FileItem</span>.<span class="property">spmData</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">List</span>($data, children: \.<span class="property">children</span>) { $item <span class="keyword">in</span> <span class="comment">// 👈🏻</span>
      <span class="type">TextField</span>(<span class="string">"Name"</span>, text: $item.<span class="property">name</span>)
    }
  }
}
</code></pre><blockquote><p>Making a few changes to the children values in the example above crashes the app, FB9486151.</p></blockquote><h2>Task priorities</h2><p>We have a new <code>task(priority:_:)</code> view modifier letting us specify the priority to use when creating the given task. The previous modifier (without <code>priority</code> parameter) has been removed.</p><p>From:</p><pre><code>.<span class="call">task</span> {
  ...
}
</code></pre><p>To:</p><pre><code>.<span class="call">task</span>(priority: .<span class="dotAccess">high</span>) {
  ...
}
</code></pre><h2><code>AnimatableModifier</code> deprecation</h2><p>The <code>AnimatableModifier</code> protocol has been deprecated, instead we can make our modifiers conform to both <code>Animatable</code> and <code>ViewModifier</code>.</p><p>From:</p><pre><code><span class="keyword">struct</span> FSAnimationEffect: <span class="type">AnimatableModifier</span> { <span class="comment">// 👈🏻</span>
  <span class="keyword">var</span> animatableData: <span class="type">CGFloat</span> { ... }

  <span class="keyword">func</span> body(content: <span class="type">Content</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    ...
  }
}
</code></pre><p>To:</p><pre><code><span class="keyword">struct</span> FSAnimationEffect: <span class="type">ViewModifier</span>, <span class="type">Animatable</span> { <span class="comment">// 👈🏻</span>
  <span class="keyword">var</span> animatableData: <span class="type">CGFloat</span> { ... }

  <span class="keyword">func</span> body(content: <span class="type">Content</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    ...
  }
}
</code></pre><h2>Documentation</h2><p>We have a new documentation for:</p><ul><li><a href="https://developer.apple.com/documentation/swiftui/fetchrequest"><code>@FetchRequest</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/fetchedresults"><code>FetchedResults</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/text"><code>Text</code></a></li></ul><h2>Conclusions</h2><p>Did you find anything else interesting in Xcode 13 beta 5? Please <a href="https://twitter.com/zntfdr">let me know</a>!</p><p>We should have at least one more Xcode beta seed before the release candidate: don't forget to <a href="https://feedbackassistant.apple.com">report any issue you find to Apple</a>!</p><blockquote><p>This article is part of a series exploring new SwiftUI features. We will cover many more during the rest of the summer: subscribe to Five Stars's <a href="https://www.fivestars.blog/feed.rss">feed RSS</a> or follow <a href="http://twitter.com/fiveStarsBlog">@FiveStarsBlog</a> on Twitter to never miss new content!</p></blockquote>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-windows</guid><title>How to layer multiple windows in SwiftUI</title><description>A complete guide on managing multiple windows in SwiftUI</description><link>https://www.fivestars.blog/articles/swiftui-windows</link><pubDate>Tue, 10 Aug 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Sometimes our apps require to display UI on top of what's on screen: a global alert, a <a href="https://www.fivestars.blog/articles/swiftui-hud/">HUD/toast</a>, etc.<br><code>ZStack</code> is the closest official SwiftUI answer for such needs, and we've covered an example <a href="https://www.fivestars.blog/articles/swiftui-hud/">here</a>.</p><p>However, <code>ZStack</code> falls short as soon as we present a <code>sheet</code> or a <code>fullScreenCover</code>:<br><code>ZStack</code>'s presentation is bound to the views it contains.</p><p>In this article, let's see how we can overcome this challenge and much more.</p><blockquote><p>Code examples are provided at the end of the article.</p></blockquote><h2>The challenge</h2><p>Picking up from where we left in <a href="https://www.fivestars.blog/articles/swiftui-hud/">Custom HUDs in SwiftUI</a>, the current limitation manifests as soon as we present something on top of the view associated with our HUD:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-windows/failure.gif" alt="a <code>sheet</code> obscures the toast"/><p>Instead, we want always to show our HUD on top of the screen, regardless of what's happening in our app:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-windows/success.gif"/><h2>App UI hierarchy</h2><p>Everything we see on apps is placed on top of <code>UIWindow</code>s. A window delivers touch events to the correct views and responds to other events such as (device) orientation changes.</p><p>With the introduction of multiple windows support for iPad (and Catalyst), <code>UIWindow</code>s are associated with and managed by <code>UIScene</code>s, which representing a UI instance of our app.</p><blockquote><p>We don't need to interact with either of them most of the time, especially with SwiftUI's new life cycle. However, even on SwiftUI apps, those components are still set up and used behind the curtain.</p></blockquote><p>Lastly, we use a <code>UISceneDelegate</code>/<code>UIWindowSceneDelegate</code> to respond to various events related to our <code>UIScene</code> (and associated <code>UISceneSession</code>).</p><p>While a <code>UIScene</code> is normally set up with one window, scenes support multiple windows.</p><h2>How to add extra windows to an app</h2><p>Two requirements:</p><ul><li>keep a strong reference to the window</li><li>provide the associated scene (a <code>UIWindowScene</code> instance)</li></ul><p>Scenes do manage <code>UIWindow</code>s, but we own the window life-cycle:<br>if only the scene holds a reference to the window, the window will be de-allocated (and its UI won't show in the app).</p><p>Since we get our app's <code>UIWindowScene</code> instances via our <code>UISceneDelegate</code>/<code>UIWindowSceneDelegate</code>, adding windows there is probably the easiest way to do so.</p><h3>The scene delegate</h3><p>If we refer to the scene delegate of an app using the UIKit life-cycle, we will see something similar to:</p><pre><code><span class="keyword">class</span> SceneDelegate: <span class="type">UIResponder</span>, <span class="type">UIWindowSceneDelegate</span> {
  <span class="keyword">var</span> window: <span class="type">UIWindow</span>?

  <span class="keyword">func</span> scene(
    <span class="keyword">_</span> scene: <span class="type">UIScene</span>, 
    willConnectTo session: <span class="type">UISceneSession</span>, 
    options connectionOptions: <span class="type">UIScene</span>.<span class="type">ConnectionOptions</span>
  ) {
    <span class="keyword">let</span> contentView = <span class="type">ContentView</span>()

    <span class="keyword">if let</span> windowScene = scene <span class="keyword">as</span>? <span class="type">UIWindowScene</span> {
      <span class="keyword">let</span> window = <span class="type">UIWindow</span>(windowScene: windowScene)
      window.<span class="property">rootViewController</span> = <span class="type">UIHostingController</span>(rootView: contentView)
      <span class="keyword">self</span>.<span class="property">window</span> = window
      window.<span class="call">makeKeyAndVisible</span>()
    }
  }

  ...
}
</code></pre><p>The scene delegate is responsible for setting up the app's main window in <code>scene(_:willConnectTo:options:)</code>.</p><p><code>SceneDelegate</code> meets the windows requirements mentioned earlier:</p><ul><li>it keeps a strong reference to the window via its <code>var window: UIWindow</code> property</li><li>it provides the associated scene during the window initialization via <code>let window = UIWindow(windowScene: windowScene)</code></li></ul><p>Because a scene supports multiple windows, we can add as many windows as we like, for example:</p><pre><code><span class="keyword">class</span> SceneDelegate: <span class="type">UIResponder</span>, <span class="type">UIWindowSceneDelegate</span> {
  <span class="keyword">var</span> keyWindow: <span class="type">UIWindow</span>?
  <span class="keyword">var</span> hudWindow: <span class="type">UIWindow</span>?

  <span class="keyword">func</span> scene(
    <span class="keyword">_</span> scene: <span class="type">UIScene</span>, 
    willConnectTo session: <span class="type">UISceneSession</span>, 
    options connectionOptions: <span class="type">UIScene</span>.<span class="type">ConnectionOptions</span>
  ) {
    <span class="keyword">if let</span> windowScene = scene <span class="keyword">as</span>? <span class="type">UIWindowScene</span> {
      <span class="call">setupKeyWindow</span>(in: windowScene)
      <span class="call">setupHUDWindow</span>(in: windowScene)
    }
  }

  <span class="keyword">func</span> setupKeyWindow(in scene: <span class="type">UIWindowScene</span>) {
    <span class="keyword">let</span> window = <span class="type">UIWindow</span>(windowScene: scene)
    window.<span class="property">rootViewController</span> = <span class="type">UIHostingController</span>(rootView: <span class="type">MainSceneView</span>())
    <span class="keyword">self</span>.<span class="property">keyWindow</span> = window
    window.<span class="call">makeKeyAndVisible</span>()
  }

  <span class="keyword">func</span> setupHUDWindow(in scene: <span class="type">UIWindowScene</span>) {
    <span class="keyword">let</span> hudWindow = <span class="type">UIWindow</span>(windowScene: scene)
    <span class="keyword">let</span> hudViewController = <span class="type">UIHostingController</span>(rootView: <span class="type">HUDSceneView</span>())
    hudViewController.<span class="property">view</span>.<span class="property">backgroundColor</span> = .<span class="dotAccess">clear</span>
    hudWindow.<span class="property">rootViewController</span> = hudViewController
    hudWindow.<span class="property">isHidden</span> = <span class="keyword">false
    self</span>.<span class="property">hudWindow</span> = hudWindow
  }
}
</code></pre><blockquote><p>For simplicity's sake, we will add windows right from the app launch. We can add them whenever we like in our apps, as long as we meet the two requirements above.</p></blockquote><p>Where:</p><ul><li>we set up the main window as before, named <code>keyWindow</code></li><li>we set up a secondary window, named <code>hudWindow</code></li></ul><p>The setup for this secondary (and any other) window doesn't match 1-1 the set up for the main window:</p><ul><li>we don't call <code>makeKeyAndVisible()</code> on the secondary window. There can be only one key window per scene, receiving keyboard and other non-touch-related events. Instead, we make sure that secondary windows are visible via <code>isHidden = false</code></li><li>we make the <code>UIHostingController</code>'s view background color transparent. Otherwise, the main window would be hidden by the secondary window.</li></ul><p>For apps using the SwiftUI life-cycle, everything we've seen so far still holds true, with a couple of differences:</p><ol><li>we must associate the app with a scene delegate, check out <a href="https://www.fivestars.blog/articles/app-delegate-scene-delegate-swiftui/">How to add an AppDelegate and a SceneDelegate to a SwiftUI app</a> on how to do so</li><li>we don't have to set up/manage the key window in our scene delegate: SwiftUI does that for us (on a private <code>SwiftUI.AppSceneDelegate</code>).</li></ol><h2>Windows hierarchy and behaviors</h2><p>With the current setup, we're able to layer as many windows as we like, with one caveat:<br>only the topmost window receives touches.</p><p>The scene delegate places its windows in a stack.<br>When the user interacts with the screen, touches are received by the topmost window in that stack.</p><blockquote><p>There could also be multiple stacks of windows if we have windows at different <a href="https://developer.apple.com/documentation/uikit/uiwindow/1621593-windowlevel">window levels</a>.</p></blockquote><p>This is because all windows are part of the app responder chain. Unless this behavior is what we need, we can change so by either sub-classing <code>UIWindow</code>, or setting the proper <code>UIWindow</code> properties in the scene delegate.</p><h3>Non-interactive window</h3><p>For example, we could make a window ignore touches by setting <code>isUserInteractionEnabled = false</code>:</p><pre><code><span class="comment">/// in our scene delegate:</span>

<span class="keyword">func</span> setupNonInteractiveWindow(in scene: <span class="type">UIWindowScene</span>) {
  <span class="keyword">let</span> nonInteractiveWindow = <span class="type">UIWindow</span>(windowScene: scene)

  <span class="keyword">let</span> controller = <span class="type">UIHostingController</span>(rootView: <span class="type">NonInteractiveView</span>())
  controller.<span class="property">view</span>.<span class="property">backgroundColor</span> = .<span class="dotAccess">clear</span>
  nonInteractiveWindow.<span class="property">rootViewController</span> = controller
  nonInteractiveWindow.<span class="property">isHidden</span> = <span class="keyword">false</span>
  nonInteractiveWindow.<span class="property">isUserInteractionEnabled</span> = <span class="keyword">false</span> <span class="comment">// 👈🏻</span>
  <span class="keyword">self</span>.<span class="property">nonInteractiveWindow</span> = secondWindow
}
</code></pre><p>This window would be used exclusively for visualization purposes, and all touches would be ignored and received by the next window in the stack.</p><h3>Pass through window</h3><p>Most of the time, we probably want our window to:</p><ul><li>handle user interactions when the interaction touches something shown in that window</li><li>pass the interaction to other windows otherwise</li></ul><p>At first, this seems particularly tricky when presenting SwiftUI views, as this <code>UIWindow</code> interactions management is only possible via UIKit's responder chain.</p><p>However, there is a way.</p><p>In both UIKit and SwiftUI, a view does not receive touches when any of the following conditions is true:</p><ul><li>the view opacity is less than <code>0.01</code> (<code>alpha = ...</code> in UIKit):</li></ul><pre><code><span class="type">Button</span>(<span class="string">"This button can be tapped"</span>) {
  <span class="comment">// ...</span>
}

<span class="type">Button</span>(<span class="string">"This button also can be tapped"</span>) {
  <span class="comment">// ...</span>
}
.<span class="call">opacity</span>(<span class="number">0.01</span>)

<span class="type">Button</span>(<span class="string">"This button *cannot* be tapped"</span>) {
  <span class="comment">// ...</span>
}
.<span class="call">opacity</span>(<span class="number">0.001</span>) <span class="comment">// 👈🏻</span>
</code></pre><ul><li>the view doesn't allow hit testing (<code>isUserInteractionEnabled = false</code> in UIKit):</li></ul><pre><code><span class="type">Button</span>(<span class="string">"This button cannot be tapped"</span>) {
  <span class="comment">// ...</span>
}
.<span class="call">allowsHitTesting</span>(<span class="keyword">false</span>)
</code></pre><ul><li>the view is hidden (<code>isHidden = true</code> in UIKit):</li></ul><pre><code><span class="type">Button</span>(<span class="string">"This button cannot be tapped"</span>) {
  <span class="comment">// ...</span>
}
.<span class="call">hidden</span>()
</code></pre><p>If no SwiftUI view responds to the user touch, then the containing <code>UIHostingController</code>'s (UI)view will.</p><p>With these two pieces of information, we can create a <code>UIWindow</code> subclass that handles touches only when anything but the <code>UIHostingController</code> (UI)view is hit:</p><pre><code><span class="keyword">class</span> PassThroughWindow: <span class="type">UIWindow</span> {
  <span class="keyword">override func</span> hitTest(<span class="keyword">_</span> point: <span class="type">CGPoint</span>, with event: <span class="type">UIEvent</span>?) -&gt; <span class="type">UIView</span>? {
    <span class="comment">// Get view from superclass.</span>
    <span class="keyword">guard let</span> hitView = <span class="keyword">super</span>.<span class="call">hitTest</span>(point, with: event) <span class="keyword">else</span> { <span class="keyword">return nil</span> }
    <span class="comment">// If the returned view is the `UIHostingController`'s view, ignore.</span>
    <span class="keyword">return</span> rootViewController?.<span class="property">view</span> == hitView ? nil : hitView
  }
}
</code></pre><p>And this is how we achieve the toast behavior seen at the beginning of the article.</p><img src="https://www.fivestars.blog/assets/posts/swiftui-windows/success.gif"/><h2>Sharing state between windows</h2><blockquote><p>Recommended read: <a href="https://www.fivestars.blog/articles/app-state/">App-wide state in SwiftUI</a>.</p></blockquote><p>From SwiftUI point of view, different windows could be as well as different apps:<br>they do not share the environment or any other state.</p><p>However, at least with UIKit's life-cycle, they have a common starting point: the scene delegate.</p><p>We could define a shared state in this delegate, that is then injected to the relevant windows, for example:</p><pre><code><span class="keyword">class</span> SharedState: <span class="type">ObservableObject</span> {
  ...
}

<span class="keyword">final class</span> SceneDelegate: <span class="type">UIResponder</span>, <span class="type">UIWindowSceneDelegate</span> {
  <span class="keyword">lazy var</span> sharedState = <span class="type">SharedState</span>()
  
  <span class="keyword">var</span> keyWindow: <span class="type">UIWindow</span>?
  <span class="keyword">var</span> secondaryWindow: <span class="type">UIWindow</span>?

  <span class="keyword">func</span> scene(
    <span class="keyword">_</span> scene: <span class="type">UIScene</span>, 
    willConnectTo session: <span class="type">UISceneSession</span>, 
    options connectionOptions: <span class="type">UIScene</span>.<span class="type">ConnectionOptions</span>
  ) {
    <span class="keyword">if let</span> windowScene = scene <span class="keyword">as</span>? <span class="type">UIWindowScene</span> {
      <span class="call">setupKeyWindow</span>(in: windowScene)
      <span class="call">setupSecondaryWindow</span>(in: windowScene)
    }
  }

  <span class="keyword">func</span> setupKeyWindow(in scene: <span class="type">UIWindowScene</span>) {
    <span class="keyword">let</span> window = <span class="type">UIWindow</span>(windowScene: scene)
    window.<span class="property">rootViewController</span> = <span class="type">UIHostingController</span>(
      rootView: <span class="type">MainSceneView</span>().<span class="call">environmentObject</span>(sharedState) <span class="comment">// 👈🏻 shared state</span>
    )
    <span class="keyword">self</span>.<span class="property">window</span> = window
    window.<span class="call">makeKeyAndVisible</span>()
  }

  <span class="keyword">func</span> setupSecondaryWindow(in scene: <span class="type">UIWindowScene</span>) {
    <span class="keyword">let</span> secondaryViewController = <span class="type">UIHostingController</span>(
      rootView: <span class="type">SecondarySceneView</span>().<span class="call">environmentObject</span>(sharedState) <span class="comment">// 👈🏻 shared state</span>
    )
    secondaryViewController.<span class="property">view</span>.<span class="property">backgroundColor</span> = .<span class="dotAccess">clear</span>

    <span class="keyword">let</span> secondaryWindow = <span class="type">PassThroughWindow</span>(windowScene: scene)
    secondaryWindow.<span class="property">rootViewController</span> = secondaryViewController
    secondaryWindow.<span class="property">isHidden</span> = <span class="keyword">false
    self</span>.<span class="property">secondaryWindow</span> = secondaryWindow
  }
}
</code></pre><p>Regardless of where changes come from, any change in this shared state will be observed by all windows.</p><p>In an app using the SwiftUI life-cycle, things are slightly trickier, as we don't have this common starting point, instead:</p><ol><li>the <code>App</code> will hold the shared state, which is then injected into the main view</li><li>the main view will inject the shared state to our scene delegate</li><li>once injected in the scene delegate, the secondary windows are created, and the state is injected in their views</li></ol><p>Both examples with UIKit and SwiftUI life-cycle are provided <a href="https://github.com/FiveStarsBlog/CodeSamples/tree/main/Windows/">here</a>.</p><h2>Conclusions</h2><p>Layering multiple windows is not something we do very often nowadays. However, it's a need quickly justified even on simple apps (see HUD example above).</p><p>As this nearly 1500-words article has shown us, SwiftUI window support is far from ready (FB9018136), but, fortunately, it is achievable by going back to UIKit and work with some knowledge on both UI frameworks.</p><p>Do you use multiple windows in your apps? What are your use cases? Please let me know via <a href="mailto:hello@fivestars.blog">email</a> or <a href="https://twitter.com/zntfdr">Twitter</a>.</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/onsubmit</guid><title>SwiftUI's new onSubmit modifier</title><description>A dive into the new search and textfield submission pattern</description><link>https://www.fivestars.blog/articles/onsubmit</link><pubDate>Tue, 3 Aug 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>This year's SwiftUI flagship feature is no doubt the ability to <a href="https://www.wwdcnotes.com/notes/wwdc21/10023/">direct and reflect focus</a>.</p><p>As part of this great and heavily requested enhancement, views such as <a href="https://developer.apple.com/documentation/swiftui/textfield"><code>TextField</code></a> had to adapt their APIs. Let's dive in.</p><h2>The shift</h2><p>Prior to this year's WWDC, <code>TextField</code> initializers followed this pattern:</p><pre><code><span class="keyword">init</span>(
  <span class="keyword">_</span> titleKey: <span class="type">LocalizedStringKey</span>, 
  text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;, 
  onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }, 
  onCommit: <span class="keyword">@escaping</span> () -&gt; <span class="type">Void</span> = {}
)
</code></pre><p>Where:</p><ul><li>the title was used both as a description and as a placeholder for the view</li><li><code>text</code> is the main <code>TextField</code> value</li><li><code>onEditingChanged</code> was called when the <code>TextField</code> gained or lost the focus (the <code>Bool</code> parameter was <code>true</code> when the field gained the focus, <code>false</code> when it lost the focus)</li><li><code>onCommit</code> triggered when the user submitted (e.g., hit the Return key) while on focus on this specific field</li></ul><p>New this year, all former initializers have been deprecated and replaced by new ones:</p><pre><code><span class="keyword">init</span>(
  <span class="keyword">_</span> titleKey: <span class="type">LocalizedStringKey</span>, 
  text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;, 
  prompt: <span class="type">Text</span>? = <span class="keyword">nil</span>
)
</code></pre><p>Where:</p><ul><li>the title describes the view</li><li>the <code>text</code> is unchanged</li><li>the <code>prompt</code> is used as the field placeholder</li></ul><p>To summarize, we have...</p><ul><li>...gained one parameter, <code>prompt</code>, to disambiguate between the view description and placeholder</li><li>...lost two parameters, <code>onEditingChanged</code> has been replaced with the new focus API, <code>onCommit</code> has been replaced by the new <code>onSubmit</code> modifier</li></ul><p>Let's explore <code>onSubmit</code> next.</p><blockquote><p>As of Xcode 13b4, the new focus APIs are not yet ready (FB9432618), and the <code>TextField</code> documentation still uses the deprecated initializers (FB9438780).</p></blockquote><h2>onSubmit definition</h2><blockquote><p>For an overview on the new <code>onSubmit</code> modifier and more, check out <a href="https://swiftwithmajid.com/2021/07/21/submitting-values-to-swiftui-view/">Submitting values to SwiftUI view</a> by <a href="https://twitter.com/mecid/">Majid Jabrayilov</a>.</p></blockquote><p>There's only one definition for this modifier:</p><pre><code><span class="keyword">public func</span> onSubmit(
  of triggers: <span class="type">SubmitTriggers</span> = .<span class="dotAccess">text</span>, 
  <span class="keyword">_</span> action: <span class="keyword">@escaping</span> (() -&gt; <span class="type">Void</span>)
) -&gt; <span class="keyword">some</span> <span class="type">View</span>
</code></pre><p>The optional <code>triggers</code> parameter lets us define what triggers the given closure:<br>the current options are either <code>.text</code> (for <code>TextField</code>s and <code>SecureFields</code>) or <code>.search</code> for search submissions via the new <a href="https://www.wwdcnotes.com/notes/wwdc21/10176/"><code>searchable(text:placement:)</code></a> modifier.</p><pre><code><span class="comment">// Triggers when text fields submit:</span>

<span class="type">Form</span> {
  <span class="type">TextField</span>(...)
  <span class="type">TextField</span>(...)
}
.<span class="call">onSubmit</span> {
  <span class="call">print</span>(<span class="string">"Form submitted"</span>)
}

<span class="comment">// Triggers when search submits:</span>

<span class="type">NavigationView</span> {
  <span class="type">List</span> {
    ...
  }
  .<span class="call">searchable</span>(text: $text)
  .<span class="call">onSubmit</span>(of: .<span class="dotAccess">search</span>) {
    <span class="call">print</span>(<span class="string">"Search submitted"</span>)
  }
}
</code></pre><p><code>SubmitTriggers</code> is an option set: meaning that, theoretically, we could have a single, "catch-all" <code>onSubmit</code> modifier:</p><pre><code>.<span class="call">onSubmit</span>(of: [.<span class="dotAccess">text</span>, .<span class="dotAccess">search</span>]) {
  <span class="call">print</span>(<span class="string">"Something has been submitted"</span>)
}
</code></pre><p>Unless we have an unique situation where this is needed, it's preferred to separate the two cases with two different submission closures:</p><pre><code><span class="type">NavigationView</span> {
  <span class="type">Form</span> {
    <span class="type">TextField</span>(...)
    <span class="type">TextField</span>(...)
  }
  .<span class="call">onSubmit</span> {
    <span class="call">print</span>(<span class="string">"Form submitted"</span>)
  }
  .<span class="call">searchable</span>(text: $text)
  .<span class="call">onSubmit</span>(of: .<span class="dotAccess">search</span>) {
    <span class="call">print</span>(<span class="string">"Search submitted"</span>)
  }
}
</code></pre><h2>onSubmit environment</h2><p>Behind the scenes, <code>onSubmit</code> adds a <code>TriggerSubmitAction</code> value into the environment. Understanding this is very important: the position and location of the view modifier are fundamental.</p><p>For example, this search will trigger the <code>onSubmit</code> closure:</p><pre><code><span class="type">FSView</span>()
 .<span class="call">searchable</span>(text: $text)
 .<span class="call">onSubmit</span>(of: .<span class="dotAccess">search</span>) {
   <span class="call">print</span>(<span class="string">"Search submitted"</span>)
 }
</code></pre><p>While this one won't:</p><pre><code><span class="type">FSView</span>()
 .<span class="call">onSubmit</span>(of: .<span class="dotAccess">search</span>) {
   <span class="call">print</span>(<span class="string">"Search submitted"</span>)
 }
 .<span class="call">searchable</span>(text: $text)
</code></pre><p>The explanation lays on the environment:</p><ul><li>in the first example, <code>searchable(...)</code> receives the environment given by <code>onSubmit</code>, which includes the <code>onSubmit</code> closure</li><li>in the latter example, the <code>onSubmit</code> modifier is applied after/underneath the <code>searchable(...)</code> modifier. In this case, the <code>onSubmit</code> closure is not part of <code>searchable(...)</code>'s environment, thus never triggering it</li></ul><p>Keeping this in mind, it should be clear what the next example does:</p><pre><code><span class="type">Form</span> {
  <span class="type">TextField</span>(<span class="string">"Username"</span>, text: $username)
    .<span class="call">onSubmit</span> {
      ...
    }

  <span class="type">SecureField</span>(<span class="string">"Password"</span>, text: $password)
}
</code></pre><p>Here <code>onSubmit</code> triggers only when the user submits on the <code>TextField</code>, but not on the <code>SecureField</code>.</p><p>To avoid confusion, it's recommended to apply <code>onSubmit</code> on the container (the <code>Form</code> in this example) instead.</p><p>This showcases a SwiftUI characteristic that we've explored <a href="https://www.fivestars.blog/articles/preferencekey-reduce/">over</a> and <a href="https://www.fivestars.blog/articles/swiftui-hierarchy-list/">over</a>: SwiftUI never tries to be <em>clever</em>, it always behaves as intended, by doing as little work as possible, for performance reasons.</p><blockquote><p>Unfortunately, this environment value is not exposed to third party developers: the submit action cannot be triggered programmatically (FB9429770).</p></blockquote><h2>Cumulative</h2><p>Similarly to the new <a href="https://www.fivestars.blog/articles/safe-area-insets/"><code>safeAreaInset(edge:alignment:spacing:content:)</code></a> modifier, <code>onSubmit</code> is also cumulative:<br>if we apply multiple <code>onSubmit</code> modifiers, their closures will all trigger, according to their <code>SubmitTriggers</code>, from outermost to innermost.</p><p>For example:</p><pre><code><span class="type">Form</span> {
  <span class="type">TextField</span>(...)
  <span class="type">TextField</span>(...)
  ...
}
.<span class="call">onSubmit</span> {
  <span class="call">print</span>(<span class="string">"This will trigger last"</span>)
}
.<span class="call">onSubmit</span> {
  <span class="call">print</span>(<span class="string">"This will trigger second"</span>)
}
.<span class="call">onSubmit</span> {
  <span class="call">print</span>(<span class="string">"This will trigger first"</span>)
}
</code></pre><h2>EnvironmentValues and closures</h2><p>SwiftUI already had view modifiers accepting closures before (<code>onAppear</code> for example), however, <code>onSubmit</code> might be the first case where the closure is injected into the environment.</p><p>Let's recreate (a simplified version of) the <code>onSubmit</code> modifier ourselves, which can also help us back port the new behavior to older OSes.</p><p>First, we need an <code>EnvironmentKey</code>:</p><pre><code><span class="keyword">struct</span> TriggerSubmitKey: <span class="type">EnvironmentKey</span> {
  <span class="keyword">static let</span> defaultValue: () -&gt; <span class="type">Void</span> = {}
}
</code></pre><p>This definition contains the default value of our closure, which is empty.<br>Alternatively, we could make the definition optional, <code>static let defaultValue: (() -&gt; Void)? = nil</code>, we will use the non-optional variant for simplicity's sake.</p><p>Next, we will define the <code>onSubmitAction</code> environment value. As we want to mock SwiftUI's behavior, we will make our closures cumulative and triggering from outermost to innermost:</p><pre><code><span class="keyword">extension</span> <span class="type">EnvironmentValues</span> {
  <span class="keyword">public var</span> onSubmitAction: () -&gt; <span class="type">Void</span> {
    <span class="keyword">get</span> {
      <span class="keyword">self</span>[<span class="type">TriggerSubmitKey</span>.<span class="keyword">self</span>]
    } <span class="keyword">set</span> {
      <span class="keyword">let</span> oldValue = <span class="keyword">self</span>[<span class="type">TriggerSubmitKey</span>.<span class="keyword">self</span>]
      <span class="keyword">self</span>[<span class="type">TriggerSubmitKey</span>.<span class="keyword">self</span>] = {
        <span class="call">oldValue</span>()
        <span class="call">newValue</span>()
      }
    }
  }
}
</code></pre><p>The "magic" happens on the <code>onSubmitAction</code> setter, where we make sure to always trigger both the "old" closure first, and then the new one.</p><p>Lastly, we need a new modifier definition, injecting the closure into the environment:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">func</span> onSubmit(<span class="keyword">_</span> action: <span class="keyword">@escaping</span> (() -&gt; <span class="type">Void</span>)) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">environment</span>(\.<span class="property">onSubmitAction</span>, action)
  }
}
</code></pre><p>With this, we're now all set to start reading and using our new environment value, for example we could define a new text field or a button that trigger the environment action:</p><pre><code><span class="keyword">struct</span> SubmitButton&lt;Label: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">onSubmitAction</span>) <span class="keyword">private var</span> onSubmitAction
  <span class="keyword">@ViewBuilder var</span> label: <span class="type">Label</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(action: onSubmitAction) {
      label
    }
  }
}

<span class="keyword">struct</span> SubmitTextField: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">onSubmitAction</span>) <span class="keyword">private var</span> onSubmitAction
  <span class="keyword">let</span> title: <span class="type">LocalizedStringKey</span>
  <span class="keyword">@Binding var</span> text: <span class="type">String</span>

  <span class="keyword">init</span>(<span class="keyword">_</span> title: <span class="type">LocalizedStringKey</span>, text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;) {
    <span class="keyword">self</span>.<span class="property">title</span> = title
    <span class="keyword">self</span>.<span class="property">_text</span> = text
  }

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TextField</span>(title, text: $text, onCommit: onSubmitAction) <span class="comment">// 👈🏻 Uses the iOS 13/14 API</span>
  }
}
</code></pre><p>Here's an example on how we could use them:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> text = <span class="string">""</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Form</span> {
      <span class="type">SubmitTextField</span>(<span class="string">"Name"</span>, text: $text)
      <span class="type">SubmitButton</span> {
        <span class="type">Text</span>(<span class="string">"Submit form"</span>)
      }
    }
    .<span class="call">onSubmit</span> {
      <span class="call">print</span>(<span class="string">"Form Submitted"</span>)
    }
  }
}
</code></pre><p>In this example we can submit either when:</p><ul><li>we commit on the text field</li><li>we tap on the button</li></ul><p>This is possible because both views, <code>SubmitTextField</code> and <code>SubmitButton</code>, have access to the <code>onSubmitAction</code> environment value. We cannot do this directly with SwiftUI's definition, because SwiftUI's <code>TriggerSubmitAction</code> environment value is not exposed to third party developers, FB9429770.</p><h2>Conclusions</h2><p>This year's changes on both view focus and submission are two very welcome quality of life improvements that will allow SwiftUI apps to create new flows that were not possible before (without bridging back to legacy frameworks).</p><p>What's your favorite change from this year? Let me know let me know via <a href="mailto:hello@fivestars.blog">email</a> or <a href="https://twitter.com/zntfdr">Twitter</a>!</p><blockquote><p>This article is part of a series exploring new SwiftUI features. We will cover many more during the rest of the summer: subscribe to Five Stars's <a href="https://www.fivestars.blog/feed.rss">feed RSS</a> or follow <a href="http://twitter.com/fiveStarsBlog">@FiveStarsBlog</a> on Twitter to never miss new content!</p></blockquote>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/xcode-13-beta-4</guid><title>What's new in Xcode 13 beta 4</title><description>Button and AttributeString style updates, &amp; more!</description><link>https://www.fivestars.blog/articles/xcode-13-beta-4</link><pubDate>Wed, 28 Jul 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Xcode 13 beta 4 has just been released: let's have a look at what's new in SwiftUI!</p><h2>Bordered <code>Button</code> style updates</h2><img src="https://www.fivestars.blog/assets/posts/xcode-13-beta-4/btn-1.png"/><p>A new <code>buttonBorderShape(_:)</code> view modifier has been introduced, replacing the previous <code>BorderedButtonStyle</code> <code>shape</code> parameter:</p><p>From:</p><pre><code><span class="type">Button</span>(<span class="string">"Tap me"</span>) {}
  .<span class="call">buttonStyle</span>(<span class="type">BorderedButtonStyle</span>(shape: .<span class="dotAccess">roundedRectangle</span>))

<span class="type">Button</span>(<span class="string">"Tap me"</span>) {}
  .<span class="call">buttonStyle</span>(<span class="type">BorderedButtonStyle</span>(shape: .<span class="dotAccess">capsule</span>))
</code></pre><p>To:</p><pre><code><span class="type">Button</span>(<span class="string">"Tap me"</span>) {}
  .<span class="call">buttonStyle</span>(.<span class="dotAccess">bordered</span>)
  .<span class="call">buttonBorderShape</span>(.<span class="dotAccess">roundedRectangle</span>)

<span class="type">Button</span>(<span class="string">"Tap me"</span>) {}
  .<span class="call">buttonStyle</span>(.<span class="dotAccess">bordered</span>)
  .<span class="call">buttonBorderShape</span>(.<span class="dotAccess">capsule</span>)
</code></pre><img src="https://www.fivestars.blog/assets/posts/xcode-13-beta-4/btn-2.png"/><p>The new <code>buttonBorderShape(_:)</code> parameter type, <code>ButtonBorderShape</code>, comes with a new <code>roundedRectangle(radius: CGFloat)</code> option, meaning that we can now customize the button radius:</p><pre><code><span class="type">ForEach</span>(<span class="number">1</span>..&lt;<span class="number">6</span>) { index <span class="keyword">in</span>
  <span class="type">Button</span>(<span class="string">"Tap me"</span>) {}
    .<span class="call">buttonStyle</span>(.<span class="dotAccess">bordered</span>)
    .<span class="call">buttonBorderShape</span>(.<span class="call">roundedRectangle</span>(radius: <span class="number">4</span> * <span class="type">Double</span>(index)))
}
</code></pre><blockquote><p>As of Xcode 13b4, there's no public API to read the environment <code>BorderedButtonStyle</code> value, FB9413086.</p></blockquote><h2>Control prominence updates</h2><img src="https://www.fivestars.blog/assets/posts/xcode-13-beta-4/bordered-prominent.png"/><p>The <code>controlProminence(_:)</code> modifier has been deprecated in favor of a new <code>.borderedProminent</code> button style.</p><p>From:</p><pre><code><span class="type">Button</span>(<span class="string">"Tap me"</span>) {}
  .<span class="call">buttonStyle</span>(.<span class="dotAccess">bordered</span>)
  .<span class="call">controlProminence</span>(.<span class="dotAccess">increased</span>)
</code></pre><p>To:</p><pre><code><span class="type">Button</span>(<span class="string">"Tap me"</span>) {}
  .<span class="call">buttonStyle</span>(.<span class="dotAccess">borderedProminent</span>)
</code></pre><p><code>Prominence</code> is still used used with for (<code>List</code>'s) <code>headerProminence(_:)</code> modifier.</p><blockquote><p><code>controlProminence</code> environment value has not yet been deprecated, FB9413125.</p></blockquote><h2>New strikethrough and underline <code>AttributedString</code> styles</h2><img src="https://www.fivestars.blog/assets/posts/xcode-13-beta-4/attr.png"/><p><code>AttributeString</code>s can now further customize the strikethrough and underline lines with a new <code>Text.LineStyle.Pattern</code> type, here are all the new styles:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">HStack</span> {
      <span class="type">VStack</span> {
        <span class="type">Text</span>(<span class="call">strikethroughAttributedString</span>(pattern: .<span class="dotAccess">solid</span>))
        <span class="type">Text</span>(<span class="call">strikethroughAttributedString</span>(pattern: .<span class="dotAccess">dot</span>))
        <span class="type">Text</span>(<span class="call">strikethroughAttributedString</span>(pattern: .<span class="dotAccess">dash</span>))
        <span class="type">Text</span>(<span class="call">strikethroughAttributedString</span>(pattern: .<span class="dotAccess">dashDot</span>))
        <span class="type">Text</span>(<span class="call">strikethroughAttributedString</span>(pattern: .<span class="dotAccess">dashDotDot</span>))
      }

      <span class="type">VStack</span> {
        <span class="type">Text</span>(<span class="call">underlineAttributedString</span>(pattern: .<span class="dotAccess">solid</span>))
        <span class="type">Text</span>(<span class="call">underlineAttributedString</span>(pattern: .<span class="dotAccess">dot</span>))
        <span class="type">Text</span>(<span class="call">underlineAttributedString</span>(pattern: .<span class="dotAccess">dash</span>))
        <span class="type">Text</span>(<span class="call">underlineAttributedString</span>(pattern: .<span class="dotAccess">dashDot</span>))
        <span class="type">Text</span>(<span class="call">underlineAttributedString</span>(pattern: .<span class="dotAccess">dashDotDot</span>))
      }
    }
  }

  <span class="keyword">private func</span> strikethroughAttributedString(
    pattern: <span class="type">Text</span>.<span class="type">LineStyle</span>.<span class="type">Pattern</span>
  ) -&gt; <span class="type">AttributedString</span> {
    <span class="keyword">var</span> attributedString = <span class="type">AttributedString</span>(<span class="string">"Strikethrough"</span>)
    attributedString.<span class="property">strikethroughStyle</span> = .<span class="keyword">init</span>(pattern: pattern, color: .<span class="dotAccess">red</span>)
    <span class="keyword">return</span> attributedString
  }

  <span class="keyword">func</span> underlineAttributedString(
    pattern: <span class="type">Text</span>.<span class="type">LineStyle</span>.<span class="type">Pattern</span>
  ) -&gt; <span class="type">AttributedString</span> {
    <span class="keyword">var</span> attributedString = <span class="type">AttributedString</span>(<span class="string">"Underline"</span>)
    attributedString.<span class="property">underlineStyle</span> = .<span class="keyword">init</span>(pattern: pattern, color: .<span class="dotAccess">red</span>)
    <span class="keyword">return</span> attributedString
  }
}
</code></pre><blockquote><p>With this update, <code>AttributedString</code>'s <code>strikethroughColor</code> and <code>underlineColor</code> have been deprecated.</p></blockquote><h2>Text field auto capitalization gains a full-SwiftUI modifier</h2><p>SwiftUI's <code>autocapitalization(_:)</code> is now deprecated in favor of a new <code>textInputAutocapitalization(_:)</code> modifier.</p><p>From:</p><pre><code><span class="type">TextField</span>(<span class="string">"All characters auto capitalization"</span>, text: $text)
  .<span class="call">autocapitalization</span>(.<span class="dotAccess">allCharacters</span>)
</code></pre><p>To:</p><pre><code><span class="type">TextField</span>(<span class="string">"All characters auto capitalization"</span>, text: $text)
  .<span class="call">textInputAutocapitalization</span>(.<span class="dotAccess">characters</span>)
</code></pre><p>The new modifier uses a new <code>TextInputAutocapitalization</code> SwiftUI struct:</p><pre><code><span class="keyword">struct</span> TextInputAutocapitalization {
  <span class="comment">/// Defines an autocapitalizing behavior that will not capitalize anything.</span>
  <span class="keyword">public static var</span> never: <span class="type">TextInputAutocapitalization</span> { <span class="keyword">get</span> }

  <span class="comment">/// Defines an autocapitalizing behavior that will capitalize the first
  /// letter of every word.</span>
  <span class="keyword">public static var</span> words: <span class="type">TextInputAutocapitalization</span> { <span class="keyword">get</span> }

  <span class="comment">/// Defines an autocapitalizing behavior that will capitalize the first
  /// letter in every sentence.</span>
  <span class="keyword">public static var</span> sentences: <span class="type">TextInputAutocapitalization</span> { <span class="keyword">get</span> }

  <span class="comment">/// Defines an autocapitalizing behavior that will capitalize every letter.</span>
  <span class="keyword">public static var</span> characters: <span class="type">TextInputAutocapitalization</span> { <span class="keyword">get</span> }
}
</code></pre><p>While the deprecated modifier used UIKit's <code>UITextAutocapitalizationType</code>:</p><pre><code><span class="keyword">public enum</span> UITextAutocapitalizationType : <span class="type">Int</span> {
  <span class="keyword">case</span> none = <span class="number">0</span>
  <span class="keyword">case</span> words = <span class="number">1</span>
  <span class="keyword">case</span> sentences = <span class="number">2</span>
  <span class="keyword">case</span> allCharacters = <span class="number">3</span>
}
</code></pre><h2>Documentation</h2><p>We have a lot more documentation for:</p><ul><li><a href="https://developer.apple.com/documentation/swiftui/image/task(_:)"><code>Task</code> view modifiers</a></li><li><code>AccessibilityChildBehavior</code> properties (<a href="https://developer.apple.com/documentation/swiftui/accessibilitychildbehavior/combine"><code>combine</code></a>, <a href="https://developer.apple.com/documentation/swiftui/accessibilitychildbehavior/ignore"><code>ignore</code></a>, <a href="https://developer.apple.com/documentation/swiftui/accessibilitychildbehavior"><code>contain</code></a>)</li></ul><h2>Conclusions</h2><p>Did you find anything else interesting in Xcode 13 beta 4? Please <a href="https://twitter.com/zntfdr">let me know</a>!</p><p>As always, please <a href="https://feedbackassistant.apple.com">report any issue to Apple</a>: we're still months away from the official release, things can and will be fixed, as long as we report them.</p><blockquote><p>This article is part of a series exploring new SwiftUI features. We will cover many more during the rest of the summer: subscribe to Five Stars's <a href="https://www.fivestars.blog/feed.rss">feed RSS</a> or follow <a href="http://twitter.com/fiveStarsBlog">@FiveStarsBlog</a> on Twitter to never miss new content!</p></blockquote>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/alwaysEmitIntoClient</guid><title>Backporting new features with @_alwaysEmitIntoClient</title><description>An exploration of the private @_alwaysEmitIntoClient attribute</description><link>https://www.fivestars.blog/articles/alwaysEmitIntoClient</link><pubDate>Tue, 27 Jul 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>In <a href="https://www.fivestars.blog/articles/swiftui-patterns-view-builders/">SwiftUI patterns evolution: view builders</a>, we've covered a new pattern introduced in Xcode 13. At the end of the article, we've seen how <em>relatively simple</em> it was to backport such patterns via extensions.</p><p>New in <a href="https://www.fivestars.blog/articles/xcode-13-beta-3/">Xcode 13 beta 3</a>, most of those extensions are no longer needed: the SwiftUI team back-ported this pattern for us. How is it possible? Let's find out.</p><h2>The change</h2><p>Prior to Xcode 13 beta 3, the new pattern was limited to the newly announced OSes, for example:</p><pre><code><span class="keyword">@available</span>(iOS <span class="number">15.0</span>, macOS <span class="number">12.0</span>, tvOS <span class="number">15.0</span>, watchOS <span class="number">8.0</span>, *) <span class="comment">// 👈🏻</span>
<span class="keyword">extension</span> <span class="type">Section</span> <span class="keyword">where</span> <span class="type">Parent</span>: <span class="type">View</span>, <span class="type">Content</span>: <span class="type">View</span>, <span class="type">Footer</span>: <span class="type">View</span> {
  <span class="keyword">public init</span>(
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>, 
    <span class="keyword">@ViewBuilder</span> header: () -&gt; <span class="type">Parent</span>, 
    <span class="keyword">@ViewBuilder</span> footer: () -&gt; <span class="type">Footer</span>
  )
}
</code></pre><p>New in Xcode 13 beta 3, those definitions are now available to all OSes supporting SwiftUI:</p><pre><code><span class="keyword">@available</span>(iOS <span class="number">13.0</span>, macOS <span class="number">10.15</span>, tvOS <span class="number">13.0</span>, watchOS <span class="number">6.0</span>, *) <span class="comment">// 👈🏻</span>
<span class="keyword">extension</span> <span class="type">Section</span> <span class="keyword">where</span> <span class="type">Parent</span>: <span class="type">View</span>, <span class="type">Content</span>: <span class="type">View</span>, <span class="type">Footer</span>: <span class="type">View</span> {
  <span class="keyword">public init</span>(
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>, 
    <span class="keyword">@ViewBuilder</span> header: () -&gt; <span class="type">Parent</span>, 
    <span class="keyword">@ViewBuilder</span> footer: () -&gt; <span class="type">Footer</span>
  )
}
</code></pre><p>Older OSes did not magically learn the new pattern: once the OS is shipped, the system framework capabilities for that OS are sealed and cannot be changed (strictly speaking, this is <em>mostly</em> true).</p><p>This is all we get from SwiftUI's headers. Let's dig deeper.</p><h2>Swift Interfaces and ABI stability</h2><p>A core part of <a href="https://swift.org/blog/abi-stability-and-more/">Swift's ABI stability</a> is <a href="https://forums.swift.org/t/plan-for-module-stability/14551">Module Stability</a>, which lets apps use libraries/frameworks even when compiled with different Swift versions.</p><p>On top of Module Stability, we have <a href="https://swift.org/blog/library-evolution/">Library Evolution</a>, which lets libraries change while remaining binary-compatible with previous versions.</p><p>For app developers, all of this comes down to library Swift interfaces, also known as <code>.swiftinterface</code> file(s), and what's declared on them.</p><h3>System frameworks Swift interfaces</h3><p>Xcode always comes with Apple's OSes framework binaries, including SwiftUI's, which also contains the <code>.swiftinterface</code> files mentioned above.</p><blockquote><p>The location might vary, currently it's <code>Xcode.app/Contents/Developer/Platforms/YOUR_PLATFORM_HERE.platform/Developer/SDKs/YOUR_PLATFORM_HERE.sdk/System/Library/Frameworks/</code></p></blockquote><p>Continuing with SwiftUI's <code>Section</code> example, prior to Xcode 13 beta 3, the Swift interface declared:</p><pre><code><span class="keyword">@available</span>(iOS <span class="number">15.0</span>, macOS <span class="number">12.0</span>, tvOS <span class="number">15.0</span>, watchOS <span class="number">8.0</span>, *)
<span class="keyword">extension</span> <span class="type">Section</span> <span class="keyword">where</span> <span class="type">Parent</span>: <span class="type">View</span>, <span class="type">Content</span>: <span class="type">View</span>, <span class="type">Footer</span>: <span class="type">View</span> {
  <span class="keyword">public init</span>(
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>, 
    <span class="keyword">@ViewBuilder</span> header: () -&gt; <span class="type">Parent</span>, 
    <span class="keyword">@ViewBuilder</span> footer: () -&gt; <span class="type">Footer</span>
  )
}
</code></pre><p>Which translates 1-1 to what we see in SwiftUI's headers.</p><p>From Xcode 13 beta 3, the previous definition becomes:</p><pre><code><span class="keyword">@available</span>(iOS <span class="number">13.0</span>, macOS <span class="number">10.15</span>, tvOS <span class="number">13.0</span>, watchOS <span class="number">6.0</span>, *)
<span class="keyword">extension</span> <span class="type">Section</span> <span class="keyword">where</span> <span class="type">Parent</span>: <span class="type">View</span>, <span class="type">Content</span>: <span class="type">View</span>, <span class="type">Footer</span>: <span class="type">View</span> {
  <span class="keyword">@_alwaysEmitIntoClient
  public init</span>(
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>, 
    <span class="keyword">@ViewBuilder</span> header: () -&gt; <span class="type">Parent</span>, 
    <span class="keyword">@ViewBuilder</span> footer: () -&gt; <span class="type">Footer</span>
  ) {
    <span class="keyword">self</span>.<span class="keyword">init</span>(header: <span class="call">header</span>(), footer: <span class="call">footer</span>(), content: content)
  }
}
</code></pre><p>We have two significant changes, the initializer...</p><ul><li>... comes with its complete implementation</li><li>... is associated with the <code>@_alwaysEmitIntoClient</code> attribute</li></ul><p>Going back to Swift's Module Stability and Library Evolution, this definition is possible because it's entirely additive:</p><ul><li>the new initializer is a <em>convenience</em> initializer, using another <a href="https://developer.apple.com/documentation/swiftui/section/init(header:footer:content:)"><code>public</code> initializer</a>, which has been available since iOS 13 (and equivalent in other platforms)</li><li>since the interface exposes the complete implementation, we can now use this new API even in older OSes</li></ul><p>All of this is possible thanks to the <code>@_alwaysEmitIntoClient</code> attribute.</p><h2><code>@_alwaysEmitIntoClient</code></h2><blockquote><p>The underscore prefix means that this attribute hasn't gone through Swift Evolution just yet.</p></blockquote><p>From what we know so far, it looks like <code>@_alwaysEmitIntoClient</code> is similar to <code>@inlinable</code>, which also exposes the associated implementation as part of the module’s public interface (regardless of whether the declaration is <code>public</code> or <code>internal</code>).</p><p>There are two major differences between <code>@_alwaysEmitIntoClient</code> and <code>@inlinable</code>:</p><ul><li>an <code>@inlinable</code> declaration is part of the library binary. Removing an <code>@inlinable</code> symbol is both a binary-breaking and a source-breaking change</li><li>the compiler is allowed to replace calls to an <code>@inlinable</code> symbol with a copy of the symbol’s implementation at call site</li></ul><p>On the contrary:</p><ul><li>an <code>@_alwaysEmitIntoClient</code> declaration is never part of a library binary. Removing a <code>@_alwaysEmitIntoClient</code> symbol is a binary-compatible and source-breaking change</li><li>an <code>@_alwaysEmitIntoClient</code> implementation will always get copied into the client (e.g., an app) binary</li></ul><p>This last point explains why using <code>@_alwaysEmitIntoClient</code> allows back-porting new definitions to older OSes:<br>it's not the framework that has the new capabilities. It's the app that brings them within its binary.</p><p>There are a few drawbacks with this approach:</p><ul><li><code>@_alwaysEmitIntoClient</code> code that’s compiled into apps cannot be improved/fixed via OS updates. The only way to update those is for the app to be rebuilt with a newer <code>@_alwaysEmitIntoClient</code> implementation</li><li><code>@_alwaysEmitIntoClient</code> brings unnecessary code size bloat, as these implementations, again, are never part of the library binary and must come within the app binaries using them, even on future OSes</li><li><code>@_alwaysEmitIntoClient</code> does not work with new types</li></ul><p>It's up to the library maintainers to decide whether the trade-off is worth it.</p><blockquote><p>Beside for view builders, among others, SwiftUI also uses <code>@_alwaysEmitIntoClient</code> in all the new styles declaration, allowing us to use <code>.toggleStyle(.switch)</code> instead of the legacy <code>.toggleStyle(SwitchToggleStyle())</code> even when targeting iOS 13 (and equivalent in other platforms).</p></blockquote><h2>Conclusions</h2><p>We've seen how binary frameworks can make additive changes to their API while remaining binary-compatible with previous versions:<br>SwiftUI uses this pattern quite a bit (there are 282 <code>@_alwaysEmitIntoClient</code> matches in Xcode 13b3 SwiftUI interface), have you spotted <code>@_alwaysEmitIntoClient</code> elsewhere? Please let me know via <a href="mailto:hello@fivestars.blog">email</a> or <a href="https://twitter.com/zntfdr">Twitter</a>!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/environment-objects-and-swiftui-styles</guid><title>Environment Objects and SwiftUI Styles</title><description>Some important heads up on this powerful combo.</description><link>https://www.fivestars.blog/articles/environment-objects-and-swiftui-styles</link><pubDate>Tue, 20 Jul 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>SwiftUI's environment and styles are two pillars of Apple's declarative framework. However, when SwiftUI first launched, using them together resulted in a guaranteed app crash.</p><p>More specifically, the crash happened when we used <code>@EnvironmentObject</code> inside our styles definition: when is it safe to use them together? Let's find out.</p><blockquote><p>For the impatients, the results are at the end of the article.</p></blockquote><h2>An example</h2><p>Meet <code>FSStyle</code>, a button style expecting an environment object (<code>FSEnvironmentObject</code>):</p><pre><code><span class="keyword">class</span> FSEnvironmentObject: <span class="type">ObservableObject</span> {
  <span class="keyword">@Published var</span> title = <span class="string">"tap me"</span>
}

<span class="keyword">struct</span> FSStyle: <span class="type">ButtonStyle</span> {
  <span class="keyword">@EnvironmentObject var</span> object: <span class="type">FSEnvironmentObject</span>

  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(object.<span class="property">title</span>) { }
  }
}
</code></pre><blockquote><p>For an introduction to <code>Button</code>s styles, see <a href="https://www.fivestars.blog/articles/button-styles/">Exploring SwiftUI's Button styles</a> and <a href="https://www.fivestars.blog/articles/button-styles-2/">Meet the new Button styling</a>.</p></blockquote><p>From this definition, we'd expect things to work as long as we inject the environment object at some point before applying the style. For example:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@StateObject var</span> object = <span class="type">FSEnvironmentObject</span>()

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(<span class="string">"tap me"</span>) {
    }
    .<span class="call">buttonStyle</span>(<span class="type">FSStyle</span>())
    .<span class="call">environmentObject</span>(object)
  }
}
</code></pre><p>..and yet, if we ran this code in any iOS version before 14.5, it reliably crashed 100% of the times, with <code>Fatal error: No ObservableObject of type FSEnvironmentObject found.</code>.</p><p>The crash occurs as soon as the environment object is used within the <code>makeBody(configuration:)</code> method, both the object definition and <code>makeBody(configuration:)</code> implementation don't matter.</p><blockquote><p>The main two workarounds are either to make the style conform to <code>DynamicProperty</code> (many thanks to <a href="https://github.com/linqingmo">Lin Qing Mo</a> for letting me know!), or to return a view in the <code>makeBody(configuration:)</code> method, and have that view read the environment object.</p></blockquote><p>Now that we've seen what the issue is, let's explore in which iOS versions and styles this crash occurs.</p><h2>The test setup</h2><p>We want to figure out in which iOS versions is safe to use all the possible styles (not just button styles). We can create a small test app and run it throughout all iOS versions supporting SwiftUI and report back.</p><p>Continuing with the <code>ButtonStyle</code> example, here's the complete app:</p><pre><code><span class="keyword">import</span> UIKit
<span class="keyword">import</span> SwiftUI

<span class="keyword">@UIApplicationMain
class</span> AppDelegate: <span class="type">UIResponder</span>, <span class="type">UIApplicationDelegate</span> { }

<span class="keyword">class</span> SceneDelegate: <span class="type">UIResponder</span>, <span class="type">UIWindowSceneDelegate</span> {
  <span class="keyword">var</span> window: <span class="type">UIWindow</span>?
  <span class="keyword">var</span> object: <span class="type">FSEnvironmentObject</span> = <span class="type">FSEnvironmentObject</span>()

  <span class="keyword">func</span> scene(
    <span class="keyword">_</span> scene: <span class="type">UIScene</span>, 
    willConnectTo session: <span class="type">UISceneSession</span>, 
    options connectionOptions: <span class="type">UIScene</span>.<span class="type">ConnectionOptions</span>
  ) {
    <span class="keyword">if let</span> windowScene = scene <span class="keyword">as</span>? <span class="type">UIWindowScene</span> {
      <span class="keyword">let</span> window = <span class="type">UIWindow</span>(windowScene: windowScene)
      <span class="keyword">let</span> contentView = <span class="type">ContentView</span>().<span class="call">environmentObject</span>(object)
      window.<span class="property">rootViewController</span> = <span class="type">UIHostingController</span>(rootView: contentView)
      <span class="keyword">self</span>.<span class="property">window</span> = window
      window.<span class="call">makeKeyAndVisible</span>()
    }
  }
}

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(<span class="string">"tap me"</span>) {
    }
    .<span class="call">buttonStyle</span>(<span class="type">FSStyle</span>())
  }
}

<span class="keyword">struct</span> FSStyle: <span class="type">ButtonStyle</span> {
  <span class="keyword">@EnvironmentObject var</span> object: <span class="type">FSEnvironmentObject</span>

  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(object.<span class="property">title</span>) { }
  }
}

<span class="keyword">class</span> FSEnvironmentObject: <span class="type">ObservableObject</span> {
  <span class="keyword">@Published var</span> title = <span class="string">"tap me"</span>
}
</code></pre><p>The app consists of one screen, which contains our test component/style.</p><p>A few points:</p><ul><li>we're using UIKit's lifecycle because we want to run the tests with iOS 13 as well</li><li>we're don't use <code>@StateObject</code> for the environment object because that property wrapper is iOS 14+ only</li><li>the only difference between testing <code>ButtonStyle</code> and other styles is in the <code>FSStyle</code> and <code>ContentView</code> body definition, everything else stays the same</li></ul><h2>CI/CD Setup</h2><p>We will be testing twelve iOS versions, from iOS 13.0 to iOS 14.5, and all eight styles that support customization.</p><p>Testing each combination manually would be quite a lot of work, instead, we can let a CI/CD provider do all the heavy lifting for us. Any CI/CD setup will do, here's how the various Xcode/iOS versions where distributed for this study:</p><ul><li>macOS 10.14<ul><li>iOS 13.0, Xcode	11.0</li><li>iOS 13.1,	Xcode 11.1</li><li>iOS 13.2,	Xcode 11.2</li></ul></li></ul><ul><li>macOS 10.15<ul><li>iOS 13.3, Xcode 11.3.1</li><li>iOS 13.4, Xcode 11.4.1</li><li>iOS 13.5, Xcode 11.5</li><li>iOS 13.6, Xcode 11.6</li><li>iOS 13.7, Xcode 11.7</li><li>iOS 14.0, Xcode 12.0.1</li><li>iOS 14.1, Xcode 12.1</li><li>iOS 14.2, Xcode 12.2</li><li>iOS 14.3, Xcode 12.3</li></ul></li></ul><ul><li>macOS 11.4:<ul><li>iOS 14.4, Xcode 12.4</li><li>iOS 14.5, Xcode	12.5</li></ul></li></ul><h2>Results</h2><p>As the test app only has one screen that shows immediately the tested component, all it's needed to pass the test is launching the app and not crash right away. Here is the outcome:</p><table><thead><tr><th>Style 👇🏻 / iOS 👉🏻</th><th>13.0</th><th>13.1</th><th>13.2</th><th>13.3</th><th>13.4</th><th>13.5</th><th>13.6</th><th>13.7</th><th>14.0</th><th>14.1</th><th>14.2</th><th>14.3</th><th>14.4</th><th>14.5</th></tr></thead><tbody><tr><td><code>ButtonStyle</code></td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>✅</td></tr><tr><td><code>GroupBoxStyle</code>*</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td><code>LabelStyle</code>*</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td><code>MenuStyle</code>*</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td><code>PrimitiveButtonStyle</code></td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td><code>ProgressViewStyle</code>*</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td><code>TextFieldStyle</code></td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td><code>ToggleStyle</code></td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>💥</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr></tbody></table><blockquote><p>💥 = crash, ✅ = pass. *Style available from iOS 14.</p></blockquote><p>To recap:</p><ul><li>all styles but <code>ButtonStyle</code> support <code>@EnvironmentObject</code> from iOS 14.0</li><li>from iOS 14.5, all styles including <code>ButtonStyle</code> support <code>@EnvironmentObject</code></li></ul><h2>Conclusions</h2><p>The reason why the styles + <code>@EnvironmentObject</code> combo was not supported from the beginning will probably remain within the SwiftUI team, however it could have been intentional:<br>looking at the way SwiftUI <em>stock</em> styles are applied, besides a few parameters passed via the <code>Configuration</code>, most moving parts come from <code>EnvironmentValues</code>, e.g. <code>@Environment(\.isEnabled)</code>, <code>@Environment(\.font)</code>, and <code>@Environment(\.controlProminence)</code>.</p><p>Unlike <code>@EnvironmentObject</code>, <code>EnvironmentValues</code> are supported (without 💥!) from iOS 13.0, and it's the way I also recommend going when adding dynamic to our custom styles.</p><p>Regardless of whether this was a bug or by design, as an SDK vendor, it's up to us to disambiguate and clarify such scenarios to developers.<br>As far as I know, this was not documented anywhere, nor was addressed on any release note: if developers misused it, it's on the framework.</p><blockquote><p>Another place where this could have been an issue are view modifiers, however both <code>EnvironmentValues</code> and <code>@EnvironmentObject</code> are supported (without 💥) from iOS 13.0.</p></blockquote><p>Are there any other SwiftUI feature where you've experience similar unexpected behaviors? I have the test setup from this article ready to go: feel free to reach out <a href="mailto:hello@fivestars.blog">via email</a> or <a href="https://twitter.com/zntfdr">Twitter</a>, I will be happy to run tests for your case!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/xcode-13-beta-3</guid><title>What's new in Xcode 13 beta 3</title><description>A.k.a. the "SwiftUI style" release, let's have a look at what's new!</description><link>https://www.fivestars.blog/articles/xcode-13-beta-3</link><pubDate>Thu, 15 Jul 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Xcode 13 beta 3 has just been released, and it comes with new SwiftUI goodies: let's have a look at what's new!</p><h2>Shape style erasing with <code>AnyShapeStyle</code></h2><pre><code><span class="comment">/// A type-erased ShapeStyle value.</span>
<span class="keyword">@available</span>(iOS <span class="number">15.0</span>, macOS <span class="number">12.0</span>, tvOS <span class="number">15.0</span>, watchOS <span class="number">8.0</span>, *)
<span class="keyword">@frozen public struct</span> AnyShapeStyle : <span class="type">ShapeStyle</span> {
  <span class="comment">/// Create an instance from `style`.</span>
  <span class="keyword">public init</span>&lt;S&gt;(<span class="keyword">_</span> style: <span class="type">S</span>) <span class="keyword">where</span> <span class="type">S</span> : <span class="type">ShapeStyle</span>
}
</code></pre><p>While we have <code>@ViewBuilder</code> to build advanced views, the same cannot be said for shapes. Until today, offering different shape styles meant defining multiple times the same view with different shape styles.</p><p>New from this beta seed, we have <code>AnyShapeStyle</code>, which makes our life easier:</p><img src="https://www.fivestars.blog/assets/posts/xcode-13-beta-3/anyshape.gif"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> isAngular = <span class="keyword">false

  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ZStack</span> {
      <span class="type">Rectangle</span>()
        .<span class="call">fill</span>(shapeStyle)
        .<span class="call">ignoresSafeArea</span>()
      <span class="type">Button</span>(isAngular ? <span class="string">"Angular"</span> : <span class="string">"Linear"</span>) { isAngular.<span class="call">toggle</span>() }
        .<span class="call">buttonStyle</span>(.<span class="dotAccess">bordered</span>)
        .<span class="call">controlProminence</span>(.<span class="dotAccess">increased</span>)
    }
  }

  <span class="keyword">var</span> shapeStyle: <span class="keyword">some</span> <span class="type">ShapeStyle</span> {
    <span class="keyword">let</span> gradient: <span class="type">Gradient</span> = <span class="type">Gradient</span>(colors: [.<span class="dotAccess">red</span>, .<span class="dotAccess">pink</span>, .<span class="dotAccess">purple</span>])
    <span class="keyword">if</span> isAngular {
      <span class="keyword">return</span> <span class="type">AnyShapeStyle</span>(.<span class="call">conicGradient</span>(gradient, center: .<span class="dotAccess">center</span>)) <span class="comment">// 👈🏻</span>
    } <span class="keyword">else</span> {
      <span class="keyword">return</span> <span class="type">AnyShapeStyle</span>( <span class="comment">// 👈🏻</span>
        <span class="type">LinearGradient</span>(gradient: gradient, startPoint: .<span class="dotAccess">leading</span>, endPoint: .<span class="dotAccess">trailing</span>)
      )
    }
  }
}
</code></pre><blockquote><p>Maybe we can get a <code>@ShapeStyleBuilder</code> next? 🙏🏻 FB9331755</p></blockquote><h2>Styles re-organization with <code>HierarchicalShapeStyle</code></h2><p>We have a new <code>HierarchicalShapeStyle</code>, used for all the <code>.primary</code>, <code>.secondary</code>, etc styles. Previously each one of these styles had their own style, e.g. <code>QuaternaryContentStyle</code> for <code>.quaternary</code>.</p><p>All the old styles have been deprecated and will likely be removed in an upcoming beta seed.</p><p>From:</p><pre><code><span class="keyword">extension</span> <span class="type">ShapeStyle</span> <span class="keyword">where</span> <span class="type">Self</span> == <span class="type">PrimaryContentStyle</span> {
  <span class="keyword">public static var</span> primary: <span class="type">PrimaryContentStyle</span> { <span class="keyword">get</span> }
}

<span class="keyword">extension</span> <span class="type">ShapeStyle</span> <span class="keyword">where</span> <span class="type">Self</span> == <span class="type">SecondaryContentStyle</span> {
  <span class="keyword">public static var</span> secondary: <span class="type">SecondaryContentStyle</span> { <span class="keyword">get</span> }
}

...
</code></pre><p>To:</p><pre><code><span class="keyword">extension</span> <span class="type">ShapeStyle</span> <span class="keyword">where</span> <span class="type">Self</span> == <span class="type">HierarchicalShapeStyle</span> {
  <span class="keyword">public static var</span> primary: <span class="type">HierarchicalShapeStyle</span> { <span class="keyword">get</span> }
  <span class="keyword">public static var</span> secondary: <span class="type">HierarchicalShapeStyle</span> { <span class="keyword">get</span> }
  <span class="keyword">public static var</span> tertiary: <span class="type">HierarchicalShapeStyle</span> { <span class="keyword">get</span> }
  <span class="keyword">public static var</span> quaternary: <span class="type">HierarchicalShapeStyle</span> { <span class="keyword">get</span> }
}
</code></pre><h2>Angular Gradient convenience initializers</h2><p>Angular gradients have gained new static convenience initializers (available from iOS 13 and later):</p><pre><code><span class="keyword">extension</span> <span class="type">ShapeStyle</span> <span class="keyword">where</span> <span class="type">Self</span> == <span class="type">AngularGradient</span> {
  <span class="keyword">public static func</span> conicGradient(
    <span class="keyword">_</span> gradient: <span class="type">Gradient</span>, center: <span class="type">UnitPoint</span>, angle: <span class="type">Angle</span> = .<span class="dotAccess">zero</span>
  ) -&gt; <span class="type">AngularGradient</span>

  <span class="keyword">public static func</span> conicGradient(
    colors: [<span class="type">Color</span>], center: <span class="type">UnitPoint</span>, angle: <span class="type">Angle</span> = .<span class="dotAccess">zero</span>
  ) -&gt; <span class="type">AngularGradient</span>

  <span class="keyword">public static func</span> conicGradient(
    stops: [<span class="type">Gradient</span>.<span class="type">Stop</span>], center: <span class="type">UnitPoint</span>, angle: <span class="type">Angle</span> = .<span class="dotAccess">zero</span>
  ) -&gt; <span class="type">AngularGradient</span>
}
</code></pre><blockquote><p>For an example on this, go back to the <code>AnyShapeStyle</code> example, I've sneaked one of those in there 😜</p></blockquote><h2><code>keyboardShortcut</code> environment value</h2><img src="https://www.fivestars.blog/assets/posts/xcode-13-beta-3/keyboard.png"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">Button</span>(<span class="string">"Tap me or press ⌘F"</span>) {
        <span class="call">print</span>(<span class="string">"tapped or pressed"</span>)
      }
      .<span class="call">keyboardShortcut</span>(<span class="string">"F"</span>, modifiers: [.<span class="dotAccess">command</span>])

      <span class="type">Button</span>(<span class="string">"Tap me or press ⌘S"</span>) {
        <span class="call">print</span>(<span class="string">"tapped or pressed"</span>)
      }
      .<span class="call">keyboardShortcut</span>(<span class="string">"S"</span>, modifiers: [.<span class="dotAccess">command</span>])
    }
    .<span class="call">buttonStyle</span>(<span class="type">FiveStarsButtonStyle</span>())
  }
}

<span class="keyword">private struct</span> FiveStarsButtonStyle: <span class="type">ButtonStyle</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">keyboardShortcut</span>) <span class="keyword">private var</span> shortcut: <span class="type">KeyboardShortcut</span>? <span class="comment">// 👈🏻</span>

  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    configuration.<span class="property">label</span>
      .<span class="call">font</span>(.<span class="dotAccess">body</span>.<span class="call">weight</span>(shortcut == .<span class="keyword">init</span>(<span class="string">"F"</span>) ? .<span class="dotAccess">heavy</span> : .<span class="dotAccess">regular</span>))
  }
}
</code></pre><p>Xcode 13 Beta 3 introduces new <code>\.keyboardShortcut</code> environment value, letting us read the assigned keyboard shortcut to any view. We can use this value as we please, for example to customize the appearance of a button.</p><h2>More compact <code>TimelineView</code> definitions</h2><p>Similar to styles, the SwiftUI team is pushing us to use the more compact syntax for the new <code>TimelineView</code>.</p><p>From Xcode 13b3, this generates a deprecation warning:</p><pre><code><span class="type">TimelineView</span>(<span class="type">EveryMinuteTimelineSchedule</span>()) { context <span class="keyword">in</span>
  <span class="type">Text</span>(context.<span class="property">date</span>.<span class="property">description</span>)
}
</code></pre><p>Where the recommended way is:</p><pre><code><span class="type">TimelineView</span>(.<span class="dotAccess">everyMinute</span>) { context <span class="keyword">in</span>
  <span class="type">Text</span>(context.<span class="property">date</span>.<span class="property">description</span>)
}
</code></pre><h2>View builders backport</h2><p>In <a href="https://www.fivestars.blog/articles/swiftui-patterns-view-builders/">SwiftUI patterns evolution: view builders</a> we've covered a new emerging SwiftUI pattern from this year, at the end of the article we've seen how it was relatively easy to create extensions back-porting said pattern to previous versions OSes.</p><p>Thanks to this new beta seed, most of these extensions are no longer needed, as SwiftUI backports these new initializers view modifiers all the way to iOS 13 (and equivalent on other platforms).</p><p>For example, we went from:</p><pre><code><span class="keyword">@available</span>(iOS <span class="number">15.0</span>, macOS <span class="number">12.0</span>, tvOS <span class="number">15.0</span>, watchOS <span class="number">8.0</span>, *) <span class="comment">// 👈🏻</span>
<span class="keyword">extension</span> <span class="type">Section</span> <span class="keyword">where</span> <span class="type">Parent</span> : <span class="type">View</span>, <span class="type">Content</span> : <span class="type">View</span>, <span class="type">Footer</span> : <span class="type">View</span> {
  <span class="keyword">public init</span>(
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>, 
    <span class="keyword">@ViewBuilder</span> header: () -&gt; <span class="type">Parent</span>, 
    <span class="keyword">@ViewBuilder</span> footer: () -&gt; <span class="type">Footer</span>
  )
}
</code></pre><p>To:</p><pre><code><span class="keyword">@available</span>(iOS <span class="number">13.0</span>, macOS <span class="number">10.15</span>, tvOS <span class="number">13.0</span>, watchOS <span class="number">6.0</span>, *) <span class="comment">// 👈🏻</span>
<span class="keyword">extension</span> <span class="type">Section</span> <span class="keyword">where</span> <span class="type">Parent</span> : <span class="type">View</span>, <span class="type">Content</span> : <span class="type">View</span>, <span class="type">Footer</span> : <span class="type">View</span> {
  <span class="keyword">public init</span>(
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>, 
    <span class="keyword">@ViewBuilder</span> header: () -&gt; <span class="type">Parent</span>, 
    <span class="keyword">@ViewBuilder</span> footer: () -&gt; <span class="type">Footer</span>
  )
}
</code></pre><h2>Documentation</h2><p>We have a lot more documentation for:</p><ul><li><a href="https://developer.apple.com/documentation/swiftui/asyncimage"><code>AsyncImage</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/angulargradient"><code>AngularGradient</code></a></li></ul><h2>Conclusions</h2><p>Did you find anything else interesting in Xcode 13 beta 3? Please <a href="https://twitter.com/zntfdr">let me know</a>!</p><p>As always, please <a href="https://feedbackassistant.apple.com">report any issue to Apple</a>: we're still months away from the official release, things can and will be fixed, as long as we report them.</p><blockquote><p>This article is part of a series exploring new SwiftUI features. We will cover many more during the rest of the summer: subscribe to Five Stars's <a href="https://www.fivestars.blog/feed.rss">feed RSS</a> or follow <a href="http://twitter.com/fiveStarsBlog">@FiveStarsBlog</a> on Twitter to never miss new content!</p></blockquote>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/app-delegate-scene-delegate-swiftui</guid><title>How to add an AppDelegate and a SceneDelegate to a SwiftUI app</title><description>An exploration on how to reach for and use app and scene delegates within a SwiftUI App lifecycle</description><link>https://www.fivestars.blog/articles/app-delegate-scene-delegate-swiftui</link><pubDate>Tue, 13 Jul 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Besides SwiftUI, in 2019, Apple <a href="https://www.wwdcnotes.com/notes/wwdc19/212/">introduced the concept of multiple windows</a>, where each window, more appropriately called scene, represents a UI instance of our app.</p><p>Fast forward one year later, and SwiftUI has a brand new life-cycle, dropping both UIKit's app and scene delegates entirely. Here's what's left:</p><pre><code><span class="keyword">@main
struct</span> FSApp: <span class="type">App</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">Scene</span> {
    <span class="type">WindowGroup</span> {
      <span class="type">ContentView</span>()
    }
  }
}
</code></pre><p>While the SwiftUI team keeps moving forward at a neck-breaking speed, not all third party libraries and, admittedly, not even all Apple's API, are ready for the big shift:<br>to this day, many APIs still require either an <code>AppDelegate</code> or an <code>UIScene</code> to operate.</p><p>Does it mean that we can't use the new SwiftUI life-cycle? No! The SwiftUI team has thought about these scenarios and provided us with the right tools. Let's dig in.</p><h2>Add an app delegate to a SwiftUI app</h2><p>Along with the new SwiftUI life-cycle, the <code>@UIApplicationDelegateAdaptor</code> property wrapper has been introduced, letting us associate an app delegate to a SwiftUI app.</p><p>First, let's define our <code>UIApplicationDelegate</code>:</p><pre><code><span class="keyword">class</span> FSAppDelegate: <span class="type">NSObject</span>, <span class="type">UIApplicationDelegate</span> {
  <span class="keyword">func</span> application(
    <span class="keyword">_</span> application: <span class="type">UIApplication</span>,
    didFinishLaunchingWithOptions launchOptions: [<span class="type">UIApplication</span>.<span class="type">LaunchOptionsKey</span>: <span class="type">Any</span>]? = <span class="keyword">nil</span>
  ) -&gt; <span class="type">Bool</span> {
    <span class="comment">// ...</span>
    <span class="keyword">return true</span>
  }
}
</code></pre><p>Then we can add it to our <code>App</code>:</p><pre><code><span class="keyword">@main
struct</span> FSApp: <span class="type">App</span> {
  <span class="keyword">@UIApplicationDelegateAdaptor var</span> delegate: <span class="type">FSAppDelegate</span> 

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">Scene</span> {
    <span class="type">WindowGroup</span> {
      <span class="type">ContentView</span>()
    }
  }
}
</code></pre><p>SwiftUI will both initialize and manage our delegate lifetime. No further work is necessary.</p><h3>App delegate access via the environment</h3><p>Environment is one of the most powerful SwiftUI features, if we conform our app delegate to <code>ObservableObject</code>, it will be accessible anywhere in our app:</p><pre><code><span class="keyword">class</span> FSAppDelegate: <span class="type">NSObject</span>, <span class="type">UIApplicationDelegate</span>, <span class="type">ObservableObject</span> { <span class="comment">// 👈🏻</span>
  <span class="keyword">func</span> application(
    <span class="keyword">_</span> application: <span class="type">UIApplication</span>,
    didFinishLaunchingWithOptions launchOptions: [<span class="type">UIApplication</span>.<span class="type">LaunchOptionsKey</span>: <span class="type">Any</span>]? = <span class="keyword">nil</span>
  ) -&gt; <span class="type">Bool</span> {
    <span class="comment">// ...</span>
    <span class="keyword">return true</span>
  }

  <span class="comment">// ...</span>
}
</code></pre><p>With just this change, we can access our app delegate like any other environment object:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@EnvironmentObject var</span> appDelegate: <span class="type">FSAppDelegate</span>
  
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(<span class="string">"Tap me"</span>) {
      <span class="comment">// appDelegate. ...</span>
    }
  }
}
</code></pre><blockquote><p>We didn't have to inject the app delegate ourselves.</p></blockquote><p>Besides receiving the app life-cycle events, we can add to our app delegate any <code>@Published</code> properties, and views depending on it will produce a new <code>body</code> whenever changes are published, for example:</p><pre><code><span class="keyword">class</span> FSAppDelegate: <span class="type">NSObject</span>, <span class="type">UIApplicationDelegate</span>, <span class="type">ObservableObject</span> {
  <span class="keyword">@Published var</span> date: <span class="type">Date</span> = .<span class="dotAccess">now</span> <span class="comment">// 👈🏻</span>

  <span class="keyword">func</span> application(
    <span class="keyword">_</span> application: <span class="type">UIApplication</span>,
    didFinishLaunchingWithOptions launchOptions: [<span class="type">UIApplication</span>.<span class="type">LaunchOptionsKey</span>: <span class="type">Any</span>]? = <span class="keyword">nil</span>
  ) -&gt; <span class="type">Bool</span> {
    <span class="comment">// 👇🏻 Publishes every second</span>
    <span class="type">Timer</span>
      .<span class="call">publish</span>(every: <span class="number">1</span>, on: .<span class="dotAccess">main</span>, in: .<span class="dotAccess">default</span>)
      .<span class="call">autoconnect</span>()
      .<span class="call">assign</span>(to: &amp;$date)
    <span class="keyword">return true</span>
  }
}

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@EnvironmentObject var</span> appDelegate: <span class="type">FSAppDelegate</span> <span class="comment">// 👈🏻</span>
  
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Text</span>(appDelegate.<span class="property">date</span>.<span class="call">formatted</span>(date: .<span class="dotAccess">omitted</span>, time: .<span class="dotAccess">standard</span>))
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/app-delegate-scene-delegate-swiftui/time.gif"/><h2>Add a scene delegate to a SwiftUI app</h2><p>Similar to app delegate, some legacy APIs require access to the <code>UIWindowSceneDelegate</code> or to the <code>UIScene</code> in general.</p><p>We have mainly two ways to add a scene delegate to our app. Let's create the delegate:</p><pre><code><span class="keyword">class</span> FSSceneDelegate: <span class="type">NSObject</span>, <span class="type">UIWindowSceneDelegate</span> {
  <span class="keyword">func</span> sceneWillEnterForeground(<span class="keyword">_</span> scene: <span class="type">UIScene</span>) {
    <span class="comment">// ...</span>
  }

  <span class="keyword">func</span> sceneDidBecomeActive(<span class="keyword">_</span> scene: <span class="type">UIScene</span>) {
    <span class="comment">// ...</span>
  }

  <span class="keyword">func</span> sceneWillResignActive(<span class="keyword">_</span> scene: <span class="type">UIScene</span>) {
    <span class="comment">// ...</span>
  }

  <span class="comment">// ...</span>
}
</code></pre><p>The first way is via our app delegate, thanks to the <a href="https://developer.apple.com/documentation/uikit/uiapplicationdelegate/3197905-application"><code>application(_:configurationForConnecting:options:)</code></a> method, which lets us return a new <code>UISceneConfiguration</code>, where we can also declare the scene delegate class:</p><pre><code><span class="keyword">class</span> FSAppDelegate: <span class="type">NSObject</span>, <span class="type">UIApplicationDelegate</span> {

  <span class="keyword">func</span> application(
    <span class="keyword">_</span> application: <span class="type">UIApplication</span>,
    configurationForConnecting connectingSceneSession: <span class="type">UISceneSession</span>,
    options: <span class="type">UIScene</span>.<span class="type">ConnectionOptions</span>
  ) -&gt; <span class="type">UISceneConfiguration</span> {
    <span class="keyword">let</span> sceneConfig = <span class="type">UISceneConfiguration</span>(name: <span class="keyword">nil</span>, sessionRole: connectingSceneSession.<span class="property">role</span>)
    sceneConfig.<span class="property">delegateClass</span> = <span class="type">FSSceneDelegate</span>.<span class="keyword">self</span> <span class="comment">// 👈🏻</span>
    <span class="keyword">return</span> sceneConfig
  }
}
</code></pre><p>With this change, our scene delegate will start receiving all the scene events.</p><p>The second way, which doesn't require an app delegate, is via the main app <code>info.plist</code>, where we can update the <a href="https://developer.apple.com/documentation/uikit/app_and_environment/scenes/specifying_the_scenes_your_app_supports">Application Scene Manifest</a>:</p><pre><code>&lt;dict&gt;
  &lt;key&gt;<span class="type">UISceneConfigurations</span>&lt;/key&gt;
  &lt;dict&gt;
    &lt;key&gt;<span class="type">UIWindowSceneSessionRoleApplication</span>&lt;/key&gt;
    &lt;array&gt;
      &lt;dict&gt;
        &lt;key&gt;<span class="type">UISceneDelegateClassName</span>&lt;/key&gt;
        &lt;string&gt;$(<span class="type">PRODUCT_MODULE_NAME</span>).<span class="type">FSSceneDelegate</span>&lt;/string&gt;
      &lt;/dict&gt;
    &lt;/array&gt;
  &lt;/dict&gt;
  &lt;key&gt;<span class="type">UIApplicationSupportsMultipleScenes</span>&lt;/key&gt;
  &lt;<span class="keyword">true</span>/&gt;
&lt;/dict&gt;
</code></pre><p>The most crucial bit is the <code>Delegate Class Name</code> definition, a.k.a. the value of key <code>UISceneDelegateClassName</code>, where we specify the scene delegate to be used.</p><p>Once this is set, our scene delegate will start receiving all the scene events.</p><blockquote><p>If we implement both ways, the app delegate approach takes priority.</p></blockquote><h3>Scene delegate access via the environment</h3><p>Regardless of which we way we use, like for app delegate, if we make our scene delegate conform to <code>ObservableObject</code>, it will automatically be injected in SwiftUI's environment:</p><pre><code><span class="keyword">class</span> FSSceneDelegate: <span class="type">NSObject</span>, <span class="type">UIWindowSceneDelegate</span>, <span class="type">ObservableObject</span> { <span class="comment">// 👈🏻</span>
  <span class="keyword">func</span> sceneWillEnterForeground(<span class="keyword">_</span> scene: <span class="type">UIScene</span>) {
    <span class="comment">// ...</span>
  }

  <span class="comment">// ...</span>
}
</code></pre><p>Exactly like for the app delegate, we can add dynamic properties and publish things, and all views depending on it will update accordingly:</p><pre><code><span class="keyword">class</span> FSSceneDelegate: <span class="type">NSObject</span>, <span class="type">UIWindowSceneDelegate</span>, <span class="type">ObservableObject</span> {
  <span class="keyword">@Published var</span> date: <span class="type">Date</span> = .<span class="dotAccess">now</span> <span class="comment">// 👈🏻</span>

  <span class="keyword">func</span> scene(
    <span class="keyword">_</span> scene: <span class="type">UIScene</span>,
    willConnectTo session: <span class="type">UISceneSession</span>,
    options connectionOptions: <span class="type">UIScene</span>.<span class="type">ConnectionOptions</span>
  ) {
    <span class="comment">// 👇🏻 Publishes every second</span>
    <span class="type">Timer</span>
      .<span class="call">publish</span>(every: <span class="number">1</span>, on: .<span class="dotAccess">main</span>, in: .<span class="dotAccess">default</span>)
      .<span class="call">autoconnect</span>()
      .<span class="call">assign</span>(to: &amp;$date)
  }
}

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@EnvironmentObject var</span> sceneDelegate: <span class="type">FSSceneDelegate</span> <span class="comment">// 👈🏻</span>
  
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Text</span>(sceneDelegate.<span class="property">date</span>.<span class="call">formatted</span>(date: .<span class="dotAccess">omitted</span>, time: .<span class="dotAccess">standard</span>))
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/app-delegate-scene-delegate-swiftui/time.gif"/><blockquote><p>This delegate will have its separate <code>UISceneSession</code>, meaning that we won't get access to SwiftUI's <code>@SceneStorage</code> storage.</p></blockquote><p>To be clear, it's completely fine having a view depending on both delegates:</p><pre><code><span class="keyword">struct</span> FSView: <span class="type">View</span> {
  <span class="keyword">@EnvironmentObject var</span> appDelegate: <span class="type">FSAppDelegate</span>
  <span class="keyword">@EnvironmentObject var</span> sceneDelegate: <span class="type">FSSceneDelegate</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    ...
  }
}
</code></pre><h2>Conclusions</h2><p>Since SwiftUI's introduction, one of its flagship features has been compatibility with previous UI frameworks:</p><ul><li>Is something not available in SwiftUI yet? A collection of <code>Representable</code> protocols will help you fill in the gap (<code>UIViewRepresentable</code>, <code>NSViewRepresentable</code>, <code>WKInterfaceObjectRepresentable</code>, ...).</li></ul><ul><li>Does something still require an app delegate or an <code>UIScene</code> (looking at you, <code>SKStoreReviewController</code>, FB9301675)? <code>@UIApplicationDelegateAdaptor</code> and the Scene Manifest have you covered.</li></ul><p>As time passes, we will reach for these fallbacks less and less. Until then, we're glad they're here.</p><p>Where do you find yourself going back to UIKit/AppKit/WatchKit? Let me know via <a href="mailto:hello@fivestars.blog">email</a> or <a href="http://twitter.com/zntfdr">Twitter</a>!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/button-styles-2</guid><title>Meet the new Button styling</title><description>Buttons have gained a few new tricks this year, let's see how we can take advantage of them!</description><link>https://www.fivestars.blog/articles/button-styles-2</link><pubDate>Tue, 6 Jul 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>One of the most powerful SwiftUI aspects is customization. We can create, and iterate on, views at a quick pace. The best part? We don't have to declare the whole design in the view: instead, we can embrace SwiftUI's styles.</p><p>This year <code>Button</code>s has gained new powerful customization tools; Let's see how we can take advantage of them in our styles.</p><h2>Groundwork</h2><p>This article assumes some knowledge on SwiftUI buttons and styles in general:</p><ul><li>for an overview of what's new this year, see <a href="https://swiftwithmajid.com/2021/06/30/the-many-faces-of-button-in-swiftui/ ">The many faces of button in SwiftUI</a> by <a href="https://twitter.com/mecid">Majid Jabrayilov</a></li><li>for a deep dive into <code>Button</code> styling, see <a href="https://www.fivestars.blog/articles/button-styles/">Exploring SwiftUI's Button styles</a></li><li>for a guide on how to create styles for custom views, see <a href="https://www.fivestars.blog/articles/custom-view-styles/">Custom SwiftUI view styles</a></li></ul><h2>Meet FiveStarsButtonStyle</h2><p>We're going to create our own button style that takes advantage of all the new Xcode 13 features, meet <code>FiveStarsButtonStyle</code>:</p><pre><code><span class="keyword">struct</span> FiveStarsButtonStyle: <span class="type">ButtonStyle</span> {
  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    configuration
      .<span class="dotAccess">label</span>
      .<span class="call">opacity</span>(configuration.<span class="property">isPressed</span> ? <span class="number">0.5</span> : <span class="number">1</span>)
  }
}
</code></pre><p><a href="https://www.fivestars.blog/articles/swiftui-wwdc21/">What's new in SwiftUI</a> told us that there's a new way to apply styles:</p><ul><li>the old approach, e.g. <code>Button(...).buttonStyle(PlainButtonStyle())</code>, has been deprecated</li><li>the new approach, e.g. <code>Button(...).buttonStyle(.plain)</code>, is more succinct and to the point</li></ul><p>Let's make it possible to use our style with the new API:</p><pre><code><span class="keyword">extension</span> <span class="type">ButtonStyle</span> <span class="keyword">where</span> <span class="type">Self</span> == <span class="type">FiveStarsButtonStyle</span> {
  <span class="keyword">static var</span> fiveStars: <span class="type">FiveStarsButtonStyle</span> {
    <span class="type">FiveStarsButtonStyle</span>()
  }
}
</code></pre><p>Great! We can now apply our style like any other, with the new modern API:</p><pre><code><span class="type">Button</span>(<span class="string">"Tap me"</span>) { 
  ...
}
.<span class="call">buttonStyle</span>(.<span class="dotAccess">fiveStars</span>)
</code></pre><img src="https://www.fivestars.blog/assets/posts/button-styles-2/plain.gif"/><h2>Roles</h2><p>From this year, buttons can be associated with a <code>ButtonRole</code>. Like <code>Button</code>'s <code>title</code> and <code>action</code>, <code>role</code> is assigned via parameter instead of being passed down via the environment. Here are all the possible values (as of Xcode 13b2):</p><pre><code><span class="type">Button</span>(<span class="string">"No role"</span>, role: <span class="keyword">nil</span>) { ... } <span class="comment">// default, equivalent to Button("No role") { ... }</span>
<span class="type">Button</span>(<span class="string">"Cancel role"</span>, role: .<span class="dotAccess">cancel</span>) { ... }
<span class="type">Button</span>(<span class="string">"Destructive role"</span>, role: .<span class="dotAccess">destructive</span>) { ... }
</code></pre><p>We can take advantage of this role in our style via the new <code>ButtonStyleConfiguration</code> <code>role</code> property:</p><pre><code><span class="keyword">public struct</span> ButtonStyleConfiguration {
  <span class="keyword">public let</span> role: <span class="type">ButtonRole</span>? <span class="comment">// 👈🏻</span>
  <span class="keyword">public let</span> label: <span class="type">ButtonStyleConfiguration</span>.<span class="type">Label</span>
  <span class="keyword">public let</span> isPressed: <span class="type">Bool</span>
}
</code></pre><blockquote><p><code>role</code> is the first property that has been added to both <code>ButtonStyleConfiguration</code> and <code>PrimitiveButtonStyleConfiguration</code> since their launch.</p></blockquote><p>For example:</p><pre><code><span class="keyword">struct</span> FiveStarsButtonStyle: <span class="type">ButtonStyle</span> {
  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    configuration
      .<span class="dotAccess">label</span>
      .<span class="call">padding</span>(.<span class="dotAccess">horizontal</span>)
      .<span class="call">padding</span>(.<span class="dotAccess">vertical</span>)
      .<span class="call">foregroundColor</span>(<span class="call">foregroundColor</span>(for: configuration.<span class="property">role</span>))
      .<span class="call">background</span> {
        <span class="type">RoundedRectangle</span>(cornerRadius: <span class="number">8</span>)
          .<span class="call">strokeBorder</span>(<span class="call">color</span>(for: configuration.<span class="property">role</span>), lineWidth: <span class="number">2</span>)
      }
      .<span class="call">opacity</span>(configuration.<span class="property">isPressed</span> ? <span class="number">0.5</span> : <span class="number">1</span>)
  }

  <span class="keyword">func</span> foregroundColor(for role: <span class="type">ButtonRole</span>?) -&gt; <span class="type">Color</span>? {
    role == .<span class="dotAccess">destructive</span> ? .<span class="dotAccess">red</span> : <span class="keyword">nil</span>
  }

  <span class="keyword">func</span> color(for role: <span class="type">ButtonRole</span>?) -&gt; <span class="type">Color</span> {
    <span class="keyword">switch</span> role {
      <span class="keyword">case</span> .<span class="dotAccess">cancel</span>?, .<span class="dotAccess">destructive</span>?:
        <span class="keyword">return</span> <span class="type">Color</span>.<span class="property">red</span>
      <span class="keyword">default</span>:
        <span class="keyword">return</span> <span class="type">Color</span>.<span class="property">accentColor</span>
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/button-styles-2/role.png"/><h2>Control size</h2><p>From Xcode 13 we also have the possibility to declare the size of our controls, this is done via a new <code>controlSize(_:)</code> modifier:</p><pre><code><span class="type">Button</span>(<span class="string">"Mini"</span>) { ... }
  .<span class="call">controlSize</span>(.<span class="dotAccess">mini</span>)
<span class="type">Button</span>(<span class="string">"Small"</span>) { ... }
  .<span class="call">controlSize</span>(.<span class="dotAccess">small</span>)
<span class="type">Button</span>(<span class="string">"Regular"</span>) { ... }
  .<span class="call">controlSize</span>(.<span class="dotAccess">regular</span>) <span class="comment">// default</span>
<span class="type">Button</span>(<span class="string">"Large"</span>) { ... }
  .<span class="call">controlSize</span>(.<span class="dotAccess">large</span>)
</code></pre><p>The <code>ControlSize</code> value is then passed down through the environment, which we can read and use in our style:</p><pre><code><span class="keyword">struct</span> FiveStarsButtonStyle: <span class="type">ButtonStyle</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">controlSize</span>) <span class="keyword">var</span> controlSize: <span class="type">ControlSize</span> <span class="comment">// 👈🏻</span>

  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    configuration
      .<span class="dotAccess">label</span>
      .<span class="call">frame</span>(maxWidth: controlSize == .<span class="dotAccess">large</span> ? .<span class="dotAccess">infinity</span> : <span class="keyword">nil</span>)
      .<span class="call">padding</span>(.<span class="dotAccess">horizontal</span>, horizontalPadding)
      .<span class="call">padding</span>(.<span class="dotAccess">vertical</span>, verticalPadding)
      .<span class="call">foregroundColor</span>(<span class="call">foregroundColor</span>(for: configuration.<span class="property">role</span>))
      .<span class="call">background</span> {
        <span class="type">RoundedRectangle</span>(cornerRadius: <span class="number">8</span>)
          .<span class="call">strokeBorder</span>(<span class="call">color</span>(for: configuration.<span class="property">role</span>), lineWidth: <span class="number">2</span>)
      }
      .<span class="call">opacity</span>(configuration.<span class="property">isPressed</span> ? <span class="number">0.5</span> : <span class="number">1</span>)
  }

  <span class="keyword">var</span> horizontalPadding: <span class="type">Double</span> {
    <span class="keyword">switch</span> controlSize {
      <span class="keyword">case</span> .<span class="dotAccess">mini</span>:
        <span class="keyword">return</span> <span class="number">8</span>
      <span class="keyword">case</span> .<span class="dotAccess">small</span>:
        <span class="keyword">return</span> <span class="number">16</span>
      <span class="keyword">case</span> .<span class="dotAccess">regular</span>:
        <span class="keyword">return</span> <span class="number">32</span>
      <span class="keyword">case</span> .<span class="dotAccess">large</span>:
        <span class="keyword">return</span> <span class="number">0</span> <span class="comment">// no need.</span>
      <span class="keyword">@unknown default</span>:
        <span class="keyword">return</span> <span class="number">0</span>
    }
  }

  <span class="keyword">var</span> verticalPadding: <span class="type">Double</span> {
    <span class="keyword">switch</span> controlSize {
      <span class="keyword">case</span> .<span class="dotAccess">mini</span>:
        <span class="keyword">return</span> <span class="number">2</span>
      <span class="keyword">case</span> .<span class="dotAccess">small</span>:
        <span class="keyword">return</span> <span class="number">4</span>
      <span class="keyword">case</span> .<span class="dotAccess">regular</span>:
        <span class="keyword">return</span> <span class="number">8</span>
      <span class="keyword">case</span> .<span class="dotAccess">large</span>:
        <span class="keyword">return</span> <span class="number">16</span>
      <span class="keyword">@unknown default</span>:
        <span class="keyword">return</span> <span class="number">0</span>
    }
  }

  <span class="keyword">func</span> foregroundColor(for role: <span class="type">ButtonRole</span>?) -&gt; <span class="type">Color</span>? {
    ...
  }

  <span class="keyword">func</span> color(for role: <span class="type">ButtonRole</span>?) -&gt; <span class="type">Color</span> {
    ...
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/button-styles-2/size.png"/><h2>Control prominence</h2><p>Similar to control size, we also have control prominence, which lets us further emphasize/highlight our buttons:</p><pre><code><span class="type">Button</span>(<span class="string">"Standard"</span>) { ... }
  .<span class="call">controlProminence</span>(.<span class="dotAccess">standard</span>) <span class="comment">// default</span>
<span class="type">Button</span>(<span class="string">"Increased"</span>) { ... }
  .<span class="call">controlProminence</span>(.<span class="dotAccess">increased</span>)
</code></pre><p>We use the environment to retrieve the <code>Prominence</code> value:</p><pre><code><span class="keyword">struct</span> FiveStarsButtonStyle: <span class="type">ButtonStyle</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">controlSize</span>) <span class="keyword">var</span> controlSize: <span class="type">ControlSize</span>
  <span class="keyword">@Environment</span>(\.<span class="property">controlProminence</span>) <span class="keyword">var</span> controlProminence: <span class="type">Prominence</span> <span class="comment">// 👈🏻</span>

  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    configuration
      .<span class="dotAccess">label</span>
      .<span class="call">frame</span>(maxWidth: controlSize == .<span class="dotAccess">large</span> ? .<span class="dotAccess">infinity</span> : <span class="keyword">nil</span>)
      .<span class="call">padding</span>(.<span class="dotAccess">horizontal</span>, horizontalPadding)
      .<span class="call">padding</span>(.<span class="dotAccess">vertical</span>, verticalPadding)
      .<span class="call">foregroundColor</span>(<span class="call">foregroundColor</span>(for: configuration.<span class="property">role</span>))
      .<span class="call">background</span> {
        <span class="call">backgroundColor</span>(for: configuration.<span class="property">role</span>).<span class="call">cornerRadius</span>(<span class="number">8</span>)
        <span class="keyword">if let</span> borderColor = <span class="call">borderColor</span>(for: configuration.<span class="property">role</span>) {
          <span class="type">RoundedRectangle</span>(cornerRadius: <span class="number">8</span>)
            .<span class="call">strokeBorder</span>(borderColor, lineWidth: <span class="number">2</span>)
        }
      }
      .<span class="call">opacity</span>(configuration.<span class="property">isPressed</span> ? <span class="number">0.5</span> : <span class="number">1</span>)
  }

  <span class="keyword">var</span> horizontalPadding: <span class="type">Double</span> {
    ...
  }

  <span class="keyword">var</span> verticalPadding: <span class="type">Double</span> {
    ...
  }

  <span class="keyword">func</span> foregroundColor(for role: <span class="type">ButtonRole</span>?) -&gt; <span class="type">Color</span>? {
    <span class="keyword">switch</span> (controlProminence, role) {
      <span class="keyword">case</span> (.<span class="dotAccess">standard</span>, .<span class="dotAccess">destructive</span>?):
        <span class="keyword">return</span> .<span class="dotAccess">red</span>
      <span class="keyword">case</span> (.<span class="dotAccess">increased</span>, .<span class="dotAccess">destructive</span>?):
        <span class="keyword">return</span> .<span class="dotAccess">white</span>
      <span class="keyword">case</span> (.<span class="dotAccess">increased</span>, <span class="keyword">_</span>):
        <span class="keyword">return</span> .<span class="dotAccess">black</span>
      <span class="keyword">case</span> (<span class="keyword">_</span>, <span class="keyword">_</span>):
        <span class="keyword">return nil</span>
    }
  }

  <span class="keyword">func</span> backgroundColor(for role: <span class="type">ButtonRole</span>?) -&gt; <span class="type">Color</span>? {
    <span class="keyword">switch</span> controlProminence {
      <span class="keyword">case</span> .<span class="dotAccess">increased</span>:
        <span class="keyword">return</span> <span class="call">color</span>(for: role)
      <span class="keyword">case</span> .<span class="dotAccess">standard</span>:
        <span class="keyword">fallthrough
      @unknown default</span>:
        <span class="keyword">return nil</span>
    }
  }

  <span class="keyword">func</span> color(for role: <span class="type">ButtonRole</span>?) -&gt; <span class="type">Color</span> {
    <span class="keyword">switch</span> role {
      <span class="keyword">case</span> .<span class="dotAccess">cancel</span>?, .<span class="dotAccess">destructive</span>?:
        <span class="keyword">return</span> <span class="type">Color</span>.<span class="property">red</span>
      <span class="keyword">default</span>:
        <span class="keyword">return</span> <span class="type">Color</span>.<span class="property">accentColor</span>
    }
  }

  <span class="keyword">func</span> borderColor(for role: <span class="type">ButtonRole</span>?) -&gt; <span class="type">Color</span>? {
    <span class="keyword">switch</span> controlProminence {
      <span class="keyword">case</span> .<span class="dotAccess">standard</span>:
        <span class="keyword">return</span> <span class="call">color</span>(for: role)
      <span class="keyword">case</span> .<span class="dotAccess">increased</span>:
        <span class="keyword">fallthrough
      @unknown default</span>:
        <span class="keyword">return nil</span>
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/button-styles-2/prominence.png"/><h2>Passing parameters</h2><p>With the old API, it was easy to pass parameters to our styles. Let's say that we want to add a <code>cornerRadius</code> parameter to our style, to be used for the round corners:</p><pre><code><span class="keyword">struct</span> FiveStarsButtonStyle: <span class="type">ButtonStyle</span> {
  <span class="keyword">let</span> cornerRadius: <span class="type">Double</span>
  ...
}
</code></pre><p>With our new modern declaration, this is not possible:</p><pre><code><span class="keyword">extension</span> <span class="type">ButtonStyle</span> <span class="keyword">where</span> <span class="type">Self</span> == <span class="type">FiveStarsButtonStyle</span> {
  <span class="keyword">static var</span> fiveStars: <span class="type">FiveStarsButtonStyle</span> {
    <span class="type">FiveStarsButtonStyle</span>() <span class="comment">// 👈🏻 we need to pass our cornerRadius here</span>
  }
}
</code></pre><p>However, no body forbids us to further expand this extension and declare a static function:</p><pre><code><span class="keyword">extension</span> <span class="type">ButtonStyle</span> <span class="keyword">where</span> <span class="type">Self</span> == <span class="type">FiveStarsButtonStyle</span> {
  <span class="keyword">static func</span> fiveStars(cornerRadius: <span class="type">Double</span> = <span class="number">8</span>) -&gt; <span class="type">FiveStarsButtonStyle</span> {
    <span class="type">FiveStarsButtonStyle</span>(cornerRadius: cornerRadius)
  }
}
</code></pre><p>Which we can use as following:</p><pre><code><span class="type">Button</span>(<span class="string">"Radius 12"</span>) { 
  ...
}
.<span class="call">buttonStyle</span>(.<span class="call">fiveStars</span>(cornerRadius: <span class="number">12</span>))

<span class="type">Button</span>(<span class="string">"Default radius"</span>) { 
  ...
}
.<span class="call">buttonStyle</span>(.<span class="call">fiveStars</span>())
</code></pre><img src="https://www.fivestars.blog/assets/posts/button-styles-2/corner.png"/><p>Since we have a default value, we can even keep the original static property:</p><pre><code><span class="keyword">extension</span> <span class="type">ButtonStyle</span> <span class="keyword">where</span> <span class="type">Self</span> == <span class="type">FiveStarsButtonStyle</span> {
  <span class="keyword">static func</span> fiveStars(cornerRadius: <span class="type">Double</span> = <span class="number">8</span>) -&gt; <span class="type">FiveStarsButtonStyle</span> {
    <span class="type">FiveStarsButtonStyle</span>(cornerRadius: cornerRadius)
  }

  <span class="keyword">static var</span> fiveStars: <span class="type">FiveStarsButtonStyle</span> {
    <span class="call">fiveStars</span>()
  }
}
</code></pre><p>Which lets us use the more compact API when we want to use the default behavior:</p><pre><code><span class="type">Button</span>(<span class="string">"Tap me"</span>) { 
  ...
}
.<span class="call">buttonStyle</span>(.<span class="call">fiveStars</span>(cornerRadius: <span class="number">12</span>))

<span class="type">Button</span>(<span class="string">"Tap me"</span>) { 
  ...
}
.<span class="call">buttonStyle</span>(.<span class="dotAccess">fiveStars</span>) <span class="comment">// 👈🏻</span>
</code></pre><p>An alternative approach is to use environment values, in <code>FiveStarsButtonStyle</code> we're using <code>Color.accentColor</code> for example, if we'd like to change that value, we can do so via the <code>accentColor(_:)</code> modifier:</p><pre><code><span class="type">Button</span>(<span class="string">"Default accent"</span>) { 
  ...
}
.<span class="call">buttonStyle</span>(.<span class="dotAccess">fiveStars</span>)
.<span class="call">controlProminence</span>(.<span class="dotAccess">increased</span>)

<span class="type">Button</span>(<span class="string">"Purple accent"</span>) { 
  ...
}
.<span class="call">buttonStyle</span>(.<span class="dotAccess">fiveStars</span>)
.<span class="call">accentColor</span>(.<span class="dotAccess">purple</span>) <span class="comment">// 👈🏻</span>
.<span class="call">controlProminence</span>(.<span class="dotAccess">increased</span>)
</code></pre><img src="https://www.fivestars.blog/assets/posts/button-styles-2/accent.png"/><blockquote><p><code>.accentColor(_:)</code> has been deprecated in favor of the new <code>tint(_:)</code> modifier. However, as of Xcode 13b2, it doesn't work as expected (FB9248929).</p></blockquote><h2>Etcetera</h2><img src="https://www.fivestars.blog/assets/posts/button-styles-2/gist.png"/><p>So far, <code>FiveStarsButtonStyle</code> only considers the new Xcode 13 SwiftUI additions. However, there's more that we can take care of, such as dynamic types, <code>isEnabled</code> state, platform-specific styles, etc.</p><blockquote><p>Reminder: just because we have these powerful tools at our disposal, it doesn't mean that we <em>have to</em> support them all in our apps/styles. Add support to what makes sense to your product(s).</p></blockquote><p>A more complete example is available <a href="https://github.com/FiveStarsBlog/CodeSamples/tree/1fbcac192e1ef9ea3ffb9f619f14e02e5c51a28b/Button-Styles">here</a>.</p><h2>Conclusions</h2><p>We built a reasonably advanced style with dynamics based on the button definition, environment values, etc. However, when it's time to use it, all we have to do is call <code>.buttonStyle(.fiveStars)</code>.</p><p>This whole complexity becomes an implementation detail hidden behind a style name: developers using our style don't need to worry about any of that, and the goal is beautifully achieved thanks to SwiftUI styles.</p><p>Do you use custom styles in your apps? Are you going to take advantage of the new roles and control properties? Let me know via <a href="mailto:hello@fivestars.blog">email</a> or <a href="http://twitter.com/zntfdr">Twitter</a>!</p><blockquote><p>This article is part of a series exploring new SwiftUI features. We will cover many more during the rest of the summer: subscribe to Five Stars's <a href="https://www.fivestars.blog/feed.rss">feed RSS</a> or follow <a href="http://twitter.com/FiveStarsBlog">@FiveStarsBlog</a> on Twitter to never miss new content!</p></blockquote>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-patterns-view-builders</guid><title>SwiftUI patterns evolution: view builders</title><description>We're now at the third big SwiftUI iteration, let's see a new trend with view builders</description><link>https://www.fivestars.blog/articles/swiftui-patterns-view-builders</link><pubDate>Tue, 29 Jun 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>With WWDC21, SwiftUI has officially entered its third iteration. Big changes also bring pattern shifts: let's see how SwiftUI itself has further embraced view builders.</p><h2>Generic views</h2><p>In <a href="https://www.fivestars.blog/articles/swiftui-patter-passing-views/">SwiftUI patterns: passing &amp; accepting views</a> we've explored how SwiftUI accepts views to embed within other views.<br>One of the main ways was accepting a generic view, used for example in <code>Section</code>'s <code>header</code> and <code>footer</code> parameters:</p><pre><code><span class="keyword">extension</span> <span class="type">Section</span> <span class="keyword">where</span> <span class="type">Parent</span>: <span class="type">View</span>, <span class="type">Content</span>: <span class="type">View</span>, <span class="type">Footer</span>: <span class="type">View</span> {

  <span class="comment">/// Creates a section with a header, footer, and the provided section content.
  /// - Parameters:
  ///   - header: A view to use as the section's header.
  ///   - footer: A view to use as the section's footer.
  ///   - content: The section's content.</span>
  <span class="keyword">public init</span>(header: <span class="type">Parent</span>, footer: <span class="type">Footer</span>, <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>)
}
</code></pre><p>To be used as:</p><pre><code><span class="type">Section</span>(
  header: <span class="type">Text</span>(<span class="string">"This is a header"</span>), 
  footer: <span class="type">Text</span>(<span class="string">"This is a footer"</span>)
) {
  <span class="comment">// section content</span>
}
</code></pre><p>The original idea of this API was probably to indicate that <code>Section</code> expects a single simple view for those parameters. However, this limitation was easily bypassed, for example by using a <code>Group</code> or another view:</p><pre><code><span class="type">Section</span>(
  header: <span class="type">Group</span> {
    <span class="type">Text</span>(<span class="string">"This is not a simple header"</span>)
    <span class="type">ForEach</span>(<span class="number">1</span>..&lt;<span class="number">100</span>) { <span class="keyword">_ in</span>
      <span class="type">Text</span>(<span class="string">"Five"</span>)
    }
  }, 
  footer: <span class="type">Group</span> {
    <span class="type">Text</span>(<span class="string">"This is not a simple footer"</span>)
    <span class="type">ForEach</span>(<span class="number">1</span>..&lt;<span class="number">100</span>) { <span class="keyword">_ in</span>
      <span class="type">Text</span>(<span class="string">"Stars"</span>)
    }
  }
) {
  <span class="comment">// section content</span>
}

<span class="comment">// or</span> 

<span class="type">Section</span>(
  header: <span class="type">VeryComplicatedViewAsHeader</span>(), 
  footer: <span class="type">VeryComplicatedViewAsFooter</span>()
) {
  <span class="comment">// section content</span>
}
</code></pre><p>Moreover, accepting a generic view made things awkward in case we wanted to "just" add a condition:</p><pre><code><span class="type">Section</span>(
  header: <span class="type">Group</span> {
    <span class="keyword">if</span> shouldShowHeader {
      <span class="type">Text</span>(<span class="string">"This is a header"</span>)
    }
  },
  footer: <span class="type">Text</span>(<span class="string">"This is a footer"</span>)
) {
  <span class="comment">// section content</span>
}
</code></pre><p>New this year, all APIs accepting a generic view parameter have been deprecated in favor of new alternatives using view builders:</p><pre><code><span class="keyword">@available</span>(iOS <span class="number">15.0</span>, macOS <span class="number">12.0</span>, tvOS <span class="number">15.0</span>, watchOS <span class="number">8.0</span>, *)
<span class="keyword">extension</span> <span class="type">Section</span> <span class="keyword">where</span> <span class="type">Parent</span>: <span class="type">View</span>, <span class="type">Content</span>: <span class="type">View</span>, <span class="type">Footer</span>: <span class="type">View</span> {

  <span class="comment">/// Creates a section with a header, footer, and the provided section
  /// content.
  ///
  /// - Parameters:
  ///   - content: The section's content.
  ///   - header: A view to use as the section's header.
  ///   - footer: A view to use as the section's footer.</span>
  <span class="keyword">public init</span>(
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>, 
    <span class="keyword">@ViewBuilder</span> header: () -&gt; <span class="type">Parent</span>, 
    <span class="keyword">@ViewBuilder</span> footer: () -&gt; <span class="type">Footer</span>
  )
}
</code></pre><p>Which make all previous workarounds obsolete:</p><pre><code><span class="type">Section</span> {
  <span class="comment">// section content</span>
} header: {
  <span class="keyword">if</span> shouldShowHeader {
    <span class="type">Text</span>(<span class="string">"this is a header"</span>)
  }
} footer: {
  <span class="type">Text</span>(<span class="string">"this is a footer"</span>)
}
</code></pre><p>Views affected:</p><ul><li><code>Section</code></li></ul><pre><code><span class="keyword">extension</span> <span class="type">Section</span> <span class="keyword">where</span> <span class="type">Parent</span>: <span class="type">View</span>, <span class="type">Content</span>: <span class="type">View</span>, <span class="type">Footer</span>: <span class="type">View</span> {
  <span class="comment">// From:</span>
  <span class="keyword">public init</span>(
    header: <span class="type">Parent</span>, <span class="comment">// 👈🏻 1</span>
    footer: <span class="type">Footer</span>, <span class="comment">// 👈🏻 2</span>
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>
  )

  <span class="comment">// To:</span>
  <span class="keyword">public init</span>(
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>, 
    <span class="keyword">@ViewBuilder</span> header: () -&gt; <span class="type">Parent</span>, <span class="comment">// 👈🏻 1</span>
    <span class="keyword">@ViewBuilder</span> footer: () -&gt; <span class="type">Footer</span> <span class="comment">// 👈🏻 2</span>
  )
}
</code></pre><ul><li><code>Picker</code>:</li></ul><pre><code><span class="keyword">extension</span> <span class="type">Picker</span> {
  <span class="comment">// From:</span>
  <span class="keyword">public init</span>(
    selection: <span class="type">Binding</span>&lt;<span class="type">SelectionValue</span>&gt;, 
    label: <span class="type">Label</span>, <span class="comment">// 👈🏻</span>
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>
  )

  <span class="comment">// To:</span>
  <span class="keyword">public init</span>(
    selection: <span class="type">Binding</span>&lt;<span class="type">SelectionValue</span>&gt;, 
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>, 
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span> <span class="comment">// 👈🏻</span>
  )
}
</code></pre><ul><li><code>Slider</code>:</li></ul><pre><code><span class="keyword">extension</span> <span class="type">Slider</span> {
  <span class="comment">// From:</span>
  <span class="keyword">public init</span>&lt;V&gt;(
    value: <span class="type">Binding</span>&lt;<span class="type">V</span>&gt;, 
    <span class="keyword">in</span> bounds: <span class="type">ClosedRange</span>&lt;<span class="type">V</span>&gt; = <span class="number">0</span>...<span class="number">1</span>, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }, 
    minimumValueLabel: <span class="type">ValueLabel</span>, <span class="comment">// 👈🏻 1</span>
    maximumValueLabel: <span class="type">ValueLabel</span>, <span class="comment">// 👈🏻 2</span>
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>
  ) <span class="keyword">where</span> <span class="type">V</span>: <span class="type">BinaryFloatingPoint</span>, <span class="type">V</span>.<span class="type">Stride</span>: <span class="type">BinaryFloatingPoint</span>

  <span class="comment">// To:</span>
  <span class="keyword">public init</span>&lt;V&gt;(
    value: <span class="type">Binding</span>&lt;<span class="type">V</span>&gt;, 
    <span class="keyword">in</span> bounds: <span class="type">ClosedRange</span>&lt;<span class="type">V</span>&gt; = <span class="number">0</span>...<span class="number">1</span>, 
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>, 
    <span class="keyword">@ViewBuilder</span> minimumValueLabel: () -&gt; <span class="type">ValueLabel</span>, <span class="comment">// 👈🏻 1</span>
    <span class="keyword">@ViewBuilder</span> maximumValueLabel: () -&gt; <span class="type">ValueLabel</span>, <span class="comment">// 👈🏻 2</span>
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }
  ) <span class="keyword">where</span> <span class="type">V</span>: <span class="type">BinaryFloatingPoint</span>, <span class="type">V</span>.<span class="type">Stride</span>: <span class="type">BinaryFloatingPoint</span>
}
</code></pre><blockquote><p>Interestingly, <code>Stepper</code> already used the view builders approach from the start. It clearly was ahead of its time!</p></blockquote><ul><li><code>GroupBox</code>:</li></ul><pre><code><span class="keyword">extension</span> <span class="type">GroupBox</span> {
  <span class="comment">// From:</span>
  <span class="keyword">public init</span>(
    label: <span class="type">Label</span>, <span class="comment">// 👈🏻</span>
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>
  )

  <span class="comment">// To:</span>
  <span class="keyword">public init</span>(
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>, 
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span> <span class="comment">// 👈🏻</span>
  )
}
</code></pre><ul><li><code>NavigationLink</code>:</li></ul><pre><code><span class="keyword">extension</span> <span class="type">NavigationLink</span> {
  <span class="comment">// From:</span>
  <span class="keyword">public init</span>(
    destination: <span class="type">Destination</span>, <span class="comment">// 👈🏻</span>
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>
  )

  <span class="comment">// To:</span>
  <span class="keyword">public init</span>(
    <span class="keyword">@ViewBuilder</span> destination: () -&gt; <span class="type">Destination</span>, <span class="comment">// 👈🏻</span>
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>
  )
</code></pre><blockquote><p>Unfortunately, this was the only change made to <code>NavigationLink</code> (FB8722348, FB8910787, FB8997675, FB9197698).</p></blockquote><h3>View Modifiers</h3><p>This shift is not limited to just views. Modifiers have been updated as well:</p><ul><li><code>background</code> and <code>overlay</code>:</li></ul><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="comment">// From:</span>
  <span class="keyword">@inlinable public func</span> background&lt;Background: <span class="type">View</span>&gt;(
    <span class="keyword">_</span> background: <span class="type">Background</span>, <span class="comment">// 👈🏻</span>
    alignment: <span class="type">Alignment</span> = .<span class="dotAccess">center</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span>

  <span class="comment">// To:</span>
  <span class="keyword">@inlinable public func</span> background&lt;V: <span class="type">View</span>&gt;(
    alignment: <span class="type">Alignment</span> = .<span class="dotAccess">center</span>, 
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">V</span> <span class="comment">// 👈🏻</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span>
}

<span class="keyword">extension</span> <span class="type">View</span> {
  <span class="comment">// From:</span>
  <span class="keyword">@inlinable public func</span> overlay&lt;Overlay: <span class="type">View</span>&gt;(
    <span class="keyword">_</span> overlay: <span class="type">Overlay</span>, <span class="comment">// 👈🏻</span>
    alignment: <span class="type">Alignment</span> = .<span class="dotAccess">center</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span>

  <span class="comment">// To:</span>
  <span class="keyword">@inlinable public func</span> overlay&lt;V: <span class="type">View</span>&gt;(
    alignment: <span class="type">Alignment</span> = .<span class="dotAccess">center</span>, 
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">V</span> <span class="comment">// 👈🏻</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span>
}
</code></pre><ul><li><code>mask</code>:</li></ul><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="type">From</span>:
  <span class="keyword">@inlinable public func</span> mask&lt;Mask: <span class="type">View</span>&gt;(
    <span class="keyword">_</span> mask: <span class="type">Mask</span> <span class="comment">// 👈🏻</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View

  To</span>:
  <span class="keyword">@inlinable public func</span> mask&lt;Mask: <span class="type">View</span>&gt;(
    alignment: <span class="type">Alignment</span> = .<span class="dotAccess">center</span>, <span class="comment">// 🆕</span>
    <span class="keyword">@ViewBuilder _</span> mask: () -&gt; <span class="type">Mask</span> <span class="comment">// 👈🏻</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span>
}
</code></pre><ul><li><code>contextMenu</code>:</li></ul><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="comment">// From:</span>
  <span class="keyword">public func</span> contextMenu&lt;MenuItems: <span class="type">View</span>&gt;(
    <span class="keyword">_</span> contextMenu: <span class="type">ContextMenu</span>&lt;<span class="type">MenuItems</span>&gt;? <span class="comment">// 👈🏻</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span>

  <span class="comment">// To:</span>
  <span class="keyword">public func</span> contextMenu&lt;MenuItems: <span class="type">View</span>&gt;(
    <span class="keyword">@ViewBuilder</span> menuItems: () -&gt; <span class="type">MenuItems</span> <span class="comment">// 👈🏻</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span>
</code></pre><blockquote><p>The <code>ContextMenu</code> view has been deprecated altogether.</p></blockquote><h3>Parameters order</h3><p>Careful readers might have noticed already, but the parameters <em>upgrade</em> to view builders was not the only change in the definitions above. Most new declarations also present a different parameter order.</p><p>To understand why, let's have a look at <code>Picker</code> as an example. As a reminder, here are the old/new APIs:</p><pre><code><span class="keyword">extension</span> <span class="type">Picker</span> {
  <span class="comment">// From:</span>
  <span class="keyword">public init</span>(
    selection: <span class="type">Binding</span>&lt;<span class="type">SelectionValue</span>&gt;, 
    label: <span class="type">Label</span>, <span class="comment">// 👈🏻</span>
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>
  )

  <span class="comment">// To:</span>
  <span class="keyword">public init</span>(
    selection: <span class="type">Binding</span>&lt;<span class="type">SelectionValue</span>&gt;, 
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>, 
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span> <span class="comment">// 👈🏻</span>
  )
}
</code></pre><p>And here's a <code>Picker</code> defined with the legacy API:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">enum</span> PizzaTopping: <span class="type">String</span>, <span class="type">CaseIterable</span>, <span class="type">Identifiable</span> {
    <span class="keyword">case</span> 🍍, 🍄, 🫒, 🐓
    <span class="keyword">var</span> id: <span class="type">String</span> { rawValue }
  }

  <span class="keyword">@State var</span> flavor: <span class="type">PizzaTopping</span> = .🍍

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">Form</span> {
        <span class="type">Picker</span>(
          selection: $flavor,
          label: <span class="type">Text</span>(<span class="string">"Pick your topping"</span>)
        ) {
          <span class="type">ForEach</span>(<span class="type">PizzaTopping</span>.<span class="property">allCases</span>) { topping <span class="keyword">in</span>
            <span class="type">Text</span>(topping.<span class="property">rawValue</span>)
              .<span class="call">tag</span>(topping)
          }
        }
      }
    }
  }
}
</code></pre><p>Let's focus on the <code>Picker</code> declaration:</p><pre><code><span class="type">Picker</span>(
  selection: $flavor,
  label: <span class="type">Text</span>(<span class="string">"Pick your topping"</span>)
) {
  <span class="type">ForEach</span>(<span class="type">PizzaTopping</span>.<span class="property">allCases</span>) { topping <span class="keyword">in</span>
    <span class="type">Text</span>(topping.<span class="property">rawValue</span>)
      .<span class="call">tag</span>(topping)
  }
}
</code></pre><p>If the SwiftUI team only changed the <code>label</code> parameter type to a view builder without reordering the parameters, we would have the following declaration:</p><pre><code><span class="type">Picker</span>(selection: $flavor) {
  <span class="type">Text</span>(<span class="string">"Pick your topping"</span>)
} content: {
  <span class="type">ForEach</span>(<span class="type">PizzaTopping</span>.<span class="property">allCases</span>) { topping <span class="keyword">in</span>
    <span class="type">Text</span>(topping.<span class="property">rawValue</span>)
      .<span class="call">tag</span>(topping)
  }
}
</code></pre><p>We have two trailing closures defining two views: it's clear what the second closure is, labeled <code>content</code>, but what about <code>Text("Pick your topping")</code>?</p><p>It's easy to answer now, but it won't be in a few months, or for somebody else reviewing our code today: is that a prompt? a suggestion? an accessibility description?</p><p>Let's now use the actual new initializer definition:</p><pre><code><span class="type">Picker</span>(selection: $flavor) {
  <span class="type">ForEach</span>(<span class="type">PizzaTopping</span>.<span class="property">allCases</span>) { topping <span class="keyword">in</span>
    <span class="type">Text</span>(topping.<span class="property">rawValue</span>)
      .<span class="call">tag</span>(topping)
  }
} label: {
  <span class="type">Text</span>(<span class="string">"Pick your topping"</span>)
}
</code></pre><p>This is much clearer. The first closure contains the main definition for <code>Picker</code>, while the second closure defines the view label, as we can tell from the closure name.</p><p>On a similar note, view modifiers have also changed order, in this case to gain the trailing closure syntax.<br>Let's take <code>background</code> as an example:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="comment">// From:</span>
  <span class="keyword">@inlinable public func</span> background&lt;Background: <span class="type">View</span>&gt;(
    <span class="keyword">_</span> background: <span class="type">Background</span>, <span class="comment">// 👈🏻</span>
    alignment: <span class="type">Alignment</span> = .<span class="dotAccess">center</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span>

  <span class="comment">// To:</span>
  <span class="keyword">@inlinable public func</span> background&lt;V: <span class="type">View</span>&gt;(
    alignment: <span class="type">Alignment</span> = .<span class="dotAccess">center</span>, 
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">V</span> <span class="comment">// 👈🏻</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span>
} 
</code></pre><p>Since the <code>content</code> parameter has shifted to the last parameter, we can take advantage of the trailing closure syntax:</p><pre><code>mainView
  .<span class="call">background</span>(alignment: .<span class="dotAccess">bottom</span>) {
    backgroundView
  }
</code></pre><p>Which is much better than if the view parameter stayed in its original position:</p><pre><code>mainView
  .<span class="call">background</span>(
    content: {
      backgroundView
    },
    alignment: .<span class="dotAccess">bottom</span>
  )
</code></pre><h3>Backport to early OSes</h3><p>Unfortunately, the new APIs are supported only in upcoming OSes (iOS 15+, macOS 12+, tvOS 15+, watchOS 8+).</p><p>However, besides the initializer definition, the views and modifiers behavior is unchanged:<br>we can make our own extensions, mocking the new API, and making them compatible all the way to iOS 13 (or equivalent for other platforms).</p><p>The following is a backport example for <code>Section</code>, we can use the same approach with all other views/modifiers:</p><pre><code><span class="keyword">@available</span>(iOS, introduced: <span class="number">13.0</span>, deprecated: <span class="number">15.0</span>, 
           message: <span class="string">"Delete this extension, as it's no longer necessary in iOS 15+"</span>)
<span class="keyword">@available</span>(macOS, introduced: <span class="number">10.15</span>, deprecated: <span class="number">12.0</span>, 
           message: <span class="string">"Delete this extension, as it's no longer necessary in macOS 12+"</span>)
<span class="keyword">@available</span>(tvOS, introduced: <span class="number">13.0</span>, deprecated: <span class="number">15.0</span>, 
           message: <span class="string">"Delete this extension, as it's no longer necessary in tvOS 15+"</span>)
<span class="keyword">@available</span>(watchOS, introduced: <span class="number">6.0</span>, deprecated: <span class="number">8.0</span>, 
           message: <span class="string">"Delete this extension, as it's no longer necessary in watchOS 8+"</span>)
<span class="keyword">extension</span> <span class="type">Section</span> <span class="keyword">where</span> <span class="type">Parent</span>: <span class="type">View</span>, <span class="type">Content</span>: <span class="type">View</span>, <span class="type">Footer</span>: <span class="type">View</span> {
  <span class="comment">/// Creates a section with a header, footer, and the provided section
  /// content.
  ///
  /// - Parameters:
  ///   - content: The section's content.
  ///   - header: A view to use as the section's header.
  ///   - footer: A view to use as the section's footer.</span>
  <span class="keyword">public init</span>(
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>,
    <span class="keyword">@ViewBuilder</span> header: () -&gt; <span class="type">Parent</span>,
    <span class="keyword">@ViewBuilder</span> footer: () -&gt; <span class="type">Footer</span>
  ) {
    <span class="keyword">self</span>.<span class="keyword">init</span>(header: <span class="call">header</span>(), footer: <span class="call">footer</span>(), content: content)
  }
}
</code></pre><p>Because this extension matches 1-1 the new APIs, once we drop support for older OSes, we can simply delete this extension, and Xcode will automatically start using SwiftUI's new initializer. No further change necessary.</p><blockquote><p>Since we wrote the extension, we can start using the new pattern right away, even before Xcode 13 is officially released.</p></blockquote><h2>Main takeaways</h2><ul><li>Accepting views via a generic view is a deprecated pattern in SwiftUI, accept view builders instead</li><li>Reorder your closure parameters for the best API use experience:<ul><li>the main view <code>@ViewBuilder</code> parameter comes first, labels and secondary view closures come later</li><li>enable trailing closures syntax when possible</li><li>if your view accepts <code>@escaping</code> closures to be called on associated events, these come after the view builders</li></ul></li></ul><h2>Conclusions</h2><p>When Swift came out, every <em>early</em> major version brought big disrupting changes.<br>The SwiftUI team has clearly learned from those <em>painful</em> experiences: a SwiftUI project built in 2019 compiles fine today in Xcode 13.</p><p>While SwiftUI APIs have been backward-compatible, this doesn't mean that the framework hasn't changed, quite the contrary! SwiftUI has and continues to evolve in this third iteration. Let's see where SwiftUI brings us next!</p><p>What other new patterns have you noticed in Xcode 13? Let me know via <a href="mailto:hello@fivestars.blog">email</a> or <a href="https://twitter.com/zntfdr">Twitter</a>!</p><blockquote><p>This article is part of a series exploring new SwiftUI features. We will cover many more during the rest of the summer: subscribe to Five Stars's <a href="https://www.fivestars.blog/feed.rss">feed RSS</a> or follow <a href="https://twitter.com/fiveStarsBlog">@FiveStarsBlog</a> on Twitter to never miss new content!</p></blockquote>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/xcode-13-beta-2</guid><title>What's new in Xcode 13 beta 2</title><description>A quick look at all the new SwiftUI goodies!</description><link>https://www.fivestars.blog/articles/xcode-13-beta-2</link><pubDate>Fri, 25 Jun 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Xcode 13 beta 2 has just been released, and it comes with new SwiftUI goodies: let's have a look at what's new!</p><h2>textSelection</h2><img src="https://www.fivestars.blog/assets/posts/xcode-13-beta-2/textselection.png"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> { 
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> { 
    <span class="type">Text</span>(<span class="string">"FIVE STARS"</span>)
      .<span class="call">font</span>(.<span class="dotAccess">largeTitle</span>)
      .<span class="call">textSelection</span>(.<span class="dotAccess">enabled</span>)
  }
}
</code></pre><p>The <a href="https://developer.apple.com/documentation/swiftui/view/textselection(_:)"><code>textSelection(_:)</code></a> modifier is now available on iOS, this view modifier allows users to select/copy the content of <code>Text</code> views.</p><h2>Mini ControlSize</h2><img src="https://www.fivestars.blog/assets/posts/xcode-13-beta-2/mini.png"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> { 
    <span class="type">VStack</span> { 
      <span class="type">ForEach</span>(<span class="type">ControlSize</span>.<span class="property">allCases</span>, id: \.<span class="keyword">self</span>) { size <span class="keyword">in</span> 
        <span class="type">Button</span> {
        } label: {
          <span class="type">Text</span>(<span class="type">String</span>(describing: size))
        }
        .<span class="call">controlSize</span>(size)
        .<span class="call">controlSize</span>(size) 
      }
    }
    .<span class="call">buttonStyle</span>(.<span class="dotAccess">bordered</span>)
    .<span class="call">controlProminence</span>(.<span class="dotAccess">increased</span>) 
  }
}
</code></pre><p>We have a new <a href="https://developer.apple.com/documentation/swiftui/controlsize/mini?changes=l_5"><code>.mini</code></a> <a href="https://developer.apple.com/documentation/swiftui/controlsize?changes=l_5"><code>ControlSize</code></a>:<br>it doesn't seem to be different than the <code>.small</code> size in this specific example, but it might in other systems/contexts.</p><blockquote><p>As of Xcode 13b2, <code>.mini</code> is not part of <a href="https://developer.apple.com/documentation/swiftui/controlsize/allcases-swift.type.property?changes=l_5"><code>ControlSize.allCases</code></a> (FB9208000). Update: Xcode 13b3 fixes this issue.</p></blockquote><h2>dismissSearch</h2><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> query = <span class="string">""</span>
  <span class="keyword">@Environment</span>(\.<span class="property">dismissSearch</span>) <span class="keyword">private var</span> dismissSearch

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">List</span> {
        <span class="comment">// ...</span>
      }
      .<span class="call">searchable</span>(text: $query) {
      }
      .<span class="call">toolbar</span> {
        <span class="type">Button</span>(<span class="string">"Cancel"</span>) {
          <span class="call">dismissSearch</span>()
        }
      }
    }
  }
}
</code></pre><p>There's a new <a href="https://developer.apple.com/documentation/swiftui/environmentvalues/dismisssearch"><code>dismissSearch</code></a> environment value which, when triggered:</p><ul><li>will set <code>isSearching</code> to <code>false</code></li><li>any search text will be cleared</li><li>any search field will lose focus</li></ul><h2>presentationMode deprecation</h2><pre><code><span class="keyword">extension</span> <span class="type">EnvironmentValues</span> {
  <span class="keyword">@available</span>(iOS, introduced: <span class="number">13.0</span>, deprecated: <span class="number">100000.0</span>, message: <span class="string">"Use isPresented or dismiss"</span>)
  <span class="keyword">@available</span>(macOS, introduced: <span class="number">10.15</span>, deprecated: <span class="number">100000.0</span>, message: <span class="string">"Use isPresented or dismiss"</span>)
  <span class="keyword">@available</span>(tvOS, introduced: <span class="number">13.0</span>, deprecated: <span class="number">100000.0</span>, message: <span class="string">"Use isPresented or dismiss"</span>)
  <span class="keyword">@available</span>(watchOS, introduced: <span class="number">6.0</span>, deprecated: <span class="number">100000.0</span>, message: <span class="string">"Use isPresented or dismiss"</span>)
  <span class="keyword">public var</span> presentationMode: <span class="type">Binding</span>&lt;<span class="type">PresentationMode</span>&gt; { <span class="keyword">get</span> }
}
</code></pre><p><a href="https://developer.apple.com/documentation/swiftui/presentationmode"><code>presentationMode</code></a> has been soft deprecated in favor of the new <a href="https://developer.apple.com/documentation/swiftui/environmentvalues/ispresented"><code>isPresented</code></a> and <a href="https://developer.apple.com/documentation/swiftui/environmentvalues/dismiss"><code>dismiss</code></a> environment values.</p><blockquote><p>This is something I suggested/mentioned in <a href="https://www.fivestars.blog/articles/swiftui-wwdc21/">What's new in SwiftUI</a>.</p></blockquote><h2>Text format initializer</h2><img src="https://www.fivestars.blog/assets/posts/xcode-13-beta-2/text.png"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Text</span>(<span class="number">0.005</span>, format: .<span class="dotAccess">number</span>)
    <span class="type">Text</span>(<span class="number">5</span>, format: .<span class="dotAccess">number</span>)
    <span class="type">Text</span>(<span class="number">5_000</span>, format: .<span class="dotAccess">number</span>)
    <span class="type">Text</span>(<span class="number">5_000_000</span>, format: .<span class="dotAccess">number</span>)
  }
}
</code></pre><p><code>Text</code> has gained a new <a href="https://developer.apple.com/documentation/swiftui/text/init(_:format:)?changes=latest_beta"><code>init(_:format:)</code></a> initializer, specifically dedicated to formatting values.</p><h2>Searchable Prompt</h2><img src="https://www.fivestars.blog/assets/posts/xcode-13-beta-2/search.png"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> query: <span class="type">String</span> = <span class="string">""</span>
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">List</span> {
        <span class="type">EmptyView</span>()
      }
<span class="comment">//    .searchable("Title", text: $query)  👈🏻 Old way</span>
      .<span class="call">searchable</span>(text: $query, prompt: <span class="string">"Prompt"</span>) <span class="comment">//  👈🏻 New way</span>
      .<span class="call">navigationTitle</span>(<span class="string">"Search"</span>)
    }
  }
}
</code></pre><p>Similar to <code>TextField</code>s, also the <a href="https://developer.apple.com/documentation/SwiftUI/View/searchable(_:text:placement:)-1r1py"><code>searchable</code></a> modifier is now embracing prompts instead of titles.</p><h2>Conclusions</h2><p>iOS 15 is still a few months away: while we might get even more features in upcoming betas, this is the perfect time to test out our apps and <a href="https://feedbackassistant.apple.com">report any beta issue we spot to Apple</a>.</p><p>Did you find anything else interesting in Xcode 13 beta 2? Please <a href="https://twitter.com/zntfdr">let me know</a>!</p><blockquote><p>This article is part of a series exploring new SwiftUI features. We will cover many more during the rest of the summer: subscribe to Five Stars's <a href="https://www.fivestars.blog/feed.rss">feed RSS</a> or follow <a href="http://twitter.com/fiveStarsBlog">@FiveStarsBlog</a> on Twitter to never miss new content!</p></blockquote>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/safe-area-insets-2</guid><title>Backport SwiftUI safe area insets to iOS 13 and 14</title><description>safeAreaInset() is awesome, can we use it in previous iOS versions?</description><link>https://www.fivestars.blog/articles/safe-area-insets-2</link><pubDate>Tue, 22 Jun 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p><code>safeAreaInset()</code> is an iOS 15+ SwiftUI view modifier, empowering us to define views that become part of the observed safe area. We've extensively covered this view modifier in <a href="https://www.fivestars.blog/articles/safe-area-insets/">a previous article</a>: in this article, let's explore how we can use it in older iOS versions.</p><h2>Scroll views</h2><p><code>safeAreaInset()</code> affects the proposed region of the views it's applied to, it shines when applied to scroll views (<a href="https://www.fivestars.blog/articles/safe-area-insets/">see the previous article</a> for a deeper look):</p><table><tbody><tr><td><img src="https://www.fivestars.blog/assets/posts/safe-area-insets/no.gif"/></td><td><img src="https://www.fivestars.blog/assets/posts/safe-area-insets/yes.gif"/></td></tr><tr><td>The button is defined in an <code>overlay()</code></td><td>The button is defined in a <code>safeAreaInset()</code></td></tr></tbody></table><p>When the button is placed as an overlay, it doesn't effect the <code>ScrollView</code> content, meaning that the button will cover the last ScrollView elements.</p><p><code>ScrollView</code> comes with two frames (more details <a href="https://www.fivestars.blog/articles/scrollview-offset/">here</a>):</p><ul><li>the frame layer, used to position the <code>ScrollView</code> itself in the view hierarchy</li><li>the content layer, where all the <code>ScrollView</code> content is placed</li></ul><p>When an edge of the <code>ScrollView</code> frame layer is adjacent to the equivalent safe area edge, the content layer will be displayed beyond the frame layer.<br>This is why, in the images above, we can see some of the content underneath the iPhone's Home Indicator, or in the top safe area, touching the notch.</p><p><code>safeAreaInset()</code> + <code>ScrollView</code> is too good to having to wait until we only support iOS 15+ before start using it, let's see how we can backport it to any iOS compatible with SwiftUI.</p><h2>Overlay</h2><blockquote><p>For simplicity's sake, we will focus on the bottom edge: the same approach works for any other edges.</p></blockquote><p>Since <code>safeAreaInset()</code> is off-limits, we will use an <code>overlay</code> for our "safe area" content:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollView</span> {
      <span class="type">ForEach</span>(<span class="number">1</span>..&lt;<span class="number">60</span>) { <span class="keyword">_ in</span>
        <span class="type">Text</span>(<span class="string">"Five Stars"</span>)
          .<span class="call">font</span>(.<span class="dotAccess">title</span>)
          .<span class="call">frame</span>(maxWidth: .<span class="dotAccess">infinity</span>)
      }
    }
    .<span class="call">overlay</span>(
      <span class="type">Button</span> {
        <span class="comment">// ...</span>
      } label: {
        <span class="type">Text</span>(<span class="string">"Continue"</span>)
          .<span class="call">foregroundColor</span>(.<span class="dotAccess">white</span>)
          .<span class="call">padding</span>()
          .<span class="call">frame</span>(maxWidth: .<span class="dotAccess">infinity</span>)
          .<span class="call">background</span>(<span class="type">Color</span>.<span class="property">accentColor</span>.<span class="call">cornerRadius</span>(<span class="number">8</span>))
          .<span class="call">padding</span>(.<span class="dotAccess">horizontal</span>)
      },
      alignment: .<span class="dotAccess">bottom</span>
    )
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/safe-area-insets-2/original.png"/><p>Both our <code>ScrollView</code> and <code>overlay</code> content don't really matter, let's refactor them out:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollView</span> {
      scrollViewContent
    }
    .<span class="call">overlay</span>(
      overlayContent,
      alignment: .<span class="dotAccess">bottom</span>
    )
  }

  <span class="keyword">var</span> scrollViewContent: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ForEach</span>(<span class="number">1</span>..&lt;<span class="number">60</span>) { <span class="keyword">_ in</span>
      <span class="type">Text</span>(<span class="string">"Five Stars"</span>)
        .<span class="call">font</span>(.<span class="dotAccess">title</span>)
        .<span class="call">frame</span>(maxWidth: .<span class="dotAccess">infinity</span>)
    }
  }

  <span class="keyword">var</span> overlayContent: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span> {
      <span class="comment">// ...</span>
    } label: {
      <span class="type">Text</span>(<span class="string">"Continue"</span>)
        .<span class="call">foregroundColor</span>(.<span class="dotAccess">white</span>)
        .<span class="call">padding</span>()
        .<span class="call">frame</span>(maxWidth: .<span class="dotAccess">infinity</span>)
        .<span class="call">background</span>(<span class="type">Color</span>.<span class="property">accentColor</span>.<span class="call">cornerRadius</span>(<span class="number">8</span>))
        .<span class="call">padding</span>(.<span class="dotAccess">horizontal</span>)
    }
  }
}
</code></pre><p>Great! The first challenge is computing how much extra inset our <code>overlay</code> adds to our <code>ScrollView</code>. Let's tackle this next.</p><h2>Computing the extra inset</h2><p>Our <code>overlay</code>'s extra inset is equivalent the height of our <code>overlayContent</code>.</p><p><a href="https://www.fivestars.blog/articles/scrollview-offset/">In</a> <a href="https://www.fivestars.blog/articles/swiftui-keyboard/">this</a> <a href="https://www.fivestars.blog/articles/content-friendly-layouts/">website</a> <a href="https://www.fivestars.blog/articles/swiftui-protocols/">we've</a> <a href="https://www.fivestars.blog/articles/trucated-text/">used</a> <a href="https://www.fivestars.blog/articles/adaptive-swiftui-views/">multiple</a> <a href="https://www.fivestars.blog/articles/flexible-swiftui/">times</a> <code>readSize(:)</code>, a <code>View</code> extension we defined <a href="https://www.fivestars.blog/articles/swiftui-share-layout-information/">here</a>, letting us read the size of any given view. Here's an adaptation of the same extension, surfacing just the height of the view we're targeting:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">func</span> readHeight(onChange: <span class="keyword">@escaping</span> (<span class="type">CGFloat</span>) -&gt; <span class="type">Void</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">background</span>(
      <span class="type">GeometryReader</span> { geometryProxy <span class="keyword">in</span>
        <span class="type">Spacer</span>()
          .<span class="call">preference</span>(
            key: <span class="type">HeightPreferenceKey</span>.<span class="keyword">self</span>, 
            value: geometryProxy.<span class="property">size</span>.<span class="property">height</span>
          )
      }
    )
    .<span class="call">onPreferenceChange</span>(<span class="type">HeightPreferenceKey</span>.<span class="keyword">self</span>, perform: onChange)
  }
}

<span class="keyword">private struct</span> HeightPreferenceKey: <span class="type">PreferenceKey</span> {
  <span class="keyword">static var</span> defaultValue: <span class="type">CGFloat</span> = .<span class="dotAccess">zero</span>
  <span class="keyword">static func</span> reduce(value: <span class="keyword">inout</span> <span class="type">CGFloat</span>, nextValue: () -&gt; <span class="type">CGFloat</span>) {}
}
</code></pre><p>We can now read the "extra inset" and store it in a <code>@State</code> variable, for example <code>overlayContentHeight</code>:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> overlayContentHeight: <span class="type">CGFloat</span> = <span class="number">0</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollView</span> {
      scrollViewContent
    }
    .<span class="call">overlay</span>(
      overlayContent
        .<span class="call">readHeight</span> {
          overlayContentHeight = $0
        }
      ,
      alignment: .<span class="dotAccess">bottom</span>
    )
  }

  <span class="keyword">var</span> scrollViewContent: <span class="keyword">some</span> <span class="type">View</span> { ... }
  <span class="keyword">var</span> overlayContent: <span class="keyword">some</span> <span class="type">View</span> { ... }
}
</code></pre><p>We now know by how much our <code>ScrollView</code> needs to scroll past the last <code>scrollViewContent</code> element.</p><h2>Spacer</h2><p><code>Spacer</code> is mostly used <a href="https://www.fivestars.blog/articles/stack-spacer-alternatives/">to distribute views within stacks</a>. However, in our challenge, we will use it to add that extra scroll necessary in our <code>ScrollView</code>:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> overlayContentHeight: <span class="type">CGFloat</span> = <span class="number">0</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollView</span> {
      scrollViewContent
      <span class="type">Spacer</span>(minLength: overlayContentHeight)
    }
    .<span class="call">overlay</span>( ... )
  }

  <span class="keyword">var</span> scrollViewContent: <span class="keyword">some</span> <span class="type">View</span> { ... }
  <span class="keyword">var</span> overlayContent: <span class="keyword">some</span> <span class="type">View</span> { ... }
}
</code></pre><p>Vertical <code>Spacer</code>s usually don't take space in vertical <code>ScrollView</code>s: we can enforce them by using their <code>minLenght</code> parameter.</p><p>We've now created our own bottom safe view area inset, compatible with any iOS version!</p><img src="https://www.fivestars.blog/assets/posts/safe-area-insets-2/13.gif"/><h2>A new challenge</h2><p>Our solution works great; however, it's not portable, as there's quite a bit of implementation logic. So let's see what we can do about it.</p><h3>View extension</h3><p>We could create our own view modifier, mocking <code>safeAreaInset</code>:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">func</span> bottomSafeAreaInset&lt;OverlayContent: <span class="type">View</span>&gt;(<span class="keyword">_</span> overlayContent: <span class="type">OverlayContent</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">modifier</span>(<span class="type">BottomInsetViewModifier</span>(overlayContent: overlayContent))
  }
}

<span class="keyword">struct</span> BottomInsetViewModifier&lt;OverlayContent: <span class="type">View</span>&gt;: <span class="type">ViewModifier</span> {
  <span class="keyword">var</span> overlayContent: <span class="type">OverlayContent</span>
  <span class="keyword">@State var</span> overlayContentHeight: <span class="type">CGFloat</span> = <span class="number">0</span>

  <span class="keyword">func</span> body(content: <span class="type">Self</span>.<span class="type">Content</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    content
      .<span class="call">overlay</span>(
        overlayContent
          .<span class="call">readHeight</span> {
            overlayContentHeight = $0
          }
        ,
        alignment: .<span class="dotAccess">bottom</span>
      )
  }
}
</code></pre><p>Which we can use as following:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollView</span> {
      scrollViewContent
    }
    .<span class="call">bottomSafeAreaInset</span>(overlayContent)
  }

  <span class="keyword">var</span> scrollViewContent: <span class="keyword">some</span> <span class="type">View</span> { ... }
  <span class="keyword">var</span> overlayContent: <span class="keyword">some</span> <span class="type">View</span> { ... }
}
</code></pre><p>Nobody is now reading our <code>overlayContentHeight</code>, let's solve that by creating a new <code>bottomSafeAreaInset</code> environment value:</p><pre><code><span class="keyword">struct</span> BottomSafeAreaInsetKey: <span class="type">EnvironmentKey</span> {
  <span class="keyword">static var</span> defaultValue: <span class="type">CGFloat</span> = <span class="number">0</span>
}

<span class="keyword">extension</span> <span class="type">EnvironmentValues</span> {
  <span class="keyword">var</span> bottomSafeAreaInset: <span class="type">CGFloat</span> {
    <span class="keyword">get</span> { <span class="keyword">self</span>[<span class="type">BottomSafeAreaInsetKey</span>.<span class="keyword">self</span>] }
    <span class="keyword">set</span> { <span class="keyword">self</span>[<span class="type">BottomSafeAreaInsetKey</span>.<span class="keyword">self</span>] = newValue }
  }
}
</code></pre><p>Then we can make our new view modifier inject its <code>overlayContentHeight</code> value into the environment:</p><pre><code><span class="keyword">struct</span> BottomInsetViewModifier&lt;OverlayContent: <span class="type">View</span>&gt;: <span class="type">ViewModifier</span> {
  <span class="keyword">var</span> overlayContent: <span class="type">OverlayContent</span>
  <span class="keyword">@State var</span> overlayContentHeight: <span class="type">CGFloat</span> = <span class="number">0</span>

  <span class="keyword">func</span> body(content: <span class="type">Self</span>.<span class="type">Content</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    content
      .<span class="call">environment</span>(\.<span class="property">bottomSafeAreaInset</span>, overlayContentHeight) <span class="comment">// 👈🏻</span>
      .<span class="call">overlay</span>( ... )
  }
}
</code></pre><p>Since our view modifier injects <code>bottomSafeAreaInset</code> into its <code>content</code>, we can now read this value in the scroll view. Let's take advantage of that.</p><h3>ScrollView</h3><p><code>ScrollView</code> doesn't listen to our <code>bottomSafeAreaInset</code> environment value by default, at this point we probably have two possibilities:</p><ul><li>wrap <code>ScrollView</code> into a new view that listens and injects a <code>Spacer</code> with the relevant height</li><li>create a new view for our <code>Spacer</code>, and add it into the <code>ScrollView</code></li></ul><p>This is a matter of taste, let's use the second option here.</p><p>First, we define our new view:</p><pre><code><span class="keyword">struct</span> ExtraBottomSafeAreaInset: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">bottomSafeAreaInset</span>) <span class="keyword">var</span> bottomSafeAreaInset: <span class="type">CGFloat</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Spacer</span>(minLength: bottomSafeAreaInset)
  }
}
</code></pre><p>All this view does is read the <code>bottomSafeAreaInset</code> environment and use it as its <code>Spacer</code> <code>minLength</code> parameter.</p><p>Second, we use this view in our <code>ScrollView</code> content:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollView</span> {
      scrollViewContent
      <span class="type">ExtraBottomSafeAreaInset</span>() <span class="comment">// 👈🏻</span>
    }
    .<span class="call">bottomSafeAreaInset</span>( ... )
  }

  <span class="keyword">var</span> scrollViewContent: <span class="keyword">some</span> <span class="type">View</span> { ... }
  <span class="keyword">var</span> overlayContent: <span class="keyword">some</span> <span class="type">View</span> { ... }
}
</code></pre><p>Now we can easily port this "safe area inset" in as many <code>ScrollView</code>s as we like!</p><h2>Cumulative view modifiers</h2><p>The original <code>safeAreaInset()</code> view modifier was cumulative: we could apply multiple view modifiers to the same view, and the correct safe area would have been applied to the final view.</p><p>For our view modifier to behave the same way, we need to adjust our <code>BottomInsetViewModifier</code> implementation in two aspects:</p><ol><li>pass down a <code>bottomSafeAreaInset</code> value as the sum of its content height and the ancestors' <code>bottomSafeAreaInset</code></li><li>shift its <code>content</code> by its ancestors' <code>bottomSafeAreaInset</code></li></ol><pre><code><span class="keyword">struct</span> BottomInsetViewModifier&lt;OverlayContent: <span class="type">View</span>&gt;: <span class="type">ViewModifier</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">bottomSafeAreaInset</span>) <span class="keyword">var</span> ancestorBottomSafeAreaInset: <span class="type">CGFloat</span>
  <span class="keyword">var</span> overlayContent: <span class="type">OverlayContent</span>
  <span class="keyword">@State var</span> overlayContentHeight: <span class="type">CGFloat</span> = <span class="number">0</span>

  <span class="keyword">func</span> body(content: <span class="type">Self</span>.<span class="type">Content</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    content
      .<span class="call">environment</span>(\.<span class="property">bottomSafeAreaInset</span>, overlayContentHeight + ancestorBottomSafeAreaInset) <span class="comment">// 👈🏻 1</span>
      .<span class="call">overlay</span>(
        overlayContent
          .<span class="call">readHeight</span> {
            overlayContentHeight = $0
          }
          .<span class="call">padding</span>(.<span class="dotAccess">bottom</span>, ancestorBottomSafeAreaInset) <span class="comment">// 👈🏻 2</span>
        ,
        alignment: .<span class="dotAccess">bottom</span>
      )
  }
}
</code></pre><p>With this extra change, we can now apply as many <code>bottomSafeAreaInset</code> as needed, and they all will work as expected:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollView</span> {
      scrollViewContent.<span class="call">border</span>(<span class="type">Color</span>.<span class="property">red</span>)
      <span class="type">ExtraBottomSafeAreaInset</span>()
    }
    .<span class="call">bottomSafeAreaInset</span>(overlayContent)
    .<span class="call">bottomSafeAreaInset</span>(overlayContent)
    .<span class="call">bottomSafeAreaInset</span>(overlayContent)
    .<span class="call">bottomSafeAreaInset</span>(overlayContent)
    .<span class="call">bottomSafeAreaInset</span>(overlayContent)
  }

  <span class="keyword">var</span> scrollViewContent: <span class="keyword">some</span> <span class="type">View</span> { ... }
  <span class="keyword">var</span> overlayContent: <span class="keyword">some</span> <span class="type">View</span> { ... }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/safe-area-insets-2/stack.gif"/><h2>iOS 15</h2><p>Lastly, we want to make it easy to move to SwiftUI's <code>safeAreaInset</code> once we drop support for older iOS versions, we can do so by:</p><ol><li>using <code>safeAreaInset</code> for iOS 15+, this guarantees that if, in the future, anything changes on <code>safeAreaInset</code> behavior, we will see it reflected in our apps</li><li>adding an <code>@available</code> declaration to our view modifier definition, as soon as we drop iOS 14 support, this will trigger a warning telling us to adopt the official <code>safeAreaInset</code> APIs:</li></ol><pre><code><span class="keyword">@available</span>(iOS, introduced: <span class="number">13</span>, deprecated: <span class="number">15</span>, message: <span class="string">"Use .safeAreaInset() directly"</span>) <span class="comment">// 👈🏻 2</span>
<span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">@ViewBuilder
  func</span> bottomSafeAreaInset&lt;OverlayContent: <span class="type">View</span>&gt;(<span class="keyword">_</span> overlayContent: <span class="type">OverlayContent</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">if #available</span>(iOS <span class="number">15.0</span>, *) {
      <span class="keyword">self</span>.<span class="call">safeAreaInset</span>(edge: .<span class="dotAccess">bottom</span>, spacing: <span class="number">0</span>, content: { overlayContent }) <span class="comment">// 👈🏻 1</span>
    } <span class="keyword">else</span> {
      <span class="keyword">self</span>.<span class="call">modifier</span>(<span class="type">BottomInsetViewModifier</span>(overlayContent: overlayContent))
    }
  }
}
</code></pre><blockquote><p>The same <code>@available</code> attribute should be applied to all other definitions as well.</p></blockquote><p>The final gist can be found <a href="https://github.com/FiveStarsBlog/CodeSamples/tree/main/SafeAreaInset">here</a>.</p><h2>Conclusions</h2><p>WWDC feels like Christmas every year however, in reality, it's a rare privilege to be able to start using the latest/newest API right away.</p><p>This shouldn't discourage us and, instead, we should look at it as a great opportunity to challenge ourselves, try backporting some of those APIs, and maybe learn a thing or two along the way.</p><p>What new APIs are you most excited about? Let me know via on <a href="https://twitter.com/zntfdr">Twitter</a> or <a href="mailto:hello@fivestars.blog">email</a>. Thank you for reading!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/safe-area-insets</guid><title>How to control safe area insets in SwiftUI</title><description>Fresh out of WWDC21, let's dig into the brand new safeAreaInset view modifier!</description><link>https://www.fivestars.blog/articles/safe-area-insets</link><pubDate>Tue, 15 Jun 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Fresh out of WWDC21, <code>safeAreaInset()</code> is a brand new SwiftUI view modifier, which lets us define views that become part of the observed safe area. Let's take a deep dive into this new, powerful feature.</p><h2>Scroll views</h2><p>The most common <code>safeAreaInset</code> use case is probably going to be with scroll views.<br>Consider the following screen for example, where we have a <code>ScrollView</code> with some content and a button:</p><img src="https://www.fivestars.blog/assets/posts/safe-area-insets/button.png"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollView</span> {
      <span class="type">ForEach</span>(<span class="number">1</span>..&lt;<span class="number">30</span>) { <span class="keyword">_ in</span>
        <span class="type">Text</span>(<span class="string">"Five Stars"</span>)
          .<span class="call">font</span>(.<span class="dotAccess">largeTitle</span>)
      }
      .<span class="call">frame</span>(maxWidth: .<span class="dotAccess">infinity</span>)
    }
    .<span class="call">overlay</span>(alignment: .<span class="dotAccess">bottom</span>) {
      <span class="type">Button</span> {
        ...
      } label: {
        <span class="type">Text</span>(<span class="string">"Continue"</span>)
          .<span class="call">frame</span>(maxWidth: .<span class="dotAccess">infinity</span>)
      }
      .<span class="call">buttonStyle</span>(.<span class="dotAccess">bordered</span>)
      .<span class="call">controlSize</span>(.<span class="dotAccess">large</span>)
      .<span class="call">controlProminence</span>(.<span class="dotAccess">increased</span>)
      .<span class="call">padding</span>(.<span class="dotAccess">horizontal</span>)
    }
  }
}
</code></pre><blockquote><p>Note the new iOS 15 <code>.buttonStyle(.bordered)</code> <code>.controlSize(.large)</code> <code>.controlProminence(.increased)</code> view modifiers!</p></blockquote><p>Because the button is just an overlay, the scroll view is not affected by it, which becomes a problem when we scroll at the bottom:</p><img src="https://www.fivestars.blog/assets/posts/safe-area-insets/no.gif"/><p>The last elements in the <code>ScrollView</code> are hidden under the button!</p><p>Let's now swap <code>.overlay(alignment: .bottom)</code> with <code>.safeAreaInset(edge: .bottom)</code>:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollView</span> {
      <span class="type">ForEach</span>(<span class="number">1</span>..&lt;<span class="number">30</span>) { <span class="keyword">_ in</span>
        <span class="type">Text</span>(<span class="string">"Five Stars"</span>)
          .<span class="call">font</span>(.<span class="dotAccess">largeTitle</span>)
      }
      .<span class="call">frame</span>(maxWidth: .<span class="dotAccess">infinity</span>)
    }
    .<span class="call">safeAreaInset</span>(edge: .<span class="dotAccess">bottom</span>) { <span class="comment">// 👈🏻</span>
      <span class="type">Button</span> {
        ...
      } label: {
        <span class="type">Text</span>(<span class="string">"Continue"</span>)
          .<span class="call">frame</span>(maxWidth: .<span class="dotAccess">infinity</span>)
      }
      .<span class="call">buttonStyle</span>(.<span class="dotAccess">bordered</span>)
      .<span class="call">controlSize</span>(.<span class="dotAccess">large</span>)
      .<span class="call">controlProminence</span>(.<span class="dotAccess">increased</span>)
      .<span class="call">padding</span>(.<span class="dotAccess">horizontal</span>)
    }
  }
}
</code></pre><p>...and voila'! <code>ScrollView</code> observes the new region passed down by <code>safeAreaInset</code> and our last elements are now visible:</p><img src="https://www.fivestars.blog/assets/posts/safe-area-insets/yes.gif"/><p>Next, let's see how it works.</p><h2>Definition</h2><p>This modifier comes in two variants, one for each axis (horizontal/vertical):</p><pre><code><span class="comment">/// Horizontal axis.</span>
<span class="keyword">func</span> safeAreaInset&lt;V: <span class="type">View</span>&gt;(
  edge: <span class="type">HorizontalEdge</span>,
  alignment: <span class="type">VerticalAlignment</span> = .<span class="dotAccess">center</span>,
  spacing: <span class="type">CGFloat</span>? = <span class="keyword">nil</span>,
  <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">V</span>
) -&gt; <span class="keyword">some</span> <span class="type">View</span>

<span class="comment">/// Vertical axis.</span>
<span class="keyword">func</span> safeAreaInset&lt;V: <span class="type">View</span>&gt;(
  edge: <span class="type">VerticalEdge</span>, 
  alignment: <span class="type">HorizontalAlignment</span> = .<span class="dotAccess">center</span>, 
  spacing: <span class="type">CGFloat</span>? = <span class="keyword">nil</span>, 
  <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">V</span>
) -&gt; <span class="keyword">some</span> <span class="type">View</span>
</code></pre><p>We have four parameters:</p><ul><li><code>edge</code> - where we specify which region edge we're targeting: either <code>.top</code> or <code>.bottom</code> for the vertical variant, and <code>.leading</code> or <code>.trailing</code> for the horizontal one</li><li><code>alignment</code> - where we specify how our <code>safeAreaInset</code> content should align when it doesn't fit the available space</li><li><code>spacing</code> - where we can further shift the safe area beyond the bounds of the <code>safeAreaInset</code> content, by default this parameter has a non-zero value, based on the platform convention we're targeting</li><li><code>content</code> - where we define our <code>safeAreaInset</code> content</li></ul><p>Let's use it in practice to understand what this is all about.</p><h2>Examples</h2><p>By default SwiftUI places our content within the safe area, we will start with a <code>LinearGradient</code> which always takes all the available space:</p><img src="https://www.fivestars.blog/assets/posts/safe-area-insets/base.png"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">LinearGradient</span>(
      colors: [.<span class="dotAccess">mint</span>, .<span class="dotAccess">teal</span>, .<span class="dotAccess">cyan</span>, .<span class="dotAccess">indigo</span>],
      startPoint: .<span class="dotAccess">leading</span>,
      endPoint: .<span class="dotAccess">trailing</span>
    )
  }
}
</code></pre><p>Let's say that we'd like to extend our top safe area, this is now possible thanks to the new <code>safeAreaInset</code> view modifier:</p><img src="https://www.fivestars.blog/assets/posts/safe-area-insets/red.png"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">LinearGradient</span>(
      colors: [.<span class="dotAccess">mint</span>, .<span class="dotAccess">teal</span>, .<span class="dotAccess">cyan</span>, .<span class="dotAccess">indigo</span>],
      startPoint: .<span class="dotAccess">leading</span>,
      endPoint: .<span class="dotAccess">trailing</span>
    )
    .<span class="call">safeAreaInset</span>(edge: .<span class="dotAccess">top</span>, spacing: <span class="number">0</span>) {
      <span class="type">Color</span>.<span class="property">red</span>.<span class="call">frame</span>(height: <span class="number">30</span>).<span class="call">opacity</span>(<span class="number">0.5</span>)
    }
  }
}
</code></pre><p>We passed a see-through view as the view modifier content: note how <code>LinearGradient</code> doesn't extend beneath it.</p><p>This is because our <code>safeAreaInset</code>:</p><ol><li>takes in the observed region</li><li>places its <code>content</code> (the red color above) in that region (according to its parameters)</li><li>reduces the available region, based on both its <code>content</code> size and parameters, and passes it down to <code>LinearGradient</code></li></ol><p>This is a big departure from the <code>overlay</code> view modifier, where:</p><ol><li>the view where the overlay is applied to places itself in the observed region</li><li>the overlay inherits that view location and size</li><li>the overlay is placed on top of that space</li></ol><p>The way things get placed is essentially the opposite.</p><h3>Size</h3><p>Because <code>safeAreaInset</code> only cares about the observed region, its <code>content</code> can exceed the size of the view it's applied to:</p><img src="https://www.fivestars.blog/assets/posts/safe-area-insets/size.png"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">LinearGradient</span>(
      colors: [.<span class="dotAccess">mint</span>, .<span class="dotAccess">teal</span>, .<span class="dotAccess">cyan</span>, .<span class="dotAccess">indigo</span>],
      startPoint: .<span class="dotAccess">leading</span>,
      endPoint: .<span class="dotAccess">trailing</span>
    )
    .<span class="call">frame</span>(width: <span class="number">50</span>)
    .<span class="call">safeAreaInset</span>(edge: .<span class="dotAccess">top</span>, spacing: <span class="number">0</span>) {
      <span class="type">Color</span>.<span class="property">red</span>.<span class="call">frame</span>(height: <span class="number">30</span>).<span class="call">opacity</span>(<span class="number">0.5</span>)
    }
  }
}
</code></pre><p>In this example, the view where <code>safeAreaInset</code> is applied is only <code>50</code> points wide, thanks to <code>.frame(width: 50)</code>, however, the <code>safeAreaInset</code>'s content still takes all the space it needs from the observed region.</p><h3>Spacing</h3><p>The <code>spacing</code> parameter further shifts the safe area beyond the bounds of the <code>safeAreaInset</code> content, in all examples we've so far it was always set to <code>0</code>, let's pass <code>50</code> this time:</p><img src="https://www.fivestars.blog/assets/posts/safe-area-insets/gap.png"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">LinearGradient</span>(
      colors: [.<span class="dotAccess">mint</span>, .<span class="dotAccess">teal</span>, .<span class="dotAccess">cyan</span>, .<span class="dotAccess">indigo</span>],
      startPoint: .<span class="dotAccess">leading</span>,
      endPoint: .<span class="dotAccess">trailing</span>
    )
    .<span class="call">safeAreaInset</span>(edge: .<span class="dotAccess">top</span>, spacing: <span class="number">50</span>) {
      <span class="type">Color</span>.<span class="property">red</span>.<span class="call">frame</span>(height: <span class="number">30</span>).<span class="call">opacity</span>(<span class="number">0.5</span>)
    }
  }
}
</code></pre><p>We now have a <code>50</code> points gap between our <code>Color.red</code> and <code>LinearContent</code>: this spacing always reduces the region available of our original view (the <code>LinearGradient</code> in our examples) by the amount provided, and only for the edge we're targeting.</p><p>If we were to pass a negative <code>spacing</code>, then we'd be decreasing the safe region instead:</p><img src="https://www.fivestars.blog/assets/posts/safe-area-insets/overlap.png"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">LinearGradient</span>(
      colors: [.<span class="dotAccess">mint</span>, .<span class="dotAccess">teal</span>, .<span class="dotAccess">cyan</span>, .<span class="dotAccess">indigo</span>],
      startPoint: .<span class="dotAccess">leading</span>,
      endPoint: .<span class="dotAccess">trailing</span>
    )
    .<span class="call">safeAreaInset</span>(edge: .<span class="dotAccess">top</span>, spacing: -<span class="number">10</span>) {
      <span class="type">Color</span>.<span class="property">red</span>.<span class="call">frame</span>(height: <span class="number">30</span>).<span class="call">opacity</span>(<span class="number">0.5</span>)
    }
  }
}
</code></pre><p>As expected, the <code>safeAreaInset</code> content did not move, however, the <code>LinearGradient</code> now overlaps <code>Color.red</code> for <code>10</code> points, as the <code>safeAreaInset</code> has a <code>spacing</code> of <code>-10</code>.</p><h3>Alignment</h3><p>The <code>alignment</code> parameter works similarly to how it did on <code>overlay</code>, positioning the <code>safeAreaInset</code> content at the right place when it doesn't entirely fit the available space.</p><p>With <code>Color.red.frame(height: 30)</code>, the <code>safeAreaInset</code> content always took all the horizontal available space, let's limit its width to <code>30</code> and declare a <code>.trailing</code> <code>alignment</code>:</p><img src="https://www.fivestars.blog/assets/posts/safe-area-insets/align.png"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">LinearGradient</span>(
      colors: [.<span class="dotAccess">mint</span>, .<span class="dotAccess">teal</span>, .<span class="dotAccess">cyan</span>, .<span class="dotAccess">indigo</span>],
      startPoint: .<span class="dotAccess">leading</span>,
      endPoint: .<span class="dotAccess">trailing</span>
    )
    .<span class="call">safeAreaInset</span>(edge: .<span class="dotAccess">top</span>, alignment: .<span class="dotAccess">trailing</span>, spacing: <span class="number">0</span>) {
      <span class="type">Color</span>.<span class="property">red</span>.<span class="call">frame</span>(width: <span class="number">30</span>, height: <span class="number">30</span>)
    }
  }
}
</code></pre><p>No surprises here.</p><h2>Beyond the basics</h2><p>With the introduction out of the way, let's try to experiment a little more with our new modifier.</p><h3>Cumulative view modifiers</h3><p>What happens when we apply multiple <code>safeAreaInset</code> to the same view?</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">LinearGradient</span>(
      colors: [.<span class="dotAccess">mint</span>, .<span class="dotAccess">teal</span>, .<span class="dotAccess">cyan</span>, .<span class="dotAccess">indigo</span>],
      startPoint: .<span class="dotAccess">leading</span>,
      endPoint: .<span class="dotAccess">trailing</span>
    )
    .<span class="call">safeAreaInset</span>(edge: .<span class="dotAccess">bottom</span>, spacing: <span class="number">0</span>) {
      <span class="type">Color</span>.<span class="property">red</span>.<span class="call">frame</span>(height: <span class="number">30</span>).<span class="call">opacity</span>(<span class="number">0.5</span>)
    }
    .<span class="call">safeAreaInset</span>(edge: .<span class="dotAccess">bottom</span>, spacing: <span class="number">0</span>) {
      <span class="type">Color</span>.<span class="property">green</span>.<span class="call">frame</span>(height: <span class="number">30</span>).<span class="call">opacity</span>(<span class="number">0.5</span>)
    }
    .<span class="call">safeAreaInset</span>(edge: .<span class="dotAccess">bottom</span>, spacing: <span class="number">0</span>) {
      <span class="type">Color</span>.<span class="property">blue</span>.<span class="call">frame</span>(height: <span class="number">30</span>).<span class="call">opacity</span>(<span class="number">0.5</span>)
    }
  }
}
</code></pre><p>Let's go back at the beginning of the article where we described <code>safeAreaInset</code>'s three steps:</p><ol><li>the <code>safeAreaInset</code> takes in the observed region</li><li>places its <code>content</code> in that region (according to its parameters)</li><li>reduces the available region, based on the <code>content</code> size and its parameters, and passes it down</li></ol><p>The first view modifier applied is the outer-most one, the one with <code>Color.blue</code>, which takes the three steps above and passes down the reduced available region to the second-last <code>safeAreaInset</code>, the <code>Color.green</code> one, which will do the same, etc.</p><p>Here's the final result:</p><img src="https://www.fivestars.blog/assets/posts/safe-area-insets/stack.png"/><h3>Multiple edges</h3><p>We've seen how we can "stack" multiple <code>safeAreaInset</code>s, however we don't need to stop at one edge: it's totally ok to apply multiple <code>safeAreaInset</code> modifiers that apply to different edges:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">LinearGradient</span>(
      colors: [.<span class="dotAccess">mint</span>, .<span class="dotAccess">teal</span>, .<span class="dotAccess">cyan</span>, .<span class="dotAccess">indigo</span>],
      startPoint: .<span class="dotAccess">leading</span>,
      endPoint: .<span class="dotAccess">trailing</span>
    )
    .<span class="call">safeAreaInset</span>(edge: .<span class="dotAccess">top</span>, spacing: <span class="number">0</span>) {
      <span class="type">Color</span>.<span class="property">red</span>.<span class="call">frame</span>(height: <span class="number">30</span>).<span class="call">opacity</span>(<span class="number">0.5</span>)
    }
    .<span class="call">safeAreaInset</span>(edge: .<span class="dotAccess">trailing</span>, spacing: <span class="number">0</span>) {
      <span class="type">Color</span>.<span class="property">green</span>.<span class="call">frame</span>(width: <span class="number">30</span>).<span class="call">opacity</span>(<span class="number">0.5</span>)
    }
    .<span class="call">safeAreaInset</span>(edge: .<span class="dotAccess">bottom</span>, spacing: <span class="number">0</span>) {
      <span class="type">Color</span>.<span class="property">blue</span>.<span class="call">frame</span>(height: <span class="number">30</span>).<span class="call">opacity</span>(<span class="number">0.5</span>)
    }
    .<span class="call">safeAreaInset</span>(edge: .<span class="dotAccess">leading</span>, spacing: <span class="number">0</span>) {
      <span class="type">Color</span>.<span class="property">yellow</span>.<span class="call">frame</span>(width: <span class="number">30</span>).<span class="call">opacity</span>(<span class="number">0.5</span>)
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/safe-area-insets/multiple.png"/><p>The same logic is still valid, regardless of what edge each <code>safeAreaInset</code> modifier target:</p><ul><li>first we apply/place the (outermost) <code>Color.yellow</code> <code>safeAreaInset</code>, which will take all the space needed, and pass the reduced region down</li><li>then we move to the <code>Color.blue</code> <code>safeAreaInset</code>, which will do the same</li><li>etc</li></ul><h3>ignoresSafeArea</h3><p>Previously <code>ignoresSafeArea</code> meant letting our views be placed under the Home indicator, keyboard, and/or the status bar:<br>in iOS 15 <code>ignoresSafeArea</code> also means resetting any <code>safeAreaInset</code>.</p><p>In the following example we're first placing our <code>safeAreaInset</code>, and then ignore it before placing our final view:</p><img src="https://www.fivestars.blog/assets/posts/safe-area-insets/ignore.png"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">LinearGradient</span>(
      colors: [.<span class="dotAccess">mint</span>, .<span class="dotAccess">teal</span>, .<span class="dotAccess">cyan</span>, .<span class="dotAccess">indigo</span>],
      startPoint: .<span class="dotAccess">leading</span>,
      endPoint: .<span class="dotAccess">trailing</span>
    )
    .<span class="call">ignoresSafeArea</span>(edges: .<span class="dotAccess">bottom</span>)
    .<span class="call">safeAreaInset</span>(edge: .<span class="dotAccess">bottom</span>, spacing: <span class="number">0</span>) {
      <span class="type">Color</span>.<span class="property">red</span>.<span class="call">frame</span>(height: <span class="number">30</span>).<span class="call">opacity</span>(<span class="number">0.5</span>)
    }
  }
}
</code></pre><blockquote><p>As of Xcode 13b1, only <code>ScrollView</code> correctly observes <code>safeAreaInset</code>s: hopefully <code>List</code> and <code>Form</code> will be fixed in an upcoming Xcode seed (FB9166130).</p></blockquote><h2>Conclusions</h2><p>WWDC21 brought us a lot of new SwiftUI functionality, allowing us to push our apps to the next level: <code>safeAreaInset</code> is one of those view modifiers that you didn't know you needed until you do, and it comes with a great, simple API.</p><p>Are you planning to use <code>safeAreaInset</code>? What challenges will it solve in your app? Please let me know either via <a href="mailto:hello@fivestars.blog">email</a> or <a href="https://twitter.com/zntfdr">Twitter</a>!</p><blockquote><p>This is the first of a series of articles exploring new SwiftUI features. We will cover many more during the rest of the summer: subscribe to Five Stars's <a href="https://www.fivestars.blog/feed.rss">feed RSS</a> or follow <a href="http://twitter.com/fiveStarsBlog">@FiveStarsBlog</a> on Twitter to never miss new content!</p></blockquote>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-wwdc21</guid><title>What's new in SwiftUI</title><description>WWDC21 is here! Let's take a look at some of the new SwiftUI additions!</description><link>https://www.fivestars.blog/articles/swiftui-wwdc21</link><pubDate>Wed, 9 Jun 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>WWDC21 is here! SwiftUI has taken another huge step forward, and it comes with many enhancements to current views, new views, new types, new view modifiers, and more:<br>don't fret! We have a whole summer ahead of us to explore all of that, in this first article, let's have a look at some of the new changes!</p><blockquote><p>For the new "flagship" SwiftUI features, I recommend <a href="https://swiftwithmajid.com/2021/06/08/what-is-new-in-swiftui-after-wwdc21/">this article</a> by <a href="https://twitter.com/mecid">Majid Jabrayilov</a>.</p></blockquote><h2>Colors</h2><h3>New colors</h3><img src="https://www.fivestars.blog/assets/posts/swiftui-wwdc21/newcolors.png"/><p>Not all new features need to come with disrupting changes, for example, this year we gained five new colors:</p><pre><code><span class="type">HStack</span> {
  <span class="type">Group</span> {
    <span class="type">Circle</span>()
      .<span class="call">fill</span>(.<span class="dotAccess">mint</span>)
    <span class="type">Circle</span>()
      .<span class="call">fill</span>(.<span class="dotAccess">teal</span>)
    <span class="type">Circle</span>()
      .<span class="call">fill</span>(.<span class="dotAccess">cyan</span>)
    <span class="type">Circle</span>()
      .<span class="call">fill</span>(.<span class="dotAccess">indigo</span>)
    <span class="type">Circle</span>()
      .<span class="call">fill</span>(.<span class="dotAccess">brown</span>)
  }
  .<span class="call">frame</span>(width: <span class="number">32</span>, height: <span class="number">32</span>)
}
</code></pre><h3>New initializers</h3><p>All current <code>CGColor</code>/<code>UIColor</code>/<code>NSColor</code> <code>Color</code> initializers have been soft deprecated, in favor of new initializers with explicit argument labels:</p><pre><code>

<span class="keyword">@available</span>(
  ..., deprecated: ..., 
  message: <span class="string">"Use Color(uiColor:) when converting a UIColor, or create a standard Color directly"</span>
)
<span class="keyword">extension</span> <span class="type">Color</span> {
  <span class="keyword">public init</span>(<span class="keyword">_</span> color: <span class="type">UIColor</span>)
}

<span class="keyword">@available</span>(iOS <span class="number">15.0</span>)
<span class="keyword">extension</span> <span class="type">Color</span> {
  <span class="keyword">public init</span>(uiColor: <span class="type">UIColor</span>)
}
</code></pre><h2>Styles</h2><h3>New (iOS) Button style</h3><img src="https://www.fivestars.blog/assets/posts/swiftui-wwdc21/BorderedButtonStyle.png"/><p>iOS's <code>Button</code> gains <code>BorderedButtonStyle</code>, which previously was available in all other platforms <em>but</em> iOS.</p><pre><code><span class="type">Button</span>(<span class="string">"Tap me"</span>) {}
  .<span class="call">buttonStyle</span>(<span class="type">BorderedButtonStyle</span>())
</code></pre><h3>New Toggle style</h3><img src="https://www.fivestars.blog/assets/posts/swiftui-wwdc21/togglebutton.png"/><p><code>Toggle</code> also gains a new style, available on both macOS and iOS:</p><pre><code><span class="type">VStack</span> {
  <span class="type">Toggle</span>(isOn: .<span class="call">constant</span>(<span class="keyword">true</span>), label: { <span class="type">Text</span>(<span class="string">"Toggle on"</span>)})
  <span class="type">Toggle</span>(isOn: .<span class="call">constant</span>(<span class="keyword">false</span>), label: { <span class="type">Text</span>(<span class="string">"Toggle off"</span>)})
  <span class="type">Toggle</span>(isOn: .<span class="call">constant</span>(<span class="keyword">true</span>), label: { <span class="type">Text</span>(<span class="string">"Toggle on disabled"</span>)})
    .<span class="call">disabled</span>(<span class="keyword">true</span>)
  <span class="type">Toggle</span>(isOn: .<span class="call">constant</span>(<span class="keyword">false</span>), label: { <span class="type">Text</span>(<span class="string">"Toggle off disabled"</span>)})
    .<span class="call">disabled</span>(<span class="keyword">true</span>)
}
.<span class="call">toggleStyle</span>(<span class="type">ButtonToggleStyle</span>())
</code></pre><h3>New way to apply styles</h3><p>Beside actual styles, there's a new way to declare which style we'd like to apply, let's take a plain button style for example:</p><pre><code><span class="type">Button</span>(<span class="string">"Tap me"</span>) {}
  .<span class="call">buttonStyle</span>(<span class="type">PlainButtonStyle</span>())
</code></pre><p>We can now apply this style with a more succinct API:</p><pre><code><span class="type">Button</span>(<span class="string">"Tap me"</span>) {}
  .<span class="call">buttonStyle</span>(.<span class="dotAccess">plain</span>)
</code></pre><p>The old way is soft deprecated, and will trigger warnings in future Xcode releases.</p><p>This is a nice shift, and it's applied to all SwiftUI components, for example:</p><pre><code><span class="type">List</span> {
  ...
}
.<span class="call">listStyle</span>(.<span class="dotAccess">grouped</span>)


<span class="type">Toggle</span>(isOn: $isOn, label: { <span class="type">Text</span>(<span class="string">"Toggle"</span>) })
  .<span class="call">toggleStyle</span>(.<span class="dotAccess">button</span>)

<span class="type">TextField</span>(<span class="string">"Username: "</span>, text: $username)
  .<span class="call">textFieldStyle</span>(.<span class="dotAccess">roundedBorder</span>)
</code></pre><h2>SwiftUI previews InterfaceOrientation</h2><img src="https://www.fivestars.blog/assets/posts/swiftui-wwdc21/previews.png"/><p>SwiftUI previews now have a new <code>previewInterfaceOrientation(_:)</code> view modifier, allowing us to preview our screens in different orientation:</p><pre><code><span class="keyword">struct</span> ContentView_Previews: <span class="type">PreviewProvider</span> {
  <span class="keyword">static var</span> previews: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ContentView</span>()
      .<span class="call">previewInterfaceOrientation</span>(.<span class="dotAccess">portraitUpsideDown</span>)
    <span class="type">ContentView</span>()
      .<span class="call">previewInterfaceOrientation</span>(.<span class="dotAccess">landscapeLeft</span>)
  }
}
</code></pre><p><code>InterfaceOrientation</code> conforms to <code>Identifiable</code> and comes with an <code>allCases</code> static property, allowing us to preview any view in all orientations with a <code>ForEach</code>:</p><pre><code><span class="keyword">struct</span> ContentView_Previews: <span class="type">PreviewProvider</span> {
  <span class="keyword">static var</span> previews: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ForEach</span>(<span class="type">InterfaceOrientation</span>.<span class="property">allCases</span>) { interfaceOrientation <span class="keyword">in</span>
      <span class="type">ContentView</span>()
        .<span class="call">previewInterfaceOrientation</span>(interfaceOrientation)
    }
  }
}
</code></pre><p>With this out of the way, I would love to see previews side by side next, instead of stacked vertically (FB7635888).</p><h2>Dismiss action</h2><p>In previous iOS versions, if we wanted a view (a sheet, or a navigation view) to dismiss itself, we could have used the <code>presentationMode</code> environment object:</p><pre><code><span class="keyword">struct</span> OldSheetView: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">presentationMode</span>) <span class="keyword">@Binding var</span> presentationMode

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(<span class="string">"Dismiss Me"</span>) {
      presentationMode.<span class="call">dismiss</span>()
    }
  }
}
</code></pre><p>In iOS 15 we have a new <code>dismiss</code> environment variable, on which we just call <code>dismiss()</code> on itself:</p><pre><code><span class="keyword">struct</span> NewSheetView: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">dismiss</span>) <span class="keyword">var</span> dismiss

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(<span class="string">"Dismiss Me"</span>) {
      <span class="call">dismiss</span>()
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/swiftui-wwdc21/dismiss.gif"/><p>With this new addition, I believe the old <code>presentationMode</code> object should be deprecated (FB9142876), if you're aware of any reason why it shouldn't, please <a href="mailto:hello@fivestars.blog">let me know</a>!</p><h2>Badges</h2><img src="https://www.fivestars.blog/assets/posts/swiftui-wwdc21/badgetab.png"/><p>We have a new <code>badge(_:)</code> view modifier used to display badges on UI components:</p><pre><code><span class="type">TabView</span> {
  <span class="type">Text</span>(<span class="string">"FIVE STARS"</span>)
    .<span class="call">badge</span>(<span class="number">1</span>)
    .<span class="call">tabItem</span> { <span class="type">Label</span>(<span class="string">"One"</span>, systemImage: <span class="string">"1.circle.fill"</span>) }
  <span class="type">Text</span>(<span class="string">"FIVE STARS"</span>)
    .<span class="call">badge</span>(<span class="number">2</span>)
    .<span class="call">tabItem</span> { <span class="type">Label</span>(<span class="string">"Two"</span>, systemImage: <span class="string">"2.circle.fill"</span>) }
  <span class="type">Text</span>(<span class="string">"FIVE STARS"</span>)
    .<span class="call">badge</span>(<span class="number">3</span>)
    .<span class="call">tabItem</span> { <span class="type">Label</span>(<span class="string">"Three"</span>, systemImage: <span class="string">"3.circle.fill"</span>) }
  <span class="type">Text</span>(<span class="string">"FIVE STARS"</span>)
    .<span class="call">badge</span>(<span class="number">4</span>)
    .<span class="call">tabItem</span> { <span class="type">Label</span>(<span class="string">"Four"</span>, systemImage: <span class="string">"4.circle.fill"</span>) }
  <span class="type">Text</span>(<span class="string">"STARS"</span>)
    .<span class="call">badge</span>(<span class="number">5</span>)
    .<span class="call">tabItem</span> { <span class="type">Label</span>(<span class="string">"Five"</span>, systemImage: <span class="string">"5.circle.fill"</span>) }
}
</code></pre><p><code>badge(_:)</code> accepts both numbers and strings, it's displayed when applied to <code>TabView</code> items and <code>Text</code> on <code>List</code>s:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-wwdc21/badgelist.png"/><pre><code><span class="type">List</span> {
  <span class="type">Text</span>(<span class="string">"See badge on the right 👉🏻"</span>)
    .<span class="call">badge</span>(<span class="number">5</span>)
  <span class="type">Button</span> {
    <span class="comment">// ...</span>
  } label: {
    <span class="type">Text</span>(<span class="string">"Button"</span>)
      .<span class="call">badge</span>(<span class="number">10</span>)
  }
}
</code></pre><h2>Property wrappers meet Optional RawRepresentable</h2><p>Last year we saw the introduction of both <code>@AppStorage</code> and <code>@SceneStorage</code>, which we covered <a href="https://www.fivestars.blog/articles/app-scene-storage">here</a>, this year both property wrappers gained the possibility to be associated with an optional <code>RawRepresentable</code> type:</p><pre><code><span class="keyword">extension</span> <span class="type">AppStorage</span> {
  <span class="keyword">public init</span>&lt;R: <span class="type">RawRepresentable</span>&gt;(
    <span class="keyword">_</span> key: <span class="type">String</span>, 
    store: <span class="type">UserDefaults</span>? = <span class="keyword">nil</span>
  ) <span class="keyword">where</span> <span class="type">Value</span> == <span class="type">R</span>?, <span class="type">R</span>.<span class="type">RawValue</span> == <span class="type">String</span>

  <span class="keyword">public init</span>&lt;R: <span class="type">RawRepresentable</span>&gt;(
    <span class="keyword">_</span> key: <span class="type">String</span>, 
    store: <span class="type">UserDefaults</span>? = <span class="keyword">nil</span>
  ) <span class="keyword">where</span> <span class="type">Value</span> == <span class="type">R</span>?, <span class="type">R</span>.<span class="type">RawValue</span> == <span class="type">Int</span>
}

<span class="keyword">extension</span> <span class="type">SceneStorage</span> {
  <span class="keyword">public init</span>&lt;R: <span class="type">RawRepresentable</span>&gt;(
    <span class="keyword">_</span> key: <span class="type">String</span>
  ) <span class="keyword">where</span> <span class="type">Value</span> == <span class="type">R</span>?, <span class="type">R</span>.<span class="type">RawValue</span> == <span class="type">String</span>

  <span class="keyword">public init</span>&lt;R: <span class="type">RawRepresentable</span>&gt;(
    <span class="keyword">_</span> key: <span class="type">String</span>
  ) <span class="keyword">where</span> <span class="type">Value</span> == <span class="type">R</span>?, <span class="type">R</span>.<span class="type">RawValue</span> == <span class="type">Int</span>
}
</code></pre><p>Allowing us to start with a blank state, without forcing us to choose a default value:</p><pre><code><span class="keyword">enum</span> Fruit: <span class="type">Int</span>, <span class="type">Identifiable</span>, <span class="type">CaseIterable</span> {
  <span class="keyword">case</span> banana
  <span class="keyword">case</span> orange
  <span class="keyword">case</span> mango

  <span class="keyword">var</span> id: <span class="type">Int</span> { rawValue }
}

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@AppStorage</span>(<span class="string">"fruit"</span>) <span class="keyword">private var</span> fruit: <span class="type">Fruit</span>?

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Picker</span>(<span class="string">"My Favorite Fruit"</span>, selection: $fruit) {
      <span class="type">ForEach</span>(<span class="type">Fruit</span>.<span class="property">allCases</span>, id: \.<span class="keyword">self</span>) {
        <span class="type">Text</span>(<span class="string">"</span>\($0)<span class="string">"</span> <span class="keyword">as</span> <span class="type">String</span>)
      }
    }.<span class="call">pickerStyle</span>(<span class="type">SegmentedPickerStyle</span>())
  }
}
</code></pre><blockquote><p>In this example, the picker will have no value selected at launch.</p></blockquote><h2>Conclusions</h2><p>This is just a sneak peek at some of the new SwiftUI changes from this year, we will explore many more during the rest of the summer:<br>if you haven't already, this is a great moment to subscribe to Five Stars's <a href="https://www.fivestars.blog/feed.rss">feed rss</a> or follow <a href="http://twitter.com/fiveStarsBlog">@FiveStarsBlog</a> on Twitter!</p><p>What are you most excited about WWDC21? <a href="http://twitter.com/zntfdr">Let me know</a>!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/wwdc-notes-2</guid><title>WWDC NOTES 2.0!</title><description>Just in time for WWDC21, WWDC NOTES 2 is live! Here's what's new!</description><link>https://www.fivestars.blog/articles/wwdc-notes-2</link><pubDate>Wed, 2 Jun 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Just in time for WWDC21, <a href="https://www.wwdcnotes.com">WWDC Notes 2</a> is out!</p><p><a href="https://www.wwdcnotes.com"><img src="https://www.fivestars.blog/assets/posts/wwdc-notes-2/home.png"/></a></p><p>Here's what's new:</p><h2>Design</h2><img src="https://www.fivestars.blog/assets/posts/wwdc-notes-2/design.png"/><p>From <a href="https://www.wwdcnotes.com">home</a> all the way to <a href="https://www.wwdcnotes.com/community/skhillon/">individual community members</a>, every page has a fresh new look.</p><p>A few highlights:</p><ul><li>more compact headers, new menu for quick links</li><li>improved overall readability</li><li>smart list layouts: pages listing notes pick the best layout based on the number of notes</li><li>note pages: session topics are highlighted along with the note event and track. Apple's session descriptions and contributors are improved as well.</li></ul><h2>Filters</h2><img src="https://www.fivestars.blog/assets/posts/wwdc-notes-2/browseby.png"/><p>It has always been possible to filter notes by <a href="https://www.wwdcnotes.com/events/">year</a> or <a href="https://www.wwdcnotes.com/tracks/">track</a> (<a href="https://www.wwdcnotes.com/tracks/Design/">Design</a>, <a href="https://www.wwdcnotes.com/tracks/DeveloperTools/">Developer Tools</a>, ...): there's now a finer way to do so by <a href="https://www.wwdcnotes.com/topics/">topics</a>. A few examples: <a href="https://www.wwdcnotes.com/topics/MachineLearningandVision/">machine learning</a>, <a href="https://www.wwdcnotes.com/topics/Networking/">networking</a>, <a href="https://www.wwdcnotes.com/topics/AppStoreConnect/">App Store Connect</a>, <a href="https://www.wwdcnotes.com/topics/HealthandFitness/">health</a> and <a href="https://www.wwdcnotes.com/topics/">many more</a>.</p><p>Each note shows its topic(s) under their page title as well.</p><p>Similarly to topics, WWDC Notes now features <a href="https://www.wwdcnotes.com/tags/">over <strong>one-thousand</strong> tags</a>: if topics are still too broad, tags will definitively help. A few examples: <a href="https://www.wwdcnotes.com/tags/swiftui/">SwiftUI</a>, <a href="https://www.wwdcnotes.com/tags/performance/">performance</a>, <a href="https://www.wwdcnotes.com/tags/widgets/">widgets</a>, and <a href="https://www.wwdcnotes.com/tags/">many more</a>.</p><p><a href="https://www.wwdcnotes.com/topics/">Browse all topics</a>.<br><a href="https://www.wwdcnotes.com/tags/">Browse all tags</a>.</p><h2>Assets</h2><img src="https://www.fivestars.blog/assets/posts/wwdc-notes-2/assets.png"/><p>I reviewed all current 300+ notes and <a href="https://github.com/FiveStarsBlog/WWDCNotes/pull/129/">replaced over 130 images</a> with actual text and code: not only WWDC Notes now loads faster, but it's also much more accessible for everyone.</p><p>Speaking of images, <a href="https://github.com/FiveStarsBlog/WWDCNotes">WWDC Notes's repository</a> now comes with <a href="https://github.com/apps/imgbot">ImgBot</a>, which automatically optimizes the size of every image added to the repository. This lets the community focus on the notes, while Imgbot takes care of assets optimization.</p><h2>And much more!</h2><p>These are just a few of the many new updates on the website, please feel free to <a href="https://www.wwdcnotes.com">check out the new website</a> and I look forward to welcome <a href="https://www.wwdcnotes.com/what-s-missing/">your notes</a>!</p><p>Thank you for reading and I wish you a great WWDC 2021!</p><h2>ICYMI</h2><p>Here is a small recap of some iterations that went live before this big 2.0 release:</p><ul><li>New <code>Latest updates</code> home section highlighting updates on existing notes</li><li>New twitter account! Follow <a href="https://twitter.com/wwdcnotes">@wwdcnotes</a> to not miss new notes!</li><li>The <a href="https://www.wwdcnotes.com/feed.rss">feed RSS</a> is stricter on when it updates</li><li>All notes come with <a href="https://twitter.com/wwdcnotes">social media previews</a></li><li>Each note showcases related sessions</li><li><a href="https://chrome.google.com/webstore/detail/wwdcnotes-button/afkhgncidjoehjglihnijhalfkdfdcbc">New Chrome extension</a> to add notes directly from Apple's <a href="https://developer.apple.com/videos/">WWDC website</a> (by community member <a href="https://www.wwdcnotes.com/community/skhillon/">Sarthak Khillon</a>)</li><li>New <a href="https://www.wwdcnotes.com/what-s-missing/"><code>Add a new note</code></a> button on top of each page</li><li>New <a href="https://www.wwdcnotes.com/what-s-missing/"><code>What's missing</code></a> page listing all sessions without notes (yet!)</li></ul>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-patterns-closures</guid><title>SwiftUI patterns: view closures</title><description>A tour into how SwiftUI has replaced UIKit's Target-Action design pattern</description><link>https://www.fivestars.blog/articles/swiftui-patterns-closures</link><pubDate>Tue, 25 May 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Swift's introduction brought us significant shifts in the way we build products. For example, we went from...</p><ul><li>...<code>everything is an object</code> to <code>everything is a protocol</code> (admittedly, we took this one <em>too much</em> to the letter)</li><li>...<code>everything is a class</code> to <code>prefer value types wherever possible</code></li></ul><p>A paradigm that didn't make it into Swift is the <a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/Devpedia-CocoaApp/TargetAction.html"><code>Target-Action</code> design pattern</a>.</p><p>If we take a pre-iOS 14 <code>UIButton</code> for example, we used to do the following to link an action to a button:</p><pre><code><span class="keyword">final class</span> FSViewController: <span class="type">UIViewController</span> {
  ...

  <span class="keyword">override func</span> viewDidLoad() {
    <span class="keyword">super</span>.<span class="call">viewDidLoad</span>()
    <span class="call">setupViews</span>()
  }
    
  <span class="keyword">private func</span> setupViews() {
    <span class="keyword">let</span> button = <span class="type">UIButton</span>(type: .<span class="dotAccess">system</span>)
    button.<span class="call">addTarget</span>(
      <span class="keyword">self</span>,
      action: <span class="keyword">#selector</span>(<span class="call">didTapButton</span>(<span class="keyword">_</span>:)),
      for: .<span class="dotAccess">touchUpInside</span>
    )
    ...
  }

  <span class="keyword">@objc private func</span> didTapButton(<span class="keyword">_</span> sender: <span class="type">UIButton</span>) {
    <span class="comment">// button action here</span>
  }
}
</code></pre><p>Things got better in iOS 14, with a new, more Swifty, <a href="https://developer.apple.com/documentation/uikit/uiaction"><code>UIAction</code></a> API:</p><pre><code><span class="keyword">final class</span> FSViewController: <span class="type">UIViewController</span> {
  ...

  <span class="keyword">override func</span> viewDidLoad() {
    <span class="keyword">super</span>.<span class="call">viewDidLoad</span>()
    <span class="call">setupViews</span>()
  }
    
  <span class="keyword">private func</span> setupViews() {
    <span class="keyword">let</span> button = <span class="type">UIButton</span>(
      primaryAction: <span class="type">UIAction</span> { <span class="keyword">_ in</span>
        <span class="comment">// button action here</span>
      }
    )
    ...
  }
}
</code></pre><p>All of this was necessary because UIKit's buttons, and many other components such as <code>UISlider</code> and <code>UIDatePicker</code>, were a subclass of <a href="https://developer.apple.com/documentation/uikit/uicontrol"><code>UIControl</code></a>, which is based on the <code>Target-Action</code> pattern.</p><p>At the time, things had to work this way for many reasons, such as linking <code>@IBAction</code> methods to storyboard components/triggers.</p><p>When it comes to SwiftUI, the team at Apple had a chance to start fresh, Swift-first, and with no legacies:<br>in this article, let's see how they replaced the <code>Target-Action</code> pattern with view closures, and more.</p><blockquote><p>This article focuses on closures accepted on view initialization and triggered when certain events happen, for closures used to build views, refer to <a href="https://www.fivestars.blog/articles/swiftui-patter-passing-views/">SwiftUI patterns: passing &amp; accepting views</a>.</p></blockquote><h2>Buttons</h2><pre><code><span class="comment">// Definition</span>
<span class="keyword">struct</span> Button&lt;Label: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">init</span>(action: <span class="keyword">@escaping</span> () -&gt; <span class="type">Void</span>, <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>)
  <span class="keyword">init</span>(<span class="keyword">_</span> titleKey: <span class="type">LocalizedStringKey</span>, action: <span class="keyword">@escaping</span> () -&gt; <span class="type">Void</span>)
  <span class="keyword">init</span>&lt;S&gt;(<span class="keyword">_</span> title: <span class="type">S</span>, action: <span class="keyword">@escaping</span> () -&gt; <span class="type">Void</span>) <span class="keyword">where</span> <span class="type">S</span> : <span class="type">StringProtocol</span>
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(<span class="string">"tap me"</span>) { 
      <span class="comment">// button action here</span>
    }
  }
}
</code></pre><p>Creating a button without an associated action would probably make any app look broken:<br>in SwiftUI, the <code>action</code> closure is a required parameter of <code>Button</code>'s initialization.</p><p>We can no longer "just" forget to associate an action to its button, or mistakenly associate more than one action to any given button. This approach solves the previous challenges right from the start.</p><h2>TextField/SecureField</h2><pre><code><span class="comment">// Definition</span>
<span class="keyword">extension</span> <span class="type">TextField</span> <span class="keyword">where</span> <span class="type">Label</span> == <span class="type">Text</span> {
  <span class="keyword">init</span>(
    <span class="keyword">_</span> titleKey: <span class="type">LocalizedStringKey</span>, 
    text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }, 
    onCommit: <span class="keyword">@escaping</span> () -&gt; <span class="type">Void</span> = {}
  )

  <span class="keyword">init</span>&lt;S: <span class="type">StringProtocol</span>&gt;(
    <span class="keyword">_</span> title: <span class="type">S</span>, 
    text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }, 
    onCommit: <span class="keyword">@escaping</span> () -&gt; <span class="type">Void</span> = {}
  )

  <span class="keyword">init</span>&lt;T&gt;(
    <span class="keyword">_</span> titleKey: <span class="type">LocalizedStringKey</span>, 
    value: <span class="type">Binding</span>&lt;<span class="type">T</span>&gt;, 
    formatter: <span class="type">Formatter</span>, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }, 
    onCommit: <span class="keyword">@escaping</span> () -&gt; <span class="type">Void</span> = {}
  )

  <span class="keyword">init</span>&lt;S: <span class="type">StringProtocol</span>, T&gt;(
    <span class="keyword">_</span> title: <span class="type">S</span>, 
    value: <span class="type">Binding</span>&lt;<span class="type">T</span>&gt;, 
    formatter: <span class="type">Formatter</span>, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }, 
    onCommit: <span class="keyword">@escaping</span> () -&gt; <span class="type">Void</span> = {}
  )
}

<span class="keyword">extension</span> <span class="type">SecureField</span> <span class="keyword">where</span> <span class="type">Label</span> == <span class="type">Text</span> {
  <span class="keyword">init</span>(<span class="keyword">_</span> titleKey: <span class="type">LocalizedStringKey</span>, text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;, onCommit: <span class="keyword">@escaping</span> () -&gt; <span class="type">Void</span> = {})
  <span class="keyword">init</span>&lt;S: <span class="type">StringProtocol</span>&gt;(<span class="keyword">_</span> title: <span class="type">S</span>, text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;, onCommit: <span class="keyword">@escaping</span> () -&gt; <span class="type">Void</span> = {})
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> username = <span class="string">""</span>
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">TextField</span>(
        <span class="string">"Username:"</span>, 
        text: $username, 
        onEditingChanged: { isOnFocus <span class="keyword">in</span> 
          <span class="comment">// ...</span>
        }, 
        onCommit: {
          <span class="comment">// ...</span>
        }
      )

      <span class="type">TextField</span>(<span class="string">"Username:"</span>, text: $username)
    }
  }
}
</code></pre><p><code>TextField</code> comes with two optional closures:</p><ol><li><code>onEditingChanged</code>, triggered when the associated <code>TextField</code> becomes first responder and when it relinquishes its first responder status</li><li><code>onCommit</code>, triggered when the user taps the associated <code>TextField</code> keyboard action key (e.g. <code>Search</code>, <code>Go</code>, or, more commonly, <code>Return</code>).</li></ol><p>Both these actions are not required to make any <code>TextField</code> work, which is why both parameters come with a default value (that does nothing).</p><p>Sometimes, we would like to trigger some side effects while the user is typing into the <code>TextField</code>: for example, to do some validation on the input (for email or password criteria, or ..).</p><p>With UIKit's <code>Target-Action</code> approach, this was easy: at every text change, our action was triggered, which we could then use to both fetch the current text value, and trigger our side effect.</p><p>In SwiftUI, there doesn't seem to be a direct equivalent. However, it's right in front of us: it's the <code>text: Binding&lt;String&gt;</code> parameter.</p><p>The binding main function is to have a single source of truth: the value our app sees is the same as the value displayed in the <code>TextField</code> (unlike UIKit, where each <code>UITextField</code> had its storage, and our view/view controller would have another one).</p><p>However, because it's a binding, we can observe its changes with iOS 14's <a href="https://developer.apple.com/documentation/swiftui/text/onchange(of:perform:)"><code>onChange(of:perform:)</code></a> view modifier:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> username = <span class="string">""</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TextField</span>(<span class="string">"Username:"</span>, text: $username)
      .<span class="call">onChange</span>(of: username, perform: <span class="call">validate</span>(<span class="keyword">_</span>:))
  }

  <span class="keyword">func</span> validate(<span class="keyword">_</span> username: <span class="type">String</span>) {
    <span class="comment">// validate here</span>
  }
}
</code></pre><p>If we're targeting iOS 13, the same can be done by providing our own binding, for example:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> username = <span class="string">""</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">let</span> binding = <span class="type">Binding</span> {
      username
    } set: {
      <span class="call">validate</span>($0)
      username = $0
    }

    <span class="type">TextField</span>(<span class="string">"Username:"</span>, text: binding)
  }

  <span class="keyword">func</span> validate(<span class="keyword">_</span> username: <span class="type">String</span>) {
    <span class="comment">// validate here</span>
  }
}
</code></pre><p>SwiftUI's Bindings are SwiftUI's most elegant replacement of UIKit's <code>Target-Action</code> pattern: they beautifully solve data synchronization challenges and other kinds of bugs.</p><blockquote><p>We took a deep dive into all SwiftUI bindings in another article of the series <a href="https://www.fivestars.blog/articles/swiftui-patterns-bindings/">SwiftUI patterns: @Bindings</a>.</p></blockquote><h2>Sliders</h2><pre><code><span class="comment">// Definition</span>
<span class="keyword">extension</span> <span class="type">Slider</span> {
  <span class="keyword">init</span>&lt;V&gt;(
    value: <span class="type">Binding</span>&lt;<span class="type">V</span>&gt;, 
    <span class="keyword">in</span> bounds: <span class="type">ClosedRange</span>&lt;<span class="type">V</span>&gt; = <span class="number">0</span>...<span class="number">1</span>, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }, 
    minimumValueLabel: <span class="type">ValueLabel</span>, 
    maximumValueLabel: <span class="type">ValueLabel</span>, 
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>
  ) <span class="keyword">where</span> <span class="type">V</span>: <span class="type">BinaryFloatingPoint</span>, <span class="type">V</span>.<span class="type">Stride</span>: <span class="type">BinaryFloatingPoint</span>

  <span class="keyword">init</span>&lt;V&gt;(
    value: <span class="type">Binding</span>&lt;<span class="type">V</span>&gt;, 
    <span class="keyword">in</span> bounds: <span class="type">ClosedRange</span>&lt;<span class="type">V</span>&gt;, 
    step: <span class="type">V</span>.<span class="type">Stride</span> = <span class="number">1</span>, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }, 
    minimumValueLabel: <span class="type">ValueLabel</span>, 
    maximumValueLabel: <span class="type">ValueLabel</span>, 
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>
  ) <span class="keyword">where</span> <span class="type">V</span>: <span class="type">BinaryFloatingPoint</span>, <span class="type">V</span>.<span class="type">Stride</span>: <span class="type">BinaryFloatingPoint</span>
}

<span class="keyword">extension</span> <span class="type">Slider</span> <span class="keyword">where</span> <span class="type">ValueLabel</span> == <span class="type">EmptyView</span> {
  <span class="keyword">init</span>&lt;V&gt;(
    value: <span class="type">Binding</span>&lt;<span class="type">V</span>&gt;, 
    <span class="keyword">in</span> bounds: <span class="type">ClosedRange</span>&lt;<span class="type">V</span>&gt; = <span class="number">0</span>...<span class="number">1</span>, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }, 
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>
  ) <span class="keyword">where</span> <span class="type">V</span>: <span class="type">BinaryFloatingPoint</span>, <span class="type">V</span>.<span class="type">Stride</span>: <span class="type">BinaryFloatingPoint</span>
  
  <span class="keyword">init</span>&lt;V&gt;(
    value: <span class="type">Binding</span>&lt;<span class="type">V</span>&gt;, 
    <span class="keyword">in</span> bounds: <span class="type">ClosedRange</span>&lt;<span class="type">V</span>&gt;, 
    step: <span class="type">V</span>.<span class="type">Stride</span> = <span class="number">1</span>, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }, 
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>
  ) <span class="keyword">where</span> <span class="type">V</span>: <span class="type">BinaryFloatingPoint</span>, <span class="type">V</span>.<span class="type">Stride</span>: <span class="type">BinaryFloatingPoint</span>
  
  <span class="keyword">init</span>&lt;V&gt;(
    value: <span class="type">Binding</span>&lt;<span class="type">V</span>&gt;, 
    <span class="keyword">in</span> bounds: <span class="type">ClosedRange</span>&lt;<span class="type">V</span>&gt; = <span class="number">0</span>...<span class="number">1</span>, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }
  ) <span class="keyword">where</span> <span class="type">V</span>: <span class="type">BinaryFloatingPoint</span>, <span class="type">V</span>.<span class="type">Stride</span>: <span class="type">BinaryFloatingPoint</span>
  
  <span class="keyword">init</span>&lt;V&gt;(
    value: <span class="type">Binding</span>&lt;<span class="type">V</span>&gt;, 
    <span class="keyword">in</span> bounds: <span class="type">ClosedRange</span>&lt;<span class="type">V</span>&gt;, 
    step: <span class="type">V</span>.<span class="type">Stride</span> = <span class="number">1</span>, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }
  ) <span class="keyword">where</span> <span class="type">V</span>: <span class="type">BinaryFloatingPoint</span>, <span class="type">V</span>.<span class="type">Stride</span>: <span class="type">BinaryFloatingPoint</span>
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> value = <span class="number">5.0</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Slider</span>(value: $value, in: <span class="number">1</span>...<span class="number">10</span>) { isOnFocus <span class="keyword">in</span>
      <span class="comment">// ..</span>
    }
  }
}
</code></pre><blockquote><p>Did you know that <code>Slider</code> <a href="https://twitter.com/zntfdr/status/1382569553910059008">uses styles internally</a>? We might be able to define our own soon! (FB9079800)</p></blockquote><p>Similar to <code>TextField</code>, <code>Slider</code> comes with an <code>onEditingChanged</code> closure used to communicate the focus status. Besides this, the binding takes care of everything.</p><h2>Steppers</h2><p>Stepper comes in two forms:</p><ol><li>a binding form</li><li>a free form</li></ol><h3>Stepper binding form</h3><pre><code><span class="comment">// Definition</span>
<span class="keyword">extension</span> <span class="type">Stepper</span> {
  <span class="keyword">init</span>&lt;V: <span class="type">Strideable</span>&gt;(
    value: <span class="type">Binding</span>&lt;<span class="type">V</span>&gt;, 
    step: <span class="type">V</span>.<span class="type">Stride</span> = <span class="number">1</span>, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }, 
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>
  )
        
  <span class="keyword">init</span>&lt;V: <span class="type">Strideable</span>&gt;(
    value: <span class="type">Binding</span>&lt;<span class="type">V</span>&gt;, 
    <span class="keyword">in</span> bounds: <span class="type">ClosedRange</span>&lt;<span class="type">V</span>&gt;, 
    step: <span class="type">V</span>.<span class="type">Stride</span> = <span class="number">1</span>, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }, 
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>
  )
}

<span class="keyword">extension</span> <span class="type">Stepper</span> <span class="keyword">where</span> <span class="type">Label</span> == <span class="type">Text</span> {
  <span class="keyword">init</span>&lt;V: <span class="type">Strideable</span>&gt;(
    <span class="keyword">_</span> titleKey: <span class="type">LocalizedStringKey</span>, 
    value: <span class="type">Binding</span>&lt;<span class="type">V</span>&gt;, 
    step: <span class="type">V</span>.<span class="type">Stride</span> = <span class="number">1</span>, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }
  )
  
  <span class="keyword">init</span>&lt;S: <span class="type">StringProtocol</span>, V: <span class="type">Strideable</span>&gt;(
    <span class="keyword">_</span> title: <span class="type">S</span>, 
    value: <span class="type">Binding</span>&lt;<span class="type">V</span>&gt;, 
    step: <span class="type">V</span>.<span class="type">Stride</span> = <span class="number">1</span>, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }
  )
  
  <span class="keyword">init</span>&lt;V: <span class="type">Strideable</span>&gt;(
    <span class="keyword">_</span> titleKey: <span class="type">LocalizedStringKey</span>, 
    value: <span class="type">Binding</span>&lt;<span class="type">V</span>&gt;, 
    <span class="keyword">in</span> bounds: <span class="type">ClosedRange</span>&lt;<span class="type">V</span>&gt;, 
    step: <span class="type">V</span>.<span class="type">Stride</span> = <span class="number">1</span>, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }
  )
  
  <span class="keyword">init</span>&lt;S: <span class="type">StringProtocol</span>, V: <span class="type">Strideable</span>&gt;(
    <span class="keyword">_</span> title: <span class="type">S</span>, value: <span class="type">Binding</span>&lt;<span class="type">V</span>&gt;, 
    <span class="keyword">in</span> bounds: <span class="type">ClosedRange</span>&lt;<span class="type">V</span>&gt;, 
    step: <span class="type">V</span>.<span class="type">Stride</span> = <span class="number">1</span>, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }
  )
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> value = <span class="number">5</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Stepper</span>(<span class="string">"Value:"</span>, value: $value, step: <span class="number">1</span>) { isOnFocus <span class="keyword">in</span>
      <span class="comment">// ..</span>
    }
  }
}
</code></pre><p>In this form, we're essentially looking at a component similar to <code>Slider</code>, just with a different look.</p><h3>Stepper free form</h3><pre><code><span class="comment">// Definition</span>
<span class="keyword">extension</span> <span class="type">Stepper</span> {
  <span class="keyword">init</span>(
    onIncrement: (() -&gt; <span class="type">Void</span>)?, 
    onDecrement: (() -&gt; <span class="type">Void</span>)?, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }, 
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>
  )
}

<span class="keyword">extension</span> <span class="type">Stepper</span> <span class="keyword">where</span> <span class="type">Label</span> == <span class="type">Text</span> {
  <span class="keyword">init</span>(
    <span class="keyword">_</span> titleKey: <span class="type">LocalizedStringKey</span>, 
    onIncrement: (() -&gt; <span class="type">Void</span>)?, 
    onDecrement: (() -&gt; <span class="type">Void</span>)?, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }
  )
  
  <span class="keyword">init</span>&lt;S: <span class="type">StringProtocol</span>&gt;(
    <span class="keyword">_</span> title: <span class="type">S</span>, 
    onIncrement: (() -&gt; <span class="type">Void</span>)?, 
    onDecrement: (() -&gt; <span class="type">Void</span>)?, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }
  )
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Stepper</span>(
      <span class="string">"Value"</span>,
      onIncrement: {
        <span class="comment">// called on increment tap</span>
      }, onDecrement: {
        <span class="comment">// called on decrement tap</span>
      }) { isOnFocus <span class="keyword">in</span>
      <span class="comment">//</span>
    }
  }
}
</code></pre><p>The SwiftUI team could have stopped at the <code>@Binding</code> form, instead, they went ahead and provided us a more generic and stateless <code>Stepper</code> to be used as we please.</p><p>This form offers three closures:</p><ol><li><code>onIncrement</code>, triggered when the user taps the increment button</li><li><code>onDecrement</code>, equivalent to the above for the decrement button</li><li><code>onEditingChanged</code>, for the usual focus event</li></ol><p><code>onIncrement</code> and <code>onDecrement</code> closures replace <code>UIStepper</code>'s single <code>.valueChanged</code> event, removing the need for our views to distinguish and manage these events ourselves.</p><p>Note also how <code>onIncrement</code> and <code>onDecrement</code> do not come with a default value, meaning that we cannot mistakenly define a stepper that does nothing, e.g. <code>Stepper("Value")</code>.</p><p>Instead, while both <code>onIncrement</code> and <code>onDecrement</code> are optional, we're required to define them ourselves.</p><p>If we really want a <code>Stepper</code> that does nothing, we'd need to write <code>Stepper("Value", onIncrement: nil, onDecrement: nil)</code>. I'm sure any PR reviewer would have some questions, though!</p><h2>SubscriptionView</h2><p>For completeness's sake, the last public SwiftUI view making uses of closures is a view that has no UIKit equivalent, as it's a SwiftUI implementation detail:</p><pre><code><span class="keyword">struct</span> SubscriptionView&lt;PublisherType: <span class="type">Publisher</span>, Content: <span class="type">View</span>&gt;: <span class="type">View</span> <span class="keyword">where</span> <span class="type">PublisherType</span>.<span class="type">Failure</span> == <span class="type">Never</span> {
  <span class="keyword">init</span>(
    content: <span class="type">Content</span>, 
    publisher: <span class="type">PublisherType</span>, 
    action: <span class="keyword">@escaping</span> (<span class="type">PublisherType</span>.<span class="type">Output</span>) -&gt; <span class="type">Void</span>
  )
}
</code></pre><p><code>SubscriptionView</code> is a view that takes in:</p><ul><li>another view via the <code>content</code> parameter, which is what we will display on the screen</li><li>a publisher, which <code>SubscriptionView</code> will subscribe to</li><li>an <code>action</code> closure, triggered when said publisher publishes anything</li></ul><p>In other words, it associates an observer + action to another view, for example:</p><pre><code><span class="type">SubscriptionView</span>(
  content: <span class="type">FSView</span>(),
  publisher: <span class="type">NotificationCenter</span>.<span class="property">default</span>.<span class="call">publisher</span>(for: <span class="type">UIApplication</span>.<span class="property">didBecomeActiveNotification</span>),
  action: { output <span class="keyword">in</span>
    <span class="comment">// received didBecomeActiveNotification</span>
  }
)
</code></pre><p>Which is equivalent to writing:</p><pre><code><span class="type">FSView</span>()
  .<span class="call">onReceive</span>(<span class="type">NotificationCenter</span>.<span class="property">default</span>.<span class="call">publisher</span>(for: <span class="type">UIApplication</span>.<span class="property">didBecomeActiveNotification</span>)) { <span class="keyword">_ in</span>
    <span class="comment">// received didBecomeActiveNotification</span>
  }
</code></pre><p>Using the same technique from <a href="https://www.fivestars.blog/articles/inspecting-views/">Inspecting SwiftUI views</a>, we see that these two views are indeed identical, making <code>SubscriptionView</code> an implementation detail of <code>onReceive(_:perform:)</code>, here's the <code>Mirror</code> output for both views:</p><pre><code><span class="type">SubscriptionView</span>&lt;<span class="type">Publisher</span>, <span class="type">FSView</span>&gt;(
  content: <span class="type">FSView</span>,
  publisher: <span class="type">Publisher</span>(
    center: <span class="type">NSNotificationCenter</span>,
    name: <span class="type">NSNotificationName</span>(
      _rawValue: <span class="type">__NSCFConstantString</span>
    ),
    object: <span class="type">Optional</span>&lt;<span class="type">AnyObject</span>&gt;
  ),
  action: (<span class="type">Notification</span>) -&gt; ()
)
</code></pre><p>When building our own views, the preferred way is using <code>onReceive(_:perform:)</code>.</p><h2>Recap</h2><p>It's always interesting to see how SwiftUI takes old patterns and replaces them with a more modern approach. Here's a quick summary of all view closures used in SwiftUI views:</p><ul><li><code>Button</code> definitions take in an <code>action: @escaping () -&gt; Void</code> parameter</li><li><code>TextField</code>s come with two closure with default values <code>onEditingChanged: @escaping (Bool) -&gt; Void = { _ in }</code>, and <code>onCommit: @escaping () -&gt; Void = {}</code></li><li><code>SecureField</code>s come only with the <code>onCommit: @escaping () -&gt; Void = {}</code> closure</li><li><code>Slider</code>s also come with <code>onEditingChanged: @escaping (Bool) -&gt; Void = { _ in }</code></li><li><code>Stepper</code>s come in two forms:<ul><li>in the binding form, they come only with <code>onEditingChanged: @escaping (Bool) -&gt; Void = { _ in }</code></li><li>in the free form, beside the usual <code>onEditingChanged</code> closure, they also have optional <code>onIncrement: (() -&gt; Void)?</code> and <code>onDecrement: (() -&gt; Void)?</code> closures</li></ul></li></ul><ul><li>Lastly, SwiftUI's implementation detail <code>SubscriptionView</code> comes with a <code>action: @escaping (PublisherType.Output) -&gt; Void</code> closure parameter</li></ul><p>The rule seems to be:</p><ul><li>use <code>action</code> when the closure is a core part of the view definition</li><li>use <code>onEditingChanged: @escaping (Bool) -&gt; Void = { _ in }</code> for the view focus</li><li>use <code>on...</code> when the closure is triggered only on an associated event (in the name of the parameter), and provide a default implementation when it's not a core part of the view definition</li></ul><h2>Conclusions</h2><p>This article explored how SwiftUI has replaced UIKit's <code>Target-Action</code> pattern with bindings and Swift closures.</p><p>These replacements are a core part of what makes SwiftUI what it is:</p><ul><li>by using bindings, we eliminate all possible inconsistencies that come with having multiple sources of truth</li><li>by requiring all view closures directly in their initializers, it's clear from the start what each view offers out of the box, and it's far less likely to misuse or misunderstand any view</li></ul><p>What other patterns have you seen emerge or sunset?<br>Please let me know via <a href="mailto:hello@fivestars.blog">email</a> or <a href="https://twitter.com/zntfdr">twitter</a>, thank you for reading!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-introspect</guid><title>SwiftUI Introspect</title><description>A deep dive into one of the must-have libraries for any SwiftUI app!</description><link>https://www.fivestars.blog/articles/swiftui-introspect</link><pubDate>Tue, 18 May 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>When it comes to building apps, SwiftUI guarantees iteration times that were not possible before.</p><p>SwiftUI can probably cover about 95% of any modern app needs, with the last 5% being polishing SwiftUI's rough edges by falling back on one of the previous UI frameworks.</p><p>In <a href="https://www.fivestars.blog/articles/how-to-customize-textfields/">Four ways to customize TextFields</a>, we've seen the two main fallback methods:</p><ul><li>SwiftUI's <code>UIViewRepresentable</code>/<code>NSViewRepresentable</code></li><li><a href="https://github.com/siteline/SwiftUI-Introspect ">SwiftUI Introspect</a></li></ul><p>In this article, let's take a look at SwiftUI Introspect.</p><h2>What's SwiftUI Introspect</h2><p>SwiftUI Introspect is an <a href="https://github.com/siteline/SwiftUI-Introspect ">open-source library</a> created by <a href="https://loisdiqual.com">Loïs Di Qual</a>. Its primary purpose is to fetch and modify the underlying UIKit or AppKit elements of any SwiftUI view.</p><p>This is possible thanks to many SwiftUI views (still) relying on their UIKit/AppKit counterparts, for example:</p><ul><li>in macOS, <code>Button</code> uses <code>NSButton</code> behind the scenes</li><li>in iOS, <code>TabView</code> uses a <code>UITabBarController</code> behind the scenes</li></ul><p>We rarely need to know such implementation details. However, knowing so gives us yet another powerful tool we can reach for when needed. This is precisely where SwiftUI Introspect comes in.</p><h2>Using SwiftUI Introspect</h2><p>SwiftUI Introspect provides us a <a href="https://github.com/siteline/SwiftUI-Introspect/blob/72a509c93166540c0adf8323fd2652daade7f9f6/Introspect/ViewExtensions.swift">series of view modifiers</a> following the <code>func introspectX(customize: @escaping (Y) -&gt; ()) -&gt; some View</code> pattern, where:</p><ul><li><code>X</code> is the view we're targeting</li><li><code>Y</code> is the underlying UIKit/AppKit view/view-controller type we'd like to reach for</li></ul><p>Let's say that we'd like to remove the bouncing effect from a <code>ScrollView</code>.<br>Currently, there's no SwiftUI parameter/modifier letting us do so (FB9106829).</p><p><code>ScrollView</code> uses UIKit's <code>UIScrollView</code> behind the scenes, and AppKit's <code>NSScrollView</code> in macOS. We can use Introspect's <code>func introspectScrollView(customize: @escaping (UIScrollView) -&gt; ()) -&gt; some View</code> to grab the underlying <code>UIScrollView</code>, and disable the bouncing:</p><pre><code><span class="keyword">import</span> Introspect
<span class="keyword">import</span> SwiftUI

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollView</span> {
      <span class="type">VStack</span> {
        <span class="type">Color</span>.<span class="property">red</span>.<span class="call">frame</span>(height: <span class="number">300</span>)
        <span class="type">Color</span>.<span class="property">green</span>.<span class="call">frame</span>(height: <span class="number">300</span>)
        <span class="type">Color</span>.<span class="property">blue</span>.<span class="call">frame</span>(height: <span class="number">300</span>)
      }
      .<span class="call">introspectScrollView</span> { $0.<span class="property">bounces</span> = <span class="keyword">false</span> }
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/swiftui-introspect/scroll.gif"/><p>In iOS, users can dismiss sheets by swiping them down. In UIKit, we can prevent this behavior via the <a href="https://developer.apple.com/documentation/uikit/uiviewcontroller/3229894-ismodalinpresentation"><code>isModalInPresentation</code></a> <code>UIViewController</code> property, letting our app logic control the sheet presentation. In SwiftUI, we don't have an equivalent way to do so yet (FB9106857).</p><p>Once again, we can use Introspect to grab the presenting sheet <code>UIViewController</code>, and set the <code>isModalInPresentation</code> property:</p><pre><code><span class="keyword">import</span> Introspect
<span class="keyword">import</span> SwiftUI

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> showingSheet = <span class="keyword">false

  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(<span class="string">"Show sheet"</span>) { showingSheet.<span class="call">toggle</span>() }
      .<span class="call">sheet</span>(isPresented: $showingSheet) {
        <span class="type">Button</span>(<span class="string">"Dismiss sheet"</span>) { showingSheet.<span class="call">toggle</span>() }
          .<span class="call">introspectViewController</span> { $0.<span class="property">isModalInPresentation</span> = <span class="keyword">true</span> }
      }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/swiftui-introspect/sheet.gif"/><p>Other examples:</p><ul><li>add <a href="https://github.com/siteline/SwiftUIRefresh/blob/fa8fac7b5eb5c729983a8bef65f094b5e0d12014/Sources/PullToRefresh.swift">pull to refresh</a> to <code>List</code>s (FB8506858)</li><li>add <a href="https://www.fivestars.blog/articles/how-to-customize-textfields/">toolbars</a> to <code>TextField</code>s (see <a href="https://www.fivestars.blog/articles/how-to-customize-textfields/"><code>The introspect way</code> paragraph</a>, FB9081641)</li><li>...and <a href="https://github.com/siteline/SwiftUI-Introspect/blob/master/IntrospectExamples/ContentView.swift">much more</a>.</li></ul><p>Imagine having to re-implement a whole complex screen in UIKit/AppKit because of a minor feature missing in SwiftUI: Introspect is an incredible time (life?) saver.</p><p>We've seen its clear benefits: next, let's uncover how SwiftUI Introspect works.</p><h2>How SwiftUI Introspect works</h2><blockquote><p>We will take the UIKit route: beside the <code>UI</code>/<code>NS</code> prefixes, AppKit's code is identical.</p></blockquote><p>The code shown in the article has been slightly adjusted for clarity's sake. The original implementation is available in <a href="https://github.com/siteline/SwiftUI-Introspect ">SwiftUI Introspect's repository</a>.</p><h3>The injection</h3><p>As shown in the examples above, Introspect provides us various view modifiers. If we look at their implementation, they all follow a similar pattern. Here's one example:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="comment">/// Finds a `UITextView` from a `TextEditor`</span>
  public <span class="keyword">func</span> introspectTextView(
    customize: <span class="keyword">@escaping</span> (<span class="type">UITextView</span>) -&gt; ()
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">introspect</span>(
      selector: <span class="type">TargetViewSelector</span>.<span class="property">siblingContaining</span>, 
      customize: customize
    )
  }
}
</code></pre><p>All these public <code>introspectX(customize:)</code> view modifiers are convenience implementations of a more generic <code>introspect(selector:customize:)</code> one:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {   
  <span class="comment">/// Finds a `TargetView` from a `SwiftUI.View`</span>
  public <span class="keyword">func</span> introspect&lt;TargetView: <span class="type">UIView</span>&gt;(
    selector: <span class="keyword">@escaping</span> (<span class="type">IntrospectionUIView</span>) -&gt; <span class="type">TargetView</span>?,
    customize: <span class="keyword">@escaping</span> (<span class="type">TargetView</span>) -&gt; ()
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">inject</span>(
      <span class="type">UIKitIntrospectionView</span>(
        selector: selector,
        customize: customize
      )
    )
  }
}
</code></pre><p>Here we see the introduction of one more <code>inject(_:)</code> <code>View</code> modifier, and the first Introspect view, <code>UIKitIntrospectionView</code>:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">public func</span> inject&lt;SomeView: <span class="type">View</span>&gt;(<span class="keyword">_</span> view: <span class="type">SomeView</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">overlay</span>(view.<span class="call">frame</span>(width: <span class="number">0</span>, height: <span class="number">0</span>))
  }
}
</code></pre><p><code>inject(_:)</code> takes our original view and adds on top an overlay with the given view, with its frame minimized.</p><p>For example, if we have the following view:</p><pre><code><span class="type">TextView</span>(...)
  .<span class="call">introspectTextView</span> { ... }
</code></pre><p>The final view will be:</p><pre><code><span class="type">TextView</span>(...)
  .<span class="call">overlay</span>(<span class="type">UIKitIntrospectionView</span>(...).<span class="call">frame</span>(width: <span class="number">0</span>, height: <span class="number">0</span>))
</code></pre><p>Let's take a look at <code>UIKitIntrospectionView</code> next:</p><pre><code><span class="keyword">public struct</span> UIKitIntrospectionView&lt;TargetViewType: <span class="type">UIView</span>&gt;: <span class="type">UIViewRepresentable</span> {
  <span class="keyword">let</span> selector: (<span class="type">IntrospectionUIView</span>) -&gt; <span class="type">TargetViewType</span>?
  <span class="keyword">let</span> customize: (<span class="type">TargetViewType</span>) -&gt; <span class="type">Void</span>

  <span class="keyword">public func</span> makeUIView(
    context: <span class="type">UIViewRepresentableContext</span>&lt;<span class="type">UIKitIntrospectionView</span>&gt;
  ) -&gt; <span class="type">IntrospectionUIView</span> {
    <span class="keyword">let</span> view = <span class="type">IntrospectionUIView</span>()
    view.<span class="property">accessibilityLabel</span> = <span class="string">"IntrospectionUIView&lt;</span>\(<span class="type">TargetViewType</span>.<span class="keyword">self</span>)<span class="string">&gt;"</span>
    <span class="keyword">return</span> view
  }

  <span class="keyword">public func</span> updateUIView(
    <span class="keyword">_</span> uiView: <span class="type">IntrospectionUIView</span>,
    context: <span class="type">UIViewRepresentableContext</span>&lt;<span class="type">UIKitIntrospectionView</span>&gt;
  ) {
    <span class="type">DispatchQueue</span>.<span class="property">main</span>.<span class="call">async</span> {
      <span class="keyword">guard let</span> targetView = <span class="keyword">self</span>.<span class="call">selector</span>(uiView) <span class="keyword">else</span> { <span class="keyword">return</span> }
      <span class="keyword">self</span>.<span class="call">customize</span>(targetView)
    }
  }
}
</code></pre><p><code>UIKitIntrospectionView</code> is Introspect's bridge to UIKit, which does two things:</p><ol><li>injects an <code>IntrospectionUIView</code> <code>UIView</code> in the hierarchy</li><li>reacts to <code>UIViewRepresentable</code>'s <code>updateUIView</code> life-cycle event (more on this later)</li></ol><p>This is <code>IntrospectionUIView</code>'s definition:</p><pre><code><span class="keyword">public class</span> IntrospectionUIView: <span class="type">UIView</span> {
  <span class="keyword">required init</span>() {
    <span class="keyword">super</span>.<span class="keyword">init</span>(frame: .<span class="dotAccess">zero</span>)
    isHidden = <span class="keyword">true</span>
    isUserInteractionEnabled = <span class="keyword">false</span>
  }
}
</code></pre><p><code>IntrospectionUIView</code> is a minimal, hidden, and noninteractive <code>UIView</code>: its whole purpose is to give SwiftUI Introspect an entry point into UIKit's hierarchy.</p><p>In conclusion, all <code>.introspectX(customize:)</code> view modifiers overlay a tiny, invisible, noninteractive view on top of our original view, making sure that it doesn't affect our final UI.</p><h3>The crawling</h3><p>We've now seen how SwiftUI Introspect reaches the UIKit hierarchy. All it's left to do for the library is to find the UIKit view/view-controller we're looking for.</p><p>Going back to <code>UIKitIntrospectionView</code>'s implementation, the magic happens in <code>updateUIView(_:context)</code>, which is one of the <code>UIViewRepresentable</code> life-cycle methods:</p><pre><code><span class="keyword">public struct</span> UIKitIntrospectionView&lt;TargetViewType: <span class="type">UIView</span>&gt;: <span class="type">UIViewRepresentable</span> {
  <span class="keyword">let</span> selector: (<span class="type">IntrospectionUIView</span>) -&gt; <span class="type">TargetViewType</span>?
  <span class="keyword">let</span> customize: (<span class="type">TargetViewType</span>) -&gt; <span class="type">Void</span>

  ...

  <span class="keyword">public func</span> updateUIView(
    <span class="keyword">_</span> uiView: <span class="type">IntrospectionUIView</span>,
    context: <span class="type">UIViewRepresentableContext</span>&lt;<span class="type">UIKitIntrospectionView</span>&gt;
  ) {
    <span class="type">DispatchQueue</span>.<span class="property">main</span>.<span class="call">async</span> {
      <span class="keyword">guard let</span> targetView = <span class="keyword">self</span>.<span class="call">selector</span>(uiView) <span class="keyword">else</span> { <span class="keyword">return</span> }
      <span class="keyword">self</span>.<span class="call">customize</span>(targetView)
    }
  }
}
</code></pre><p>In <code>UIKitIntrospectionView</code>'s case, this method is called by SwiftUI mainly in two scenarios:</p><ul><li>when <code>IntrospectionUIView</code> is about to be added into the view hierarchy</li><li>when <code>IntrospectionUIView</code> is about to be removed from the view hierarchy</li></ul><p>The <code>async</code> dispatch has two functions:</p><ol><li>if the method is called when the view is about to be added into the view hierarchy, we need to wait for the current runloop cycle to complete before our view is actually added (into the view hierarchy), then, and only then, we can start our search for our target view</li></ol><ol start="2"><li>if the method is called when the view is about to be removed from the view hierarchy, waiting for the runloop cycle to complete assures that our view has been removed (thus making our search fail)</li></ol><p>When SwiftUI triggers <code>updateUIView(_:context)</code>, <code>UIKitIntrospectionView</code> calls the <code>selector</code> method that we've been carrying over from the original convenience modifier implementation:<br><code>selector</code> has a <code>(IntrospectionUIView) -&gt; TargetViewType?</code> signature, a.k.a. it takes in Introspect's <code>IntrospectionUIView</code>'s view as input, and returns an optional <code>TargetViewType</code>, which is a generic representation of our original view/view-controller type that we'd like to reach for.</p><p>If this search succeeds, then we call <code>customize</code> on it, which is the method we pass/define when we apply an Introspect's view modifier on our views, thus making our change to the underlying UIKit/AppKit view/view-controller.</p><p>Going back to our <code>introspectTextView(customize:)</code> example, we pass <code>TargetViewSelector.siblingContaining</code> to our <code>selector</code>:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="comment">/// Finds a `UITextView` from a `TextEditor`</span>
  public <span class="keyword">func</span> introspectTextView(
    customize: <span class="keyword">@escaping</span> (<span class="type">UITextView</span>) -&gt; ()
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">introspect</span>(
      selector: <span class="type">TargetViewSelector</span>.<span class="property">siblingContaining</span>, 
      customize: customize
    )
  }
}
</code></pre><p><code>TargetViewSelector</code> is a caseless Swift <code>enum</code>, making it a container of <code>static</code> methods meant to be called directly, all <code>TargetViewSelector</code> methods, more or less, follow the same pattern as our <code>siblingContaing(from:)</code>:</p><pre><code><span class="keyword">public enum</span> TargetViewSelector {
  <span class="keyword">public static func</span> siblingContaining&lt;TargetView: <span class="type">UIView</span>&gt;(from entry: <span class="type">UIView</span>) -&gt; <span class="type">TargetView</span>? {
    <span class="keyword">guard let</span> viewHost = <span class="type">Introspect</span>.<span class="call">findViewHost</span>(from: entry) <span class="keyword">else</span> {
      <span class="keyword">return nil</span>
    }
    <span class="keyword">return</span> <span class="type">Introspect</span>.<span class="call">previousSibling</span>(containing: <span class="type">TargetView</span>.<span class="keyword">self</span>, from: viewHost)
  }

  ...
}
</code></pre><p>The first step is finding a view host:<br>SwiftUI wraps each <code>UIViewRepresentable</code> view within a host view, something along the lines of <code>PlatformViewHost&lt;PlatformViewRepresentableAdaptor&lt;IntrospectionUIView&gt;&gt;</code>, which is then wrapped into a "hosting view" of type <code>_UIHostingView</code>, representing an <code>UIView</code> capable of hosting a SwiftUI view.</p><p>To get the view host, Introspect uses a <code>findViewHost(from:)</code> static method from another caseless <code>Introspect</code> <code>enum</code>:</p><pre><code><span class="keyword">enum</span> Introspect {
  <span class="keyword">public static func</span> findViewHost(from entry: <span class="type">UIView</span>) -&gt; <span class="type">UIView</span>? {
    <span class="keyword">var</span> superview = entry.<span class="property">superview</span>
    <span class="keyword">while let</span> s = <span class="call">superview</span> {
      <span class="keyword">if</span> <span class="type">NSStringFromClass</span>(<span class="call">type</span>(of: s)).<span class="call">contains</span>(<span class="string">"ViewHost"</span>) {
        <span class="keyword">return</span> s
      }
      superview = s.<span class="property">superview</span>
    }
    <span class="keyword">return nil</span>
  }

  ...
}
</code></pre><p>This method starts from our <code>IntrospectionUIView</code> and recursively queries each <code>superview</code>s until a view host is found: if we cannot find a view host, our <code>IntrospectionUIView</code> is not yet part of the screen hierarchy, and our crawling stops right away.</p><p>Once we have our view host, we have our starting point to look for our target view, which is exactly what <code>TargetViewSelector.siblingContaing</code> does via the next <code>Introspect.previousSibling(containing: TargetView.self, from: viewHost)</code> command:</p><pre><code><span class="keyword">enum</span> Introspect {
  <span class="keyword">public static func</span> previousSibling&lt;AnyViewType: <span class="type">UIView</span>&gt;(
    containing type: <span class="type">AnyViewType</span>.<span class="type">Type</span>,
    from entry: <span class="type">UIView</span>
  ) -&gt; <span class="type">AnyViewType</span>? {

    <span class="keyword">guard let</span> superview = entry.<span class="property">superview</span>,
          <span class="keyword">let</span> entryIndex = superview.<span class="property">subviews</span>.<span class="call">firstIndex</span>(of: entry),
          entryIndex &gt; <span class="number">0</span>
    <span class="keyword">else</span> {
      <span class="keyword">return nil</span>
    }

    <span class="keyword">for</span> subview <span class="keyword">in</span> superview.<span class="property">subviews</span>[<span class="number">0</span>..&lt;entryIndex].<span class="call">reversed</span>() {
      <span class="keyword">if let</span> typed = <span class="call">findChild</span>(ofType: type, in: subview) {
        <span class="keyword">return</span> typed
      }
    }

    <span class="keyword">return nil</span>
  }

  ...
}
</code></pre><p>This new static method takes all <code>viewHost</code>'s parent's subviews (a.k.a. <code>viewHost</code>'s siblings), filter the subviews that come before <code>viewHost</code>, and recursively search for our target view (passed as a <code>type</code> parameter), from closest to furthest sibling, via the final <code>findChild(ofType:in:)</code> method:</p><pre><code><span class="keyword">enum</span> Introspect {
  <span class="keyword">public static func</span> findChild&lt;AnyViewType: <span class="type">UIView</span>&gt;(
    ofType type: <span class="type">AnyViewType</span>.<span class="type">Type</span>,
    in root: <span class="type">UIView</span>
  ) -&gt; <span class="type">AnyViewType</span>? {
    <span class="keyword">for</span> subview <span class="keyword">in</span> root.<span class="property">subviews</span> {
      <span class="keyword">if let</span> typed = subview <span class="keyword">as</span>? <span class="type">AnyViewType</span> {
        <span class="keyword">return</span> typed
      } <span class="keyword">else if let</span> typed = <span class="call">findChild</span>(ofType: type, in: subview) {
        <span class="keyword">return</span> typed
      }
    }
    <span class="keyword">return nil</span>
  }

  ...
}
</code></pre><p>This method, called by passing our target view and one of our <code>viewHost</code> siblings, will crawl each sibling complete subtree view hierarchy looking for our target view, and return the first match, if any.</p><h2>Analysis</h2><p>Now that we've uncovered all the inner workings of SwiftUI Introspect, it's much easier to answer common questions that we might have:</p><ul><li>Is it safe to use?</li></ul><p>As long as we don't do <em>too daring</em> things, yes. It's important to understand that we do not own the underlying AppKit/UIKit views, SwiftUI does. Changes applied via Introspect should work, however SwiftUI might override them at will and without notice.</p><ul><li>Is it future proof?</li></ul><p>No. As SwiftUI evolves, things might break and already have when new OS versions come out. The library is updated with new patches when this happens, however our users would need to update the app before they see a fix.</p><ul><li>Should we use it?</li></ul><p>If the choice is either a complete rewrite or SwiftUI Introspect, the answer is probably yes. Anyone who has read this far fully understands how the library works: if anything breaks, we should know where to look for and find a fix.</p><ul><li>Where does SwiftUI Introspect shine?</li></ul><p>Backward compatibility. Let's imagine, for example, that iOS 15 brings pull to refresh to <code>List</code> (fingers crossed! FB8506858):<br>we know that SwiftUI Introspect lets us <a href="https://github.com/siteline/SwiftUIRefresh/blob/fa8fac7b5eb5c729983a8bef65f094b5e0d12014/Sources/PullToRefresh.swift">add pull to refresh</a> to <code>List</code> in iOS 13 and 14. At that point, we can use Introspect when targeting older OS versions, and use the new SwiftUI way when targeting iOS 15 or later.</p><p>Doing so guarantees that things won't break, as newer OS version will use SwiftUI's "native" approach, and only past iOS versions use Introspect.</p><ul><li>When not to use SwiftUI Introspect?</li></ul><p>When we want complete control over a view and can't afford things to break with new OS releases: if this is our case, it's safer and more future-proof to go with <code>UIViewRepresentable</code>/<code>NSViewRepresentable</code>. Of course, we should always attempt as best we can to find a "pure" SwiftUI way first, and only when we're confident that it's not possible, look for alternatives.</p><h2>Conclusions</h2><p>SwiftUI Introspect is one of the few SwiftUI libraries that is probably a must-have to any SwiftUI app. Its execution is elegant, safe, and its advantages far outweigh the cons of adding it as a dependency.</p><p>When adding a dependency to our project, we should understand as best as we can what that dependency does, and I hope this article helps you in doing so for SwiftUI Introspect.</p><p>What other SwiftUI library do you use in your projects? Please let me know via <a href="mailto:hello@fivestars.blog">email</a> or <a href="https://twitter.com/zntfdr">twitter</a>!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/wwdc21-wishlist</guid><title>My WWDC21 Wishlist</title><description>My hopes for WWDC21!</description><link>https://www.fivestars.blog/articles/wwdc21-wishlist</link><pubDate>Tue, 11 May 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>With WWDC21 less than a month away, this is a period of hope and excitement for Apple developers worldwide.</p><p>Every one of us has a long list of things we'd like to see announced, and probably an even longer list of Apple feedbacks that we cannot wait to close.</p><p>In this article, let's take a look at some of my wishes for this year's WWDC.</p><h2>Floating panel component</h2><blockquote><p>FB8992718</p></blockquote><img src="https://www.fivestars.blog/assets/posts/wwdc20-wishlist/floating.png"/><blockquote><p>From left to right: A floating panel in the shortcuts.app, stocks.app, find-my.app, maps.app.</p></blockquote><p>If Apple gave me a magic wand and told me that I could make just one change, releasing an official floating panel UI element would be my pick.</p><p>Drawer, Floating Panel, Bottom Sheet are just a few of the names going around, this is a component heavily used in many stock apps (see screenshot above), it's very user/thumb-friendly, and it's making ways in many third-party apps as well (including <a href="https://itunes.apple.com/us/developer/federico-zanetello/id1053443073">my own</a>).</p><p>Apple already has a component named <a href="https://github.com/LeoNatan/Apple-Runtime-Headers/blob/5e50ad05dfd7d7b69fc2e0e685765fc054166b3c/iOS/PrivateFrameworks/UIKitCore.framework/_UISheetDetent.h"><code>UISheetDetents</code></a> for this. However, it's not public.</p><p>This element is tough to get right. There are several attempts out there, out of which <a href="https://github.com/scenee/FloatingPanel"><code>FloatingPanel</code></a> is the best one.</p><p>Having a native, standard floating panel would be incredibly beneficial:<br>last year we saw the consolidation on <a href="https://www.wwdcnotes.com/notes/wwdc20/10105/">how multiple column layouts look/work/behave</a>. This year I really want to see the same for this element.</p><h2>A way to "follow" Apple Developer Documentation updates</h2><blockquote><p>FB9096393</p></blockquote><img src="https://www.fivestars.blog/assets/posts/wwdc20-wishlist/docs.png"/><p>We saw a lot of <a href="https://twitter.com/zntfdr/status/1336534803017945088">new</a> <a href="https://twitter.com/zntfdr/status/1327099408345747456">SwiftUI</a> <a href="https://twitter.com/zntfdr/status/1356482517000278017">documentation</a> being added during the past year, including <a href="https://twitter.com/zntfdr/status/1336700623589318662">some technical articles</a>. However, there's no way to discover when new material is added, beside constantly monitoring all documentation pages.</p><p>Last year we obtained a feed RSS for the <a href="https://developer.apple.com/news/">Developer News</a>: maybe this year we can get another feed for the rest of the documentation?</p><h2>Automatic dark/light appearance switch based on ambient light</h2><blockquote><p>FB7602915</p></blockquote><img src="https://www.fivestars.blog/assets/posts/wwdc20-wishlist/books.png"/><p>The Books.app has been doing so even before Dark mode was announced two(!) years ago. While there are third-party tools doing this, having this functionality baked in with the rest of the system settings would be fantastic.</p><h2>System HUD(s) access</h2><blockquote><p>FB6534210</p></blockquote><img src="https://www.fivestars.blog/assets/posts/swiftui-hud/HUDImage.png"/><blockquote><p>Image from <a href="https://www.fivestars.blog/articles/swiftui-hud/">Custom HUDs in SwiftUI</a></p></blockquote><p>HUDs are one of these system components that every app needs to use at some point. Instead of reinventing the wheel over and over, it would be great if we could access the system's one(s).</p><p>Having access to system HUDs would guarantee apps to look more familiar, consistent, make them more accessible, and require way less work to third-party developers.</p><blockquote><p>There are almost <strong>1500 iOS HUD libraries</strong> on GitHub, split between <a href="https://github.com/search?l=Swift&q=hud&type=Repositories">Swift</a> and <a href="https://github.com/search?l=Objective-C&q=hud&type=Repositories">Objective-C</a>.</p></blockquote><h2>Health.app on iPadOS/macOS</h2><blockquote><p>FB9095458</p></blockquote><img src="https://www.fivestars.blog/assets/posts/wwdc20-wishlist/Health-icon.png"/><p>Apple's health efforts have been announced back in 2014 with HealthKit and the Health app:<br>from that point on, every year Apple's health capabilities broadened more and more, mainly pioneered by the Apple Watch.</p><p>While having access to all of this data on the phone is incredible, overviewing trends or stats such as resting heart rate, walking pace, etc., would be more valuable and productive on a bigger screen, especially when comparing large intervals of time.</p><h2>More proactive Apple crash reporting</h2><blockquote><p>FB7564366</p></blockquote><p>Until recently finding out about app crashes meant one of the following:</p><ul><li>opening Xcode's Organizer and download the available data from there, if the developer ever remembers to do so.</li><li>use a third-party framework/service.</li></ul><p>Two years ago Apple announced <a href="https://developer.apple.com/documentation/metrickit/">MetricKit</a>, a new system framework allowing app developers to collect their app diagnostics once per day per user, allowing faster reporting.</p><p>This is not yet as real time as getting a new crash report via email (almost) as soon as it happens, like other third-party SDKs/services nearly do, but it's a step forward. My wish for this year is to see MetricKit reports not being limited to once a day, allowing reports as fast as the competition.</p><h2>Xcode localization sanity checks</h2><blockquote><p>FB9095417</p></blockquote><p>App localization is a must-have for the best user experience:<br>it's probably impossible to guarantee at build time that our app will only use localization strings, however having some sanity checks would be most welcome.</p><p>For example, if all but one <code>.strings</code> file provides a <code>"home_title" = "...";</code> localization, triggering a warning would probably be the right thing to do.</p><p>Instead of these sanity checks being an extra build phase, they could be added as an external CLI tool, or triggered from time to time via an Xcode menu.</p><h2>A more proactive Feedback Assistant</h2><blockquote><p>FB9096364, FB9096366</p></blockquote><img src="https://www.fivestars.blog/assets/posts/wwdc20-wishlist/feedback.png"/><p>Currently we get a new email when an Apple engineer replies to a feedback of ours. However, we don't get notified when other feedback's statuses change (e.g., resolution, number of similar reports).</p><p>The only way we have to find out if anything has changed is by manually opening each feedback again, potentially discovering an update many weeks/months later, if ever.</p><p>It would be great if Feedback Assistant would send notifications/emails when any new update occurs, not just when there's a new reply to a feedback.</p><p>It's a small thing, but it'd help developers know their feedback has been received, and encourage them to use the app more, thus submitting more feedback.</p><h2>SwiftUI</h2><p>Lastly here's a <em>small</em> subset of my SwiftUI wishes:</p><ul><li>a new view similar to <a href="https://css-tricks.com/snippets/css/a-guide-to-flexbox/">CSS's Flexbox</a> (e.g. <code>HStack</code> <a href="https://www.fivestars.blog/articles/flexible-swiftui/">wrapping into multiple rows</a>) (FB9100345)</li><li>a way to access to and act upon a view proposed size (FB9100350)</li><li>have a native modifier to <a href="https://www.fivestars.blog/articles/swiftui-share-layout-information/">read a child view size</a> (FB9100353)</li><li>have access to <code>List</code>'s/<code>ScrollView</code>'s <a href="https://www.fivestars.blog/articles/scrollview-offset/">offset position</a> (FB9100361)</li><li><code>List</code>/<code>ScrollView</code> pull to refresh (FB8506858)</li><li><code>TextField</code> improvements:<ul><li>first responder control (FB9081556)</li><li>more keyboard input types, e.g. date and picker (FB9079186, FB9079187)</li><li>merge <code>TextField</code> and <code>SecureField</code> (FB8947595)</li></ul></li></ul><ul><li><a href="https://www.fivestars.blog/articles/programmatic-navigation/">a navigation revolution</a> (FB8722348, FB8910787, FB8997675)</li><li>a <code>Stack</code> view that can switch axis via parameter (FB9061954)</li><li>a way to snapshot SwiftUI views without having to go through AppKit/UIKit (FB9100363)</li><li>proper device landscape orientation for SwiftUI Previews (FB7636362)</li><li>native share sheet support (FB9088885)</li><li>new <code>onTapGesture</code> <code>Text</code> view modifier, which returns another <code>Text</code> (FB8917806)</li></ul><h2>Conclusions</h2><p>There we go! Most things are way easier said than done: I'd consider it a massive success if even 10% of these nearly 25+ wishes were to become true.</p><p>If my list has something that you'd like to see as well, I urge you to please <a href="http://feedbackassistant.apple.com">submit a feedback to Apple</a>: this is the best way to let their teams know. Thank you!</p><p>What else would you like to see at this year's WWDC? Please let me know via <a href="mailto:hello@fivestars.blog">email</a> or <a href="https://twitter.com/zntfdr">twitter</a>!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/textfieldstyle</guid><title>A TextFieldStyle API preview!</title><description>TextFieldStyle is not officially available yet, can we have a sneak peek at how it might look like?</description><link>https://www.fivestars.blog/articles/textfieldstyle</link><pubDate>Tue, 4 May 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Recently we've covered <a href="https://www.fivestars.blog/articles/how-to-customize-textfields/">four different ways to customize <code>TextField</code>s</a>:<br>ideally we wouldn't have to choose, as SwiftUI's <em>official</em> way to customize components is by using and creating associated styles.</p><p>While <code>TextFieldStyle</code>'s requirements are not public yet, we can take a sneak peek under the hood and guess at how an official API might look like: in this article, let's do just that!</p><blockquote><p>This article's code works, however please do consider it experimental and do not use it in production. As always, I have no insights on what the SwiftUI team is working on. This is entirely speculation with no inside knowledge.</p></blockquote><h2>TextFieldStyle</h2><p>Here's the current internal <code>TextFieldStye</code> declaration (as of Xcode 12.5):</p><pre><code><span class="keyword">public protocol</span> TextFieldStyle {
  <span class="keyword">associatedtype</span> _Body: <span class="type">View</span>
  <span class="keyword">@ViewBuilder func</span> _body(configuration: <span class="type">TextField</span>&lt;<span class="type">Self</span>.<span class="type">_Label</span>&gt;) -&gt; <span class="type">Self</span>.<span class="type">_Body</span>
  <span class="keyword">typealias</span> _Label = <span class="type">_TextFieldStyleLabel</span>
}
</code></pre><p>Beside a small difference in the <code>typealias</code> this declaration closely follows all other public styles.</p><p>As a reminder/comparison here are the public style requirements for <code>Button</code> and <code>Label</code>:</p><pre><code><span class="keyword">public protocol</span> ButtonStyle {
  <span class="keyword">associatedtype</span> Body: <span class="type">View</span>
  <span class="keyword">@ViewBuilder func</span> makeBody(configuration: <span class="type">Self</span>.<span class="type">Configuration</span>) -&gt; <span class="type">Self</span>.<span class="type">Body</span>
  <span class="keyword">typealias</span> Configuration = <span class="type">ButtonStyleConfiguration</span>
}

<span class="keyword">public protocol</span> LabelStyle {
  <span class="keyword">associatedtype</span> Body: <span class="type">View</span>
  <span class="keyword">func</span> makeBody(configuration: <span class="type">Self</span>.<span class="type">Configuration</span>) -&gt; <span class="type">Self</span>.<span class="type">Body</span>
  <span class="keyword">typealias</span> Configuration = <span class="type">LabelStyleConfiguration</span>
}
</code></pre><blockquote><p>We have dedicated articles to both styles in <a href="https://www.fivestars.blog/articles/button-styles/">Exploring SwiftUI's Button styles</a> and <a href="https://www.fivestars.blog/articles/label/">Label</a>.</p></blockquote><p>All these requirements come with a configuration, which dictates what we can achieve on the associated style. Let's have a look at that next.</p><h2>TextFieldStyleConfiguration</h2><p>The current <code>TextFieldStyle</code> configuration is a <code>TextField</code> instance, <code>TextField&lt;Self._Label&gt;</code>:</p><pre><code><span class="keyword">@ViewBuilder func</span> _body(configuration: <span class="type">TextField</span>&lt;<span class="type">Self</span>.<span class="type">_Label</span>&gt;) -&gt; <span class="type">Self</span>.<span class="type">_Body</span>
</code></pre><p>It might be surprising that <code>TextField</code> has an associated generic type, if we look at the <a href="https://developer.apple.com/documentation/swiftui/textfield">official declaration</a>, we see that this is indeed true, however as of today, <code>TextField</code> only exposes init methods where <code>Label == Text</code>.</p><pre><code><span class="keyword">public struct</span> TextField&lt;Label: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">public var</span> body: <span class="keyword">some</span> <span class="type">View</span> { <span class="keyword">get</span> }
  <span class="keyword">public typealias</span> Body = <span class="keyword">some</span> <span class="type">View</span>
}

<span class="keyword">extension</span> <span class="type">TextField</span> <span class="keyword">where</span> <span class="type">Label</span> == <span class="type">Text</span> {
  <span class="keyword">public init</span>(
  	<span class="keyword">_</span> titleKey: <span class="type">LocalizedStringKey</span>, 
  	text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;, 
  	onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }, 
  	onCommit: <span class="keyword">@escaping</span> () -&gt; <span class="type">Void</span> = {}
  )

  ...
}
</code></pre><p><code>Label</code> represents the placeholder view: we will probably have more flexibility once SwiftUI drops <code>UITextField</code> and have its own independent implementation.</p><p>Using the same technique from <a href="https://www.fivestars.blog/articles/inspecting-views/">Inspecting SwiftUI views</a>, we can see what kind of details a <code>TextFieldStyle</code> configuration has:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> text = <span class="string">"FIVE STARS"</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TextField</span>(
      <span class="string">"type something..."</span>,
      text: $text
    )
    .<span class="call">textFieldStyle</span>(<span class="type">InspectStyle</span>())
  }
}

<span class="keyword">struct</span> InspectStyle: <span class="type">TextFieldStyle</span> {
  <span class="keyword">@ViewBuilder
  func</span> _body(configuration: <span class="type">TextField</span>&lt;<span class="type">_Label</span>&gt;) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">let</span> _ = <span class="call">print</span>(configuration)
    configuration
  }
}
</code></pre><p><code>InspectStyle</code> returns the configuration as is, just after letting us take a peek to what it looks like:</p><pre><code><span class="type">TextField</span>&lt;<span class="type">_TextFieldStyleLabel</span>&gt;(
  text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;,
  isSecure: <span class="type">Bool</span>,
  label: <span class="type">_TextFieldStyleLabel</span>,
  onEditingChanged: (<span class="type">Bool</span>) -&gt; (),
  onCommit: () -&gt; (),
  updatesContinuously: <span class="type">Bool</span>,
  uncommittedText: <span class="type">State</span>&lt;<span class="type">Optional</span>&lt;<span class="type">String</span>&gt;&gt;
)
</code></pre><blockquote><p>Formatted and simplified for clarity's sake.</p></blockquote><p>Most of what we see is expected, with a few exceptions:</p><ul><li><code>text</code>, <code>onEditingChanged</code>, and <code>onCommit</code> are the same parameters we pass to <code>TextField</code>'s <code>init</code></li><li><code>isSecure</code> tells us whether we're applying our style to a <code>TextField</code> or a <code>SecureField</code></li><li><code>label</code> is our placeholder view</li><li><code>updatesContinuously</code> and <code>uncommittedText</code> are implementation details (if you have any information on these, please <a href="mailto:hello@fivestars.blog">let me know!</a>)</li></ul><p><code>isSecure</code> is very interesting:<br>currently, if we'd like to swap between <code>TextField</code> and a <code>SecureField</code>, we'd need to replace the field with its counterpart. However, adding/replacing/removing views is frowned upon since it's one of the easiest ways to drop performance in SwiftUI.</p><p>Hopefully <code>SecureField</code> will get deprecated, and this <code>isSecure</code> property will be exposed as part of the <code>TextField</code> initializers instead (FB8947595).</p><h2>Playing with TextFieldStyle's Configuration</h2><p>Now that we've seen how the configuration looks like, we can use it as we please.</p><p>What about creating a style that adds a clear button?</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> text = <span class="string">"FIVE STARS"</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TextField</span>(
      <span class="string">"type something..."</span>,
      text: $text
    )
    .<span class="call">textFieldStyle</span>(<span class="type">ClearStyle</span>())
  }
}

<span class="keyword">struct</span> ClearStyle: <span class="type">TextFieldStyle</span> {
  <span class="keyword">@ViewBuilder
  func</span> _body(configuration: <span class="type">TextField</span>&lt;<span class="type">_Label</span>&gt;) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">let</span> mirror = <span class="type">Mirror</span>(reflecting: configuration)
    <span class="keyword">let</span> text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt; = mirror.<span class="call">descendant</span>(<span class="string">"_text"</span>) <span class="keyword">as</span>! <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;
    configuration
      .<span class="call">overlay</span>(
        <span class="type">Button</span> { text.<span class="property">wrappedValue</span> = <span class="string">""</span> } label: { <span class="type">Image</span>(systemName: <span class="string">"clear"</span>) }
          .<span class="call">padding</span>(),
        alignment: .<span class="dotAccess">trailing</span>
      )
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/textfieldstyle/clear.gif"/><p>Or maybe we'd like to have different visuals based on whether our text fields requirements are met:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> text = <span class="string">"FIVE STARS"</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TextField</span>(
      <span class="string">"type something..."</span>,
      text: $text
    )
    .<span class="call">textFieldStyle</span>(<span class="type">RequirementStyle</span>())
  }
}

<span class="keyword">struct</span> RequirementStyle: <span class="type">TextFieldStyle</span> {
  <span class="keyword">@ViewBuilder
  func</span> _body(configuration: <span class="type">TextField</span>&lt;<span class="type">_Label</span>&gt;) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">let</span> mirror = <span class="type">Mirror</span>(reflecting: configuration)
    <span class="keyword">let</span> text: <span class="type">String</span> = mirror.<span class="call">descendant</span>(<span class="string">"_text"</span>, <span class="string">"_value"</span>) <span class="keyword">as</span>! <span class="type">String</span>
    configuration
      .<span class="call">padding</span>()
      .<span class="call">background</span>(
        <span class="type">RoundedRectangle</span>(cornerRadius: <span class="number">16</span>)
          .<span class="call">strokeBorder</span>(text.<span class="property">count</span> &gt; <span class="number">3</span> ? <span class="type">Color</span>.<span class="property">green</span> : <span class="type">Color</span>.<span class="property">red</span>)
      )
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/textfieldstyle/req.gif"/><p>We can also be mischievous and call <code>onEditingChanged</code> or <code>onCommit</code> at will:</p><pre><code><span class="keyword">struct</span> DeceiveStyle: <span class="type">TextFieldStyle</span> {
  <span class="keyword">@ViewBuilder
  func</span> _body(configuration: <span class="type">TextField</span>&lt;<span class="type">_Label</span>&gt;) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">let</span> mirror = <span class="type">Mirror</span>(reflecting: configuration)
    <span class="keyword">let</span> onCommit: () -&gt; <span class="type">Void</span> = mirror.<span class="call">descendant</span>(<span class="string">"onCommit"</span>) <span class="keyword">as</span>! () -&gt; <span class="type">Void

    VStack</span> {
      configuration
      <span class="type">Button</span>(<span class="string">"Trigger onCommit event"</span>, action: onCommit)
    }
  }
}
</code></pre><p>A limitation of this approach is that we can't subscribe our style to the <code>onEditingChanged</code> or <code>onCommit</code> events. This will probably be possible once the official APIs are public, maybe with a new <code>TextField</code> initializer accepting a <code>TextFieldConfiguration</code> along with two optional <code>onEditingChanged</code> or <code>onCommit</code> blocks.</p><h2>A preview</h2><p>So far we've played with our knowledge from <code>InspectStyle</code> and reached for each property by using Swift's <code>Mirror</code>: this works great.<br>Still, it's cumbersome to do so for every style we might define, instead, let's recreate a new <code>TextFieldStyle</code> that makes it easy to access all these properties.</p><p>First let's define our own configuration (we use <code>P</code> as suffix for "preview"):</p><pre><code><span class="keyword">struct</span> TextFieldStyleConfigurationP&lt;Label: <span class="type">View</span>&gt; {
  <span class="comment">/// The text to display and edit.</span>
  <span class="keyword">@Binding var</span> text: <span class="type">String</span>

  <span class="comment">/// Whether the text should be private (visible) or not.</span>
  <span class="keyword">let</span> isSecure: <span class="type">Bool</span>

  <span class="comment">/// A type-erased TextField.</span>
  <span class="keyword">let</span> label: <span class="type">Label</span>

  <span class="comment">/// The placeholder view.</span>
  <span class="keyword">let</span> placeholder: <span class="type">_TextFieldStyleLabel</span>

  <span class="comment">///  The action to perform when the user begins editing 
  ///  `text` and after the user finishes editing `text`.</span>
  <span class="keyword">let</span> onEditingChanged: (<span class="type">Bool</span>) -&gt; <span class="type">Void</span>

  <span class="comment">/// The action to perform when the user hit the return key.</span>
  <span class="keyword">let</span> onCommit: () -&gt; <span class="type">Void</span>

  <span class="comment">/// (???)</span>
  <span class="keyword">let</span> updatesContinuously: <span class="type">Bool</span>

  <span class="comment">/// (???)</span>
  <span class="keyword">@State var</span> uncommittedText: <span class="type">String</span>?
}
</code></pre><p>Then our style:</p><pre><code><span class="keyword">protocol</span> TextFieldStyleP {
  <span class="keyword">associatedtype</span> Body: <span class="type">View</span>
  <span class="keyword">typealias</span> _Label = <span class="type">TextField</span>&lt;<span class="type">_TextFieldStyleLabel</span>&gt;
  <span class="keyword">@ViewBuilder func</span> makeBody(configuration: <span class="type">TextFieldStyleConfigurationP</span>&lt;<span class="type">_Label</span>&gt;) -&gt; <span class="type">Self</span>.<span class="type">Body</span>
}
</code></pre><p>At this point we need to bridge SwiftUI's text styles with our new one. Instead of reinventing the wheel, we can piggyback on SwiftUI's <code>TextFieldStyle</code> with the following <code>PreviewBridgeStyle</code>:</p><pre><code><span class="keyword">struct</span> PreviewBridgeStyle&lt;Style: <span class="type">TextFieldStyleP</span>&gt;: <span class="type">TextFieldStyle</span> {
  <span class="keyword">let</span> style: <span class="type">Style</span>

  <span class="keyword">@ViewBuilder
  func</span> _body(configuration: <span class="type">TextField</span>&lt;<span class="type">_Label</span>&gt;) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">let</span> mirror = <span class="type">Mirror</span>(reflecting: configuration)
    <span class="keyword">let</span> text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt; = mirror.<span class="call">descendant</span>(<span class="string">"_text"</span>) <span class="keyword">as</span>! <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;
    <span class="keyword">let</span> isSecure: <span class="type">Bool</span> = mirror.<span class="call">descendant</span>(<span class="string">"isSecure"</span>) <span class="keyword">as</span>! <span class="type">Bool</span>
    <span class="keyword">let</span> label: <span class="type">_TextFieldStyleLabel</span> = mirror.<span class="call">descendant</span>(<span class="string">"label"</span>) <span class="keyword">as</span>! <span class="type">_TextFieldStyleLabel</span>
    <span class="keyword">let</span> onEditingChanged: (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = mirror.<span class="call">descendant</span>(<span class="string">"onEditingChanged"</span>) <span class="keyword">as</span>! (<span class="type">Bool</span>) -&gt; <span class="type">Void</span>
    <span class="keyword">let</span> onCommit: () -&gt; <span class="type">Void</span> = mirror.<span class="call">descendant</span>(<span class="string">"onCommit"</span>) <span class="keyword">as</span>! () -&gt; <span class="type">Void</span>
    <span class="keyword">let</span> updatesContinuously: <span class="type">Bool</span> = mirror.<span class="call">descendant</span>(<span class="string">"updatesContinuously"</span>) <span class="keyword">as</span>! <span class="type">Bool</span>
    <span class="keyword">let</span> uncommittedText: <span class="type">State</span>&lt;<span class="type">String</span>?&gt; = mirror.<span class="call">descendant</span>(<span class="string">"_uncommittedText"</span>) <span class="keyword">as</span>! <span class="type">State</span>&lt;<span class="type">String</span>?&gt;

    <span class="keyword">let</span> textStyleConfiguration = <span class="type">TextFieldStyleConfigurationP</span>(
      text: text,
      isSecure: isSecure,
      label: configuration,
      placeholder: label,
      onEditingChanged: onEditingChanged,
      onCommit: onCommit,
      updatesContinuously: updatesContinuously,
      uncommittedText: uncommittedText.<span class="property">wrappedValue</span>
    )

    style.<span class="call">makeBody</span>(configuration: textStyleConfiguration)
  }
}
</code></pre><p><code>PreviewBridgeStyle</code> is a <code>TextFieldStyle</code> that extracts our <code>TextFieldStyleConfigurationP</code> and passes it to our <code>TextFieldStyleP</code>.</p><p>Lastly we define the following <code>View</code> extension:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">func</span> textFieldStyleP&lt;S: <span class="type">TextFieldStyleP</span>&gt;(<span class="keyword">_</span> style: <span class="type">S</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">textFieldStyle</span>(<span class="type">PreviewBridgeStyle</span>(style: style))
  }
}
</code></pre><p>Which will do the transformation for us. From now on, we can define our styles with all the data immediately available.</p><p>Here's the <code>ClearStyle</code> again with this new approach:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> text = <span class="string">"FIVE STARS"</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
      <span class="type">TextField</span>(
        <span class="string">"type something..."</span>,
        text: $text
      )
      .<span class="call">textFieldStyleP</span>(<span class="type">ClearStyleP</span>())
      <span class="comment">// note the P suffixes</span>
  }
}

<span class="keyword">struct</span> ClearStyleP: <span class="type">TextFieldStyleP</span> {
  <span class="keyword">func</span> makeBody(configuration: <span class="type">TextFieldStyleConfigurationP</span>&lt;<span class="type">_Label</span>&gt;) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    configuration
      .<span class="dotAccess">label</span>
      .<span class="call">overlay</span>(
        <span class="type">Button</span> { configuration.<span class="property">text</span> = <span class="string">""</span> } label: { <span class="type">Image</span>(systemName: <span class="string">"clear"</span>) }
          .<span class="call">padding</span>(),
        alignment: .<span class="dotAccess">trailing</span>
      )
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/textfieldstyle/clear.gif"/><p>And here's the <code>RequirementStyle</code>:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> text = <span class="string">"FIVE STARS"</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TextField</span>(
      <span class="string">"type something..."</span>,
      text: $text
    )
    .<span class="call">textFieldStyleP</span>(<span class="type">RequirementStyleP</span>())
  }
}

<span class="keyword">struct</span> RequirementStyleP: <span class="type">TextFieldStyleP</span> {
  <span class="keyword">func</span> makeBody(configuration: <span class="type">TextFieldStyleConfigurationP</span>&lt;<span class="type">_Label</span>&gt;) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    configuration
      .<span class="dotAccess">label</span>
      .<span class="call">padding</span>()
      .<span class="call">background</span>(
        <span class="type">RoundedRectangle</span>(cornerRadius: <span class="number">16</span>)
          .<span class="call">strokeBorder</span>(configuration.<span class="property">text</span>.<span class="property">count</span> &gt; <span class="number">3</span> ? <span class="type">Color</span>.<span class="property">green</span> : <span class="type">Color</span>.<span class="property">red</span>)
      )
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/textfieldstyle/req.gif"/><p>In about one month (WWDC21!), we might get a similar official API:<br>it might be possible to use the approach above for retro compatibility (hopefully with just minor changes).</p><h2>Conclusions</h2><p>In this article we've explored what the future of <code>TextField</code> (and <code>SecureField</code>) might look like:<br>there are big expectations around this component for this year's WWDC, especially on aspects such as first responder control (FB9081556), June can't come soon enough!</p><p>What else are you looking forward to at WWDC21? Please let me know via <a href="https://twitter.com/zntfdr">twitter</a> or <a href="mailto:hello@fivestars.blog">email</a>.</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/spm-5-4</guid><title>What's new in Swift Package Manager in Swift 5.4</title><description>A look into all main SPM changes for Xcode 12.5</description><link>https://www.fivestars.blog/articles/spm-5-4</link><pubDate>Tue, 27 Apr 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Along with the brand new Xcode 12.5, Swift 5.4 and its associated Swift Package Manager (SPM from now on) are now officially released. While 5.4 might look like a small release for SPM, there are actually a lot of changes, in this article let's have a look at the main ones.</p><h2>The biggest release so far (?)</h2><p>Even before talking about the actual changes, this release is one of the biggest (if not <em>the</em> biggest) for SPM to date, this is also justified by the recent expansion of the dedicated team at Apple and the new support for Windows.</p><p>Here are all the stats of the most recent releases:</p><ul><li><a href="https://github.com/apple/swift-package-manager/compare/release/5.3...release/5.4">Swift 5.4 vs 5.3</a>: <strong>527</strong> changed files with <strong>34,434</strong> additions and <strong>36,806</strong> deletions.</li><li><a href="https://github.com/apple/swift-package-manager/compare/swift-5.2-branch...release/5.3">Swift 5.3 vs 5.2</a>: <strong>411</strong> changed files with <strong>19,920</strong> additions and <strong>8,186</strong> deletions.</li><li><a href="https://github.com/apple/swift-package-manager/compare/swift-5.1-branch...swift-5.2-branch ">Swift 5.2 vs 5.1</a>: <strong>441</strong> changed files with <strong>21,714</strong> additions and <strong>11,442</strong> deletions.</li><li><a href="https://github.com/apple/swift-package-manager/compare/swift-5.0-branch...swift-5.1-branch">Swift 5.1 vs 5.0</a>: <strong>169</strong> changed files with <strong>8,560</strong> additions and <strong>4,276</strong> deletions.</li><li><a href="https://github.com/apple/swift-package-manager/compare/swift-4.2-branch...swift-5.0-branch">Swift 5.0 vs 4.2</a>: <strong>420</strong> changed files with <strong>23,256</strong> additions and <strong>11,635</strong> deletions.</li><li><a href="https://github.com/apple/swift-package-manager/compare/swift-4.1-branch...swift-4.2-branch">Swift 4.2 vs 4.1</a>: <strong>232</strong> changed files with <strong>6,831</strong> additions and <strong>2,117</strong> deletions.</li><li><a href="https://github.com/apple/swift-package-manager/compare/swift-4.0-branch...swift-4.1-branch">Swift 4.1 vs 4.0</a>: <strong>326</strong> changed files with <strong>7,286</strong> additions and <strong>4,344</strong> deletions.</li><li><a href="https://github.com/apple/swift-package-manager/compare/swift-3.1-branch...swift-4.0-branch">Swift 4.0 vs 3.1</a>: <strong>319</strong> changed files with <strong>25,852</strong> additions and <strong>11,848</strong> deletions.</li><li><a href="https://github.com/apple/swift-package-manager/compare/swift-3.0-branch...swift-3.1-branch">Swift 3.1 vs 3.0</a>: <strong>331</strong> changed files with <strong>28,945</strong> additions and <strong>5,210</strong> deletions.</li></ul><p>Since we're at it, here's a sneak peek at SPM 5.5, which is still a few months away:</p><ul><li><a href="https://github.com/apple/swift-package-manager/compare/release/5.4...release/5.5">Swift 5.5 vs 5.4</a>: <strong>374</strong> changed files with <strong>28,185</strong> additions and <strong>6,539</strong> deletions (at the time of writing).</li></ul><p>While these stats alone don't mean much, it's clear that a lot is happening on this project, let's have a look at the actual changes next.</p><h2>External changes</h2><h3>Targets declaration</h3><p>Up to Swift 5.3 <a href="https://developer.apple.com/documentation/swift_packages/target">targets declarations</a> were separated in:</p><ul><li><code>.target(...)</code> for regular and executable targets</li><li><code>.binaryTarget(...)</code> for binary targets that reference an artifact on disk</li><li><code>.testTarget(...)</code> for testing targets</li><li><code>.systemLibrary(...)</code> for system library targets</li></ul><p>Where the difference between a regular target and an executable one was the presence (or not) of a <code>main.swift</code> file, which was then used as the entry point for an executable target.</p><p>In Swift 5.3 we've seen the introduction of the <code>@main</code> attribute, explicitly defining a new application entry point, regardless of the where its declaration is located.</p><p>In Swift 5.4 SPM gains a new <code>.executableTarget(...)</code> target, exclusively dedicated to executables, where SPM will support either having a <code>main.swift</code> entry point or a <code>@main</code> declaration (at any location).</p><p>From now on <code>.target(...)</code> will be used only for regular targets and nothing else, improving the package manifest readability.</p><h3>(Linux) Test discovery</h3><p>Until Swift 5.3 every package supporting Linux needed to have a <code>LinuxMain.swift</code> file in the <code>Tests</code> folder root: this file was needed to explicitly declare/list all the tests to be executed. From SPM 5.4 this <code>LinuxMain.swift</code> is no longer necessary.</p><p>This same file was previously not needed/used in macOS platforms, as macOS relies on the Objective-C runtime for such discovery, like it does for <code>.xcodeproj</code> test suites.</p><p>The way it now works is by building an <a href="https://github.com/apple/swift-tools-support-core/blob/435a2708a6e486d69ea7d7aaa3f4ad243bc3b408/Sources/TSCUtility/IndexStore.swift#L14"><code>IndexStore</code></a> of the tests and run the outcome through a <a href="https://github.com/apple/swift-package-manager/blob/72de2f50d45c360df8dac801a672bd0dab3ddb5f/Sources/Build/BuildOperationBuildSystemDelegateHandler.swift#L48"><code>TestDiscoveryCommand</code></a>, which will then generate the <code>LinuxMain.swift</code> file at build time.</p><blockquote><p>This functionality was previously available via a <code>--enable-test-discovery</code> flag to be passed on a <code>$ swift test</code> command, it's now enabled by default.</p></blockquote><h2>Package dependency cache</h2><p>Prior to Swift 5.4, if we had two completely separate packages with the same dependency, that dependency would have been downloaded twice and put in the <code>.build</code> folder of each of our packages.</p><p>From Swift 5.4 SPM keeps a per-user cache in a new <code>~/.swiftpm/cache</code> folder (or equivalent in other platforms), which will then be queried before fetching anything from the Internet: this will be a huge time/data saver, especially when using the same dependency in multiple, independent packages.</p><h2>Internal changes</h2><h3>Argument parser</h3><p>Apple's <a href="https://github.com/apple/swift-argument-parser/"><code>ArgumentParser</code></a> has been released <a href="https://www.fivestars.blog/articles/a-look-into-argument-parser/">a little over an year ago</a>, during this time it became the de-facto standard for every swift CLI tool out there: it should come with no surprise that <a href="https://github.com/apple/swift-package-manager/pull/2653">SPM has now adopted it</a>.</p><p>Previously SPM used <a href="https://github.com/apple/swift-tools-support-core/blob/435a2708a6e486d69ea7d7aaa3f4ad243bc3b408/Sources/TSCUtility/ArgumentParser.swift"><code>TSCUtility</code>'s <code>ArgumentParser</code></a> which we <a href="https://www.fivestars.blog/articles/executables-argument-kind/">covered here</a>.</p><h3>Good bye swift-tools-support-core</h3><p><a href="https://github.com/apple/swift-tools-support-core"><code>swift-tools-support-core</code></a> is a collection of utilities used within SPM and <a href="https://github.com/apple/swift-llbuild"><code>llbuild</code></a>: SPM kept a vendored copy of this package in its own repository, for CI/testing purposes.</p><p>Keeping this copy meant that <a href="https://github.com/apple/swift-tools-support-core/pull/44">every</a> <a href="https://github.com/apple/swift-package-manager/pull/2605">change</a> had to be synced between the two repositories: fortunately <a href="https://github.com/apple/swift-package-manager/pull/2991">this is no longer the case</a>, as SPM no longer needs such vendored copy.</p><h3>New module: Basics</h3><p>Basics is a new module that contains SPM specific utilities that shouldn't be part of <code>swift-tools-support-core</code>. For example the per-user cache location can be found <a href="https://github.com/apple/swift-package-manager/blob/a5f7859ef5d9b1ff97788e8fa0a71db65e011ce4/Sources/Basics/FileSystem%2BExtensions.swift#L16">in this module</a>.</p><p>This module name is a blast from the past: <code>swift-tools-support-core</code>'s <code>TSCBasic</code> was originally called <a href="https://github.com/apple/swift-package-manager/pull/346"><code>Basic</code></a>.</p><h3>New module: PackageCollections</h3><p><a href="https://github.com/apple/swift-package-manager/blob/a5f7859ef5d9b1ff97788e8fa0a71db65e011ce4/Documentation/PackageCollections.md">Package Collections</a> is the first module out of three of a new package discovery functionality being added into SPM, the other two being Package Registry and Package Index.</p><p>Package Collections focuses on handpicked packages lists, and provides a CLI tool, <code>$ swift package-collection</code>, in order to read and explore such lists.</p><p>While this functionality is officially being added in Swift 5.5, the ground work is already part of SPM as of Swift 5.4 (note that <code>$ swift package-collection</code> is not exposed in 5.4, making it inaccessible)</p><p>For more details on <code>PackageCollections</code>, refer to its <a href="https://github.com/apple/swift-evolution/blob/4b687970130fa33ed28c5f2eb86081312386c26d/proposals/0291-package-collections.md">Swift Evolution proposal</a> and <a href="https://github.com/apple/swift-package-manager/blob/a5f7859ef5d9b1ff97788e8fa0a71db65e011ce4/Documentation/PackageCollections.md">documentation</a>.</p><h2>Conclusions</h2><p>SPM has gotten a lot of attention in the last couple of years, and its development pace seems to be only increasing!</p><p>In this article we took a sneak peek to its main changes of the upcoming release, however there are many other changes not highlighted here: <a href="https://github.com/apple/swift-package-manager/compare/release/5.3...release/5.4">feel free to explore them yourself</a>!</p><p>What are you most excited about SPM future? Let me know via <a href="mailto:hello@fivestars.blog">email</a> or <a href="https://twitter.com/zntfdr">twitter</a>!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/how-to-customize-textfields</guid><title>Four ways to customize TextFields</title><description>SwiftUI is a dream for prototyping and building views: in this article, let’s see how we can customize a TextField.</description><link>https://www.fivestars.blog/articles/how-to-customize-textfields</link><pubDate>Tue, 20 Apr 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>SwiftUI is a dream for prototyping and building views: in this article, let’s see how we can customize a <code>TextField</code>.</p><h2>TextFieldStyle</h2><p>Before thinking about customization, we should consider what SwiftUI offers. <code>TextField</code> comes with its own style which, despite not allowing creating our own (FB9078993), presents us some options:</p><ul><li><code>DefaultTextFieldStyle</code></li><li><code>PlainTextFieldStyle</code></li><li><code>RoundedBorderTextFieldStyle</code></li></ul><img src="https://www.fivestars.blog/assets/posts/how-to-customize-textfields/defaultstyles.png"/><pre><code><span class="type">VStack</span> {
  <span class="type">Section</span>(header: <span class="type">Text</span>(<span class="string">"DefaultTextFieldStyle"</span>).<span class="call">font</span>(.<span class="dotAccess">headline</span>)) {
    <span class="type">TextField</span>(<span class="string">"Placeholder"</span>, text: .<span class="call">constant</span>(<span class="string">""</span>))
    <span class="type">TextField</span>(<span class="string">"Placeholder"</span>, text: $text)
  }
  .<span class="call">textFieldStyle</span>(<span class="type">DefaultTextFieldStyle</span>())

  <span class="type">Section</span>(header: <span class="type">Text</span>(<span class="string">"PlainTextFieldStyle"</span>).<span class="call">font</span>(.<span class="dotAccess">headline</span>)) {
    <span class="type">TextField</span>(<span class="string">"Placeholder"</span>, text: .<span class="call">constant</span>(<span class="string">""</span>))
    <span class="type">TextField</span>(<span class="string">"Placeholder"</span>, text: $text)
  }
  .<span class="call">textFieldStyle</span>(<span class="type">PlainTextFieldStyle</span>())

  <span class="type">Section</span>(header: <span class="type">Text</span>(<span class="string">"RoundedBorderTextFieldStyle"</span>).<span class="call">font</span>(.<span class="dotAccess">headline</span>)) {
    <span class="type">TextField</span>(<span class="string">"Placeholder"</span>, text: .<span class="call">constant</span>(<span class="string">""</span>))
    <span class="type">TextField</span>(<span class="string">"Placeholder"</span>, text: $text)
  }
  .<span class="call">textFieldStyle</span>(<span class="type">RoundedBorderTextFieldStyle</span>())
}
</code></pre><p><code>DefaultTextFieldStyle</code> is applied to all <code>TextField</code>s where no different style has been set. In iOS, this matches applying the <code>PlainTextFieldStyle</code>.</p><p>The difference between <code>PlainTextFieldStyle</code> <code>RoundedBorderTextFieldStyle</code> seems to be just the presence of a rounded corner border, however a <code>TextField</code> with <code>RoundedBorderTextFieldStyle</code> also comes with a white/black background (depending on the environment appearance), while a <code>TextField</code> with <code>PlainTextFieldStyle</code> is transparent:</p><img src="https://www.fivestars.blog/assets/posts/how-to-customize-textfields/defaultstylesBackground.png"/><pre><code><span class="type">VStack</span> {
  <span class="type">Section</span>(header: <span class="type">Text</span>(<span class="string">"DefaultTextFieldStyle"</span>).<span class="call">font</span>(.<span class="dotAccess">headline</span>)) {
    <span class="type">TextField</span>(<span class="string">"Placeholder"</span>, text: .<span class="call">constant</span>(<span class="string">""</span>))
    <span class="type">TextField</span>(<span class="string">"Placeholder"</span>, text: $text)
  }
  .<span class="call">textFieldStyle</span>(<span class="type">DefaultTextFieldStyle</span>())

  <span class="type">Section</span>(header: <span class="type">Text</span>(<span class="string">"PlainTextFieldStyle"</span>).<span class="call">font</span>(.<span class="dotAccess">headline</span>)) {
    <span class="type">TextField</span>(<span class="string">"Placeholder"</span>, text: .<span class="call">constant</span>(<span class="string">""</span>))
    <span class="type">TextField</span>(<span class="string">"Placeholder"</span>, text: $text)
  }
  .<span class="call">textFieldStyle</span>(<span class="type">PlainTextFieldStyle</span>())

  <span class="type">Section</span>(header: <span class="type">Text</span>(<span class="string">"RoundedBorderTextFieldStyle"</span>).<span class="call">font</span>(.<span class="dotAccess">headline</span>)) {
    <span class="type">TextField</span>(<span class="string">"Placeholder"</span>, text: .<span class="call">constant</span>(<span class="string">""</span>))
    <span class="type">TextField</span>(<span class="string">"Placeholder"</span>, text: $text)
  }
  .<span class="call">textFieldStyle</span>(<span class="type">RoundedBorderTextFieldStyle</span>())
}
.<span class="call">background</span>(<span class="type">Color</span>.<span class="property">yellow</span>)
</code></pre><p>This is all we have. Let’s talk about customization next.</p><h2>The SwiftUI way (part 1)</h2><p>Since there’s no public API to create new <code>TextField</code> styles at this time, the recommended way to customize <code>TextField</code>s is to wrap <code>TextField</code> in our definitions, similarly to what we did in <a href="https://www.fivestars.blog/articles/design-system-composing-views/">Composing SwiftUI views</a>:</p><pre><code><span class="keyword">public struct</span> FSTextField: <span class="type">View</span> {
  <span class="keyword">var</span> titleKey: <span class="type">LocalizedStringKey</span>
  <span class="keyword">@Binding var</span> text: <span class="type">String</span>

  <span class="comment">/// Whether the user is focused on this `TextField`.</span>
  <span class="keyword">@State private var</span> isEditing: <span class="type">Bool</span> = <span class="keyword">false

  public init</span>(<span class="keyword">_</span> titleKey: <span class="type">LocalizedStringKey</span>, text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;) {
    <span class="keyword">self</span>.<span class="property">titleKey</span> = titleKey
    <span class="keyword">self</span>.<span class="property">_text</span> = text
  }

  <span class="keyword">public var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TextField</span>(titleKey, text: $text, onEditingChanged: { isEditing = $0 })
      <span class="comment">// Make sure no other style is mistakenly applied.</span>
      .<span class="call">textFieldStyle</span>(<span class="type">PlainTextFieldStyle</span>())
      <span class="comment">// Text alignment.</span>
      .<span class="call">multilineTextAlignment</span>(.<span class="dotAccess">leading</span>)
      <span class="comment">// Cursor color.</span>
      .<span class="call">accentColor</span>(.<span class="dotAccess">pink</span>)
      <span class="comment">// Text color.</span>
      .<span class="call">foregroundColor</span>(.<span class="dotAccess">blue</span>)
      <span class="comment">// Text/placeholder font.</span>
      .<span class="call">font</span>(.<span class="dotAccess">title</span>.<span class="call">weight</span>(.<span class="dotAccess">semibold</span>))
      <span class="comment">// TextField spacing.</span>
      .<span class="call">padding</span>(.<span class="dotAccess">vertical</span>, <span class="number">12</span>)
      .<span class="call">padding</span>(.<span class="dotAccess">horizontal</span>, <span class="number">16</span>)
      <span class="comment">// TextField border.</span>
      .<span class="call">background</span>(border)
  }

  <span class="keyword">var</span> border: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">RoundedRectangle</span>(cornerRadius: <span class="number">16</span>)
      .<span class="call">strokeBorder</span>(
        <span class="type">LinearGradient</span>(
          gradient: .<span class="keyword">init</span>(
            colors: [
              <span class="type">Color</span>(red: <span class="number">163</span> / <span class="number">255.0</span>, green: <span class="number">243</span> / <span class="number">255.0</span>, blue: <span class="number">7</span> / <span class="number">255.0</span>),
              <span class="type">Color</span>(red: <span class="number">226</span> / <span class="number">255.0</span>, green: <span class="number">247</span> / <span class="number">255.0</span>, blue: <span class="number">5</span> / <span class="number">255.0</span>)
            ]
          ),
          startPoint: .<span class="dotAccess">topLeading</span>,
          endPoint: .<span class="dotAccess">bottomTrailing</span>
        ),
        lineWidth: isEditing ? <span class="number">4</span> : <span class="number">2</span>
      )
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/how-to-customize-textfields/customSwiftUI.gif"/><p>This is all we can officially customize in a <code>TextField</code> for now. There’s no way to change the placeholder text color, or have different fonts for the text and its placeholder (FB9079255):<br>we can work around some limitations by using external <code>Text</code>s or even applying masks to <code>TextField</code> while tracking its states, however we would enter in the “hack” territory pretty fast.</p><blockquote><p>Other <code>TextField</code>s aspects that we can customize that are not strictly UI-related: the <code>TextField</code> associated <a href="https://developer.apple.com/documentation/swiftui/view/keyboardtype(_:)">keyboard type</a>, <a href="https://developer.apple.com/documentation/swiftui/view/textcontenttype(_:)-ufdv">content type</a>, <a href="https://developer.apple.com/documentation/swiftui/view/autocapitalization(_:)">auto capitalization</a>, and <a href="https://developer.apple.com/documentation/swiftui/view/disableautocorrection(_:)">auto correction</a>.</p></blockquote><h2>The SwiftUI way (part 2)</h2><p>When <code>TextField</code> doesn’t meet our needs, we can always fall back to UIKit’s <code>UITextField</code>. While it might not always be ideal, it doesn’t take too much to create a dedicated <code>UIViewRepresentable</code>:</p><pre><code><span class="keyword">struct</span> UIKitTextField: <span class="type">UIViewRepresentable</span> {
  <span class="keyword">var</span> titleKey: <span class="type">String</span>
  <span class="keyword">@Binding var</span> text: <span class="type">String</span>

  <span class="keyword">public init</span>(<span class="keyword">_</span> titleKey: <span class="type">String</span>, text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;) {
    <span class="keyword">self</span>.<span class="property">titleKey</span> = titleKey
    <span class="keyword">self</span>.<span class="property">_text</span> = text
  }

  <span class="keyword">func</span> makeUIView(context: <span class="type">Context</span>) -&gt; <span class="type">UITextField</span> {
    <span class="keyword">let</span> textField = <span class="type">UITextField</span>(frame: .<span class="dotAccess">zero</span>)
    textField.<span class="property">delegate</span> = context.<span class="property">coordinator</span>
    textField.<span class="call">setContentHuggingPriority</span>(.<span class="dotAccess">defaultHigh</span>, for: .<span class="dotAccess">vertical</span>)
    textField.<span class="call">setContentCompressionResistancePriority</span>(.<span class="dotAccess">defaultLow</span>, for: .<span class="dotAccess">horizontal</span>)
    textField.<span class="property">placeholder</span> = <span class="type">NSLocalizedString</span>(titleKey, comment: <span class="string">""</span>)

    <span class="keyword">return</span> textField
  }

  <span class="keyword">func</span> updateUIView(<span class="keyword">_</span> uiView: <span class="type">UITextField</span>, context: <span class="type">Context</span>) {
    <span class="keyword">if</span> text != uiView.<span class="property">text</span> {
        uiView.<span class="property">text</span> = text
    }
  }

  <span class="keyword">func</span> makeCoordinator() -&gt; <span class="type">Coordinator</span> {
    <span class="type">Coordinator</span>(<span class="keyword">self</span>)
  }

  <span class="keyword">final class</span> Coordinator: <span class="type">NSObject</span>, <span class="type">UITextFieldDelegate</span> {
    <span class="keyword">var</span> parent: <span class="type">UIKitTextField</span>

    <span class="keyword">init</span>(<span class="keyword">_</span> textField: <span class="type">UIKitTextField</span>) {
      <span class="keyword">self</span>.<span class="property">parent</span> = textField
    }

    <span class="keyword">func</span> textFieldDidChangeSelection(<span class="keyword">_</span> textField: <span class="type">UITextField</span>) {
      <span class="keyword">guard</span> textField.<span class="property">markedTextRange</span> == <span class="keyword">nil</span>, parent.<span class="property">text</span> != textField.<span class="property">text</span> <span class="keyword">else</span> {
        <span class="keyword">return</span>
      }
      parent.<span class="property">text</span> = textField.<span class="property">text</span> ?? <span class="string">""</span>
    }

    <span class="keyword">func</span> textFieldShouldReturn(<span class="keyword">_</span> textField: <span class="type">UITextField</span>) -&gt; <span class="type">Bool</span> {
      textField.<span class="call">resignFirstResponder</span>()
      <span class="keyword">return true</span>
    }
  }
}
</code></pre><p>And here it is compared to SwiftUI's <code>TextField</code>:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> text = <span class="string">""</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">TextField</span>(<span class="string">"Type something... (SwiftUI)"</span>, text: $text)
      <span class="type">UIKitTextField</span>(<span class="string">"Type something... (UIKit)"</span>, text: $text)
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/how-to-customize-textfields/uikitswiftui.gif"/><p>Once we have this base text field, we can go ahead and grab all the UIKit functionality that we need, for example changing the placeholder text color is now a matter of adding the following code in <code>UIKitTextField</code>'s <code>makeUIView(context:)</code> method:</p><pre><code>textField.<span class="property">attributedPlaceholder</span> = <span class="type">NSAttributedString</span>(
  string: <span class="type">NSLocalizedString</span>(titleKey, comment: <span class="string">""</span>),
  attributes: [.<span class="dotAccess">foregroundColor</span>: <span class="type">UIColor</span>.<span class="property">red</span>]
)
</code></pre><img src="https://www.fivestars.blog/assets/posts/how-to-customize-textfields/red.png"/><p>With UIKit at our disposal, we can do much more than just simple customizations. For example, we can associate date/picker keyboard types to our text fields, both of which are not supported in SwiftUI (FB9079186 and FB9079187). More importantly, we can make any text field become/resign first responder (FB9081556).</p><blockquote><p>For an advanced <code>TextField</code> <code>UIViewRepresentable</code> example, I recommend to check out <a href="https://github.com/SwiftUIX/SwiftUIX/blob/b9d6c85b9379dc0f8887da80c3c8ab31b59845c3/Sources/Intramodular/Bridged/CocoaTextField.swift">SwiftUIX's <code>CocoaTextField</code></a>.</p></blockquote><h2>The introspect way</h2><p>Despite SwiftUI APIs being very different from their UIKit counterparts, oftentimes UIKit is still used behind the scenes.</p><p>As of iOS 14 this is still true for <code>TextField</code>, which uses <code>UITextField</code> under the hood:<br>keeping this in mind, we could traverse <code>TextField</code>’s UIKit hierarchy and look for the associated <code>UITextField</code>.</p><p>This is precisely what the <a href="https://github.com/siteline/SwiftUI-Introspect"><code>Introspect for SwiftUI</code> library</a> does, allowing us to reach for the UIKit counterparts of our SwiftUI views, thus letting us unlock UIKit’s power/control without having to create our own <code>UIViewRepresentable</code>:</p><pre><code><span class="keyword">import</span> Introspect

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> text = <span class="string">""</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TextField</span>(<span class="string">"Type something..."</span>, text: $text)
      .<span class="call">introspectTextField</span> { textField <span class="keyword">in</span>
        <span class="comment">// this method will be called with our view's UITextField (if found)</span>
        ...
      }
  }
}
</code></pre><p>As an example, SwiftUI doesn’t have a way to associate a tool bar to a given text field (FB9081641), we can use <code>Introspect</code> to patch that:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> text = <span class="string">""</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TextField</span>(<span class="string">"Type something..."</span>, text: $text)
      .<span class="call">introspectTextField</span>(customize: addToolbar)
  }

  <span class="keyword">func</span> addToolbar(to textField: <span class="type">UITextField</span>) {
    <span class="keyword">let</span> toolBar = <span class="type">UIToolbar</span>(
      frame: <span class="type">CGRect</span>(
        origin: .<span class="dotAccess">zero</span>,
        size: <span class="type">CGSize</span>(width: textField.<span class="property">frame</span>.<span class="property">size</span>.<span class="property">width</span>, height: <span class="number">44</span>)
      )
    )
    <span class="keyword">let</span> flexButton = <span class="type">UIBarButtonItem</span>(
      barButtonSystemItem: <span class="type">UIBarButtonItem</span>.<span class="type">SystemItem</span>.<span class="property">flexibleSpace</span>,
      target: <span class="keyword">nil</span>,
      action: <span class="keyword">nil</span>
    )
    <span class="keyword">let</span> doneButton = <span class="type">UIBarButtonItem</span>(
      title: <span class="string">"Done"</span>,
      style: .<span class="dotAccess">done</span>,
      target: <span class="keyword">self</span>,
      action: <span class="keyword">#selector</span>(textField.<span class="call">didTapDoneButton</span>(<span class="keyword">_</span>:))
    )
    toolBar.<span class="call">setItems</span>([flexButton, doneButton], animated: <span class="keyword">true</span>)
    textField.<span class="property">inputAccessoryView</span> = toolBar
  }
}

<span class="keyword">extension</span>  <span class="type">UITextField</span> {
  <span class="keyword">@objc func</span> didTapDoneButton(<span class="keyword">_</span> button: <span class="type">UIBarButtonItem</span>) -&gt; <span class="type">Void</span> {
    <span class="call">resignFirstResponder</span>()
  }
}
</code></pre><blockquote><p>Over 20 lines for a <code>Done</code> button!</p></blockquote><img src="https://www.fivestars.blog/assets/posts/how-to-customize-textfields/toolbar.gif"/><p>While this approach works today, it’s not guaranteed to work in future iOS releases, as we’re relying on SwiftUI’s private implementation details.</p><p>With that being said, using <code>Introspect</code> is safe: when SwiftUI’s <code>TextField</code> will no longer use <code>UITextField</code>, our customization method (<code>addToolbar(to:)</code> in the example above) will <em>not</em> be called.</p><h2>The TextFieldStyle way</h2><p>At the beginning of the article it’s mentioned how SwiftUI doesn’t allow us to create our own `TextFieldStyle’s yet.</p><p>As of Xcode 12.5, this is the complete <code>TextFieldStyle</code> declaration:</p><pre><code><span class="comment">/// A specification for the appearance and interaction of a text field.</span>
<span class="keyword">@available</span>(iOS <span class="number">13.0</span>, macOS <span class="number">10.15</span>, tvOS <span class="number">13.0</span>, watchOS <span class="number">6.0</span>, *)
<span class="keyword">public protocol</span> TextFieldStyle {

}
</code></pre><p>However it's actually possible to create our own style via a "hidden" <code>_body</code> method, therefore we could think about the actual <code>TextFieldStyle</code> declaration as following:</p><pre><code><span class="keyword">public protocol</span> TextFieldStyle {
  <span class="keyword">associatedtype</span> _Body: <span class="type">View</span>
  <span class="keyword">@ViewBuilder func</span> _body(configuration: <span class="type">TextField</span>&lt;<span class="type">Self</span>.<span class="type">_Label</span>&gt;) -&gt; <span class="type">Self</span>.<span class="type">_Body</span>
  <span class="keyword">typealias</span> _Label = <span class="type">_TextFieldStyleLabel</span>
}
</code></pre><p>...making it possible to create our own styles:</p><pre><code><span class="keyword">struct</span> FSTextFieldStyle: <span class="type">TextFieldStyle</span> {
  <span class="keyword">func</span> _body(configuration: <span class="type">TextField</span>&lt;<span class="type">_Label</span>&gt;) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
     <span class="comment">//</span>
  }
}
</code></pre><p>Here's how we could replace our previous <code>FSTextField</code> declaration with a new <code>FSTextFieldStyle</code>:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> text = <span class="string">""</span>

  <span class="comment">/// Whether the user is focused on this `TextField`.</span>
  <span class="keyword">@State private var</span> isEditing: <span class="type">Bool</span> = <span class="keyword">false

  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TextField</span>(<span class="string">"Type something..."</span>, text: $text, onEditingChanged: { isEditing = $0 })
      .<span class="call">textFieldStyle</span>(<span class="type">FSTextFieldStyle</span>(isEditing: isEditing))
  }
}

<span class="keyword">struct</span> FSTextFieldStyle: <span class="type">TextFieldStyle</span> {
  <span class="comment">/// Whether the user is focused on this `TextField`.</span>
  <span class="keyword">var</span> isEditing: <span class="type">Bool</span>

  <span class="keyword">func</span> _body(configuration: <span class="type">TextField</span>&lt;<span class="type">_Label</span>&gt;) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    configuration
      .<span class="call">textFieldStyle</span>(<span class="type">PlainTextFieldStyle</span>())
      .<span class="call">multilineTextAlignment</span>(.<span class="dotAccess">leading</span>)
      .<span class="call">accentColor</span>(.<span class="dotAccess">pink</span>)
      .<span class="call">foregroundColor</span>(.<span class="dotAccess">blue</span>)
      .<span class="call">font</span>(.<span class="dotAccess">title</span>.<span class="call">weight</span>(.<span class="dotAccess">semibold</span>))
      .<span class="call">padding</span>(.<span class="dotAccess">vertical</span>, <span class="number">12</span>)
      .<span class="call">padding</span>(.<span class="dotAccess">horizontal</span>, <span class="number">16</span>)
      .<span class="call">background</span>(border)
  }

  <span class="keyword">var</span> border: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">RoundedRectangle</span>(cornerRadius: <span class="number">16</span>)
      .<span class="call">strokeBorder</span>(
        <span class="type">LinearGradient</span>(
          gradient: .<span class="keyword">init</span>(
            colors: [
              <span class="type">Color</span>(red: <span class="number">163</span> / <span class="number">255.0</span>, green: <span class="number">243</span> / <span class="number">255.0</span>, blue: <span class="number">7</span> / <span class="number">255.0</span>),
              <span class="type">Color</span>(red: <span class="number">226</span> / <span class="number">255.0</span>, green: <span class="number">247</span> / <span class="number">255.0</span>, blue: <span class="number">5</span> / <span class="number">255.0</span>)
            ]
          ),
          startPoint: .<span class="dotAccess">topLeading</span>,
          endPoint: .<span class="dotAccess">bottomTrailing</span>
        ),
        lineWidth: isEditing ? <span class="number">4</span> : <span class="number">2</span>
      )
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/how-to-customize-textfields/customSwiftUI.gif"/><p>Unfortunately, this approach uses private API, making it unsafe to use: hopefully, we will get an official API soon (FB9078993).</p><h2>Conclusions</h2><p>SwiftUI lets us design views at a breakneck speed: when SwiftUI alone doesn’t meet all our needs, there are multiple alternatives out there that we can reach for.</p><p>In this article we’ve covered four different ways to customize a <code>TextField</code>, but the same can be applied to many other SwiftUI components. Do you use any of these methods? Do you use something else altogether? Feel free to reach out and let me know via <a href="mailto:hello@fivestars.blog">email</a> or <a href="https://twitter.com/zntfdr">twitter</a>!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-protocols</guid><title>Every SwiftUI protocol explained</title><description>How many protocols does SwiftUI define? How many do we know about and actually use? Let's answer these questions, and more!</description><link>https://www.fivestars.blog/articles/swiftui-protocols</link><pubDate>Tue, 13 Apr 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Did you know that SwiftUI defines <strong>54</strong> protocols? Let's have a look at all of them!</p><h2>Core protocols</h2><ul><li><code>View</code></li><li><code>ViewModifier</code></li><li><code>App</code></li><li><code>Scene</code></li></ul><p>SwiftUI is all about declaring and modifying views: it should come with no surprise that two of the most important protocols are <code>View</code> and <code>ViewModifier</code>, which let us define our own views and our own view modifiers.</p><p>Furthermore, from Xcode 12 SwiftUI has gained the complete app life cycle thanks to <code>App</code> and <code>Scene</code>, allowing us to build SwiftUI apps from the start.</p><pre><code><span class="keyword">@main
struct</span> FiveStarsApp: <span class="type">App</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">Scene</span> {
    <span class="type">WindowGroup</span> {
      <span class="type">ContentView</span>()
    }
  }
}
</code></pre><h2>Styles</h2><ul><li><code>ButtonStyle</code></li><li><code>DatePickerStyle</code> (iOS, macOS)</li><li><code>GaugeStyle</code> (watchOS)</li><li><code>GroupBoxStyle</code> (iOS, macOS)</li><li><code>IndexViewStyle</code> (iOS, tvOS)</li><li><code>LabelStyle</code></li><li><code>ListStyle</code></li><li><code>MenuButtonStyle</code> (macOS)</li><li><code>MenuStyle</code> (iOS, macOS)</li><li><code>NavigationViewStyle</code></li><li><code>PickerStyle</code></li><li><code>PrimitiveButtonStyle</code></li><li><code>ProgressViewStyle</code></li><li><code>ShapeStyle</code></li><li><code>TabViewStyle</code></li><li><code>TextFieldStyle</code></li><li><code>ToggleStyle</code></li><li><code>WindowStyle</code> (macOS)</li><li><code>WindowToolbarStyle</code> (macOS)</li></ul><p>With 19 definitions, this is the biggest category of SwiftUI protocols:<br>styles are similar to view modifiers, but instead of being applied to all views, they're applied only to specific views (e.g. <a href="https://www.fivestars.blog/articles/label/"><code>Label</code></a>s, <a href="https://www.fivestars.blog/articles/button-styles/"><code>Button</code></a>s, ...).</p><p>To apply a style we use the associated view modifier <code>xxStyle(_:)</code> (e.g. <a href="https://www.fivestars.blog/articles/button-styles/"><code>buttonStyle(_:)</code></a>), where we can pass any of the predefined SwiftUI styles, or even our own.</p><pre><code><span class="type">Button</span>(<span class="string">"Rounded rectangle + shadow button style"</span>) {
  <span class="comment">// button tapped</span>
}
.<span class="call">buttonStyle</span>(<span class="type">RoundedRectangleWithShadowedLabelButtonStyle</span>())
</code></pre><img src="https://www.fivestars.blog/assets/posts/swiftui-protocols/roundedRectangle.gif"/><blockquote><p>Example from <a href="https://www.fivestars.blog/articles/button-styles/">Exploring SwiftUI's <code>Button</code> styles</a>.</p></blockquote><p>An important feature of styles is that their modifier can be applied to <em>any</em> view, and all matching subviews will automatically inherit said style:</p><pre><code><span class="type">VStack</span> {
  <span class="type">Label</span>(<span class="string">"One"</span>, systemImage: <span class="string">"1.circle.fill"</span>)
  <span class="type">Label</span>(<span class="string">"Two"</span>, systemImage: <span class="string">"2.circle.fill"</span>)
  <span class="type">Label</span>(<span class="string">"Three"</span>, systemImage: <span class="string">"3.circle.fill"</span>)
}
.<span class="call">labelStyle</span>(<span class="type">ShadowLabelStyle</span>())
</code></pre><img src="https://www.fivestars.blog/assets/posts/swiftui-protocols/labels.png"/><blockquote><p>Example from <a href="https://www.fivestars.blog/articles/label/">our <code>Label</code> deep dive</a>.</p></blockquote><h2>Widgets (iOS, macOS)</h2><ul><li><code>Widget</code></li><li><code>WidgetBundle</code></li><li><code>WidgetConfiguration</code></li></ul><blockquote><p>These are part of the <a href="https://www.fivestars.blog/articles/swiftui-widgetkit/">new SwiftUI features that we gained thanks to WidgetKit</a>.</p></blockquote><p>Widgets not only are one of the main highlights of iOS 14/macOS 11, but also come with their own set of protocols and definitions.</p><p>More specifically these protocols lets us declare and configure all our apps widgets via code:</p><pre><code><span class="keyword">@main
struct</span> FSWidgets: <span class="type">WidgetBundle</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">Widget</span> {
    <span class="type">LatestArticleWidget</span>()
    <span class="type">NewArticlesListWidget</span>()
  }
}
</code></pre><p>This approach is a clear departure from previous declarations such as SiriKit intents (done via <code>.intentdefinition</code> files) and Core Data models (declared via a <code>.xcdatamodeld</code> file).</p><p>Having such declarations in code makes things more straightforward, and give developers easy and direct access to them, avoiding the need for Xcode to extract and generate them at build time, like it has to do for SiriKit intents and Core Data models. Hopefully we will see this trend continue at this year WWDC.</p><h2>Shapes</h2><ul><li><code>Shape</code></li><li><code>InsettableShape</code></li></ul><p>SwiftUI comes with many shapes such as <code>Rectangle</code> and <code>Circle</code>: instead of limiting our choices to the built-in offering, we can expand SwiftUI's catalog by defining our own shapes conforming to these shape protocols.</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ZStack</span> {
      <span class="type">Circle</span>()
        .<span class="call">stroke</span>(<span class="type">Color</span>.<span class="property">black</span>, lineWidth: <span class="number">10</span>)

      <span class="type">ProgressCircle</span>(progress: <span class="number">0.4</span>)
        .<span class="call">stroke</span>(<span class="type">Color</span>.<span class="property">yellow</span>, lineWidth: <span class="number">10</span>)
    }
  }
}

<span class="keyword">struct</span> ProgressCircle: <span class="type">Shape</span> {
  <span class="keyword">var</span> progress: <span class="type">CGFloat</span>

  <span class="keyword">func</span> path(in rect: <span class="type">CGRect</span>) -&gt; <span class="type">Path</span> {
    <span class="keyword">let</span> centerX: <span class="type">CGFloat</span> = rect.<span class="property">width</span> / <span class="number">2</span>
    <span class="keyword">let</span> centerY: <span class="type">CGFloat</span> = rect.<span class="property">height</span> / <span class="number">2</span>

    <span class="keyword">var</span> path = <span class="type">Path</span>()
    path.<span class="call">addArc</span>(
      center: <span class="type">CGPoint</span>(x: centerX, y: centerY),
      radius: <span class="call">min</span>(centerX, centerY),
      startAngle: <span class="type">Angle</span>(degrees: <span class="number">0</span>),
      endAngle: <span class="type">Angle</span>(degrees: <span class="number">360</span> * <span class="type">Double</span>(progress)),
      clockwise: <span class="keyword">false</span>
    )

    <span class="keyword">return</span> path
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/swiftui-protocols/progress.png"/><h2>Animations</h2><ul><li><code>Animatable</code></li><li><code>AnimatableModifier</code></li><li><code>GeometryEffect</code></li><li><code>VectorArithmetic</code></li></ul><p>Thanks to SwiftUI's declarative nature, animation bugs have basically disappeared: no longer we need to track and manage canceling transitions, calculate the right spring value based on gestures speed, etc.</p><p>Animations in SwiftUI are state-based (like everything else): as long as our view is on screen, the right animation for the given state change will happen automatically.</p><p><a href="https://twitter.com/SwiftUILab">Javier Nigro</a> has a great <a href="https://swiftui-lab.com/swiftui-animations-part1/">Advanced SwiftUI Animations</a> deep dive into this.</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> progress: <span class="type">CGFloat</span> = <span class="number">0</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ZStack</span> {
      <span class="type">Circle</span>()
        .<span class="call">stroke</span>(<span class="type">Color</span>.<span class="property">black</span>, lineWidth: <span class="number">10</span>)

      <span class="type">ProgressCircle</span>(progress: progress)
        .<span class="call">stroke</span>(<span class="type">Color</span>.<span class="property">yellow</span>, lineWidth: <span class="number">10</span>)
        .<span class="call">animation</span>(<span class="type">Animation</span>.<span class="property">linear</span>.<span class="call">speed</span>(<span class="number">0.4</span>))
        .<span class="call">onAppear</span> {
          progress = <span class="number">1</span>
        }
    }
  }
}

<span class="keyword">struct</span> ProgressCircle: <span class="type">Shape</span> {
  <span class="keyword">var</span> progress: <span class="type">CGFloat</span>

  <span class="comment">// 👇🏻 Animatable protocol requirement.</span>
  <span class="keyword">var</span> animatableData: <span class="type">CGFloat</span> {
    <span class="keyword">get</span> { progress }
    <span class="keyword">set</span> { progress = newValue }
  }

  <span class="keyword">func</span> path(in rect: <span class="type">CGRect</span>) -&gt; <span class="type">Path</span> {
    <span class="keyword">let</span> centerX: <span class="type">CGFloat</span> = rect.<span class="property">width</span> / <span class="number">2</span>
    <span class="keyword">let</span> centerY: <span class="type">CGFloat</span> = rect.<span class="property">height</span> / <span class="number">2</span>

    <span class="keyword">var</span> path = <span class="type">Path</span>()
    path.<span class="call">addArc</span>(
      center: <span class="type">CGPoint</span>(x: centerX, y: centerY),
      radius: <span class="call">min</span>(centerX, centerY),
      startAngle: <span class="type">Angle</span>(degrees: <span class="number">0</span>),
      endAngle: <span class="type">Angle</span>(degrees: <span class="number">360</span> * <span class="type">Double</span>(progress)),
      clockwise: <span class="keyword">false</span>
    )

    <span class="keyword">return</span> path
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/swiftui-protocols/progress.gif"/><h2>Environment</h2><ul><li><code>EnvironmentKey</code></li><li><code>EnvironmentalModifier</code></li></ul><p>SwiftUI's environment is one of the many ways we use to <a href="https://www.fivestars.blog/articles/app-state/">pass data within our view hierarchy</a>, <code>EnvironmentKey</code> lets us inject our own data in the environment, while <code>EnvironmentalModifier</code> lets us return view modifiers based on the current environment:</p><pre><code><span class="keyword">struct</span> AnimatableEnvModifier: <span class="type">EnvironmentalModifier</span> {
  <span class="keyword">func</span> resolve(in environment: <span class="type">EnvironmentValues</span>) -&gt; <span class="keyword">some</span> <span class="type">ViewModifier</span> {
    <span class="type">AnimatableModifier</span>(
    	isReduceMotionEnabled: environment.<span class="property">accessibilityReduceMotion</span>
    )
  }

  <span class="keyword">struct</span> AnimatableModifier: <span class="type">ViewModifier</span> {
    <span class="keyword">let</span> isReduceMotionEnabled: <span class="type">Bool</span>

    <span class="keyword">func</span> body(content: <span class="type">Content</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
      content
        .<span class="call">animation</span>(isReduceMotionEnabled ? .<span class="dotAccess">none</span> : .<span class="dotAccess">easeInOut</span>)
    }
  }
}

<span class="type">FSView</span>()
  .<span class="call">modifier</span>(<span class="type">AnimatableEnvModifier</span>())
</code></pre><h2>Previews</h2><ul><li><code>PreviewContext</code></li><li><code>PreviewContextKey</code></li><li><code>PreviewProvider</code></li></ul><p>With SwiftUI's introduction we have also gained Xcode previews, which speeds up immensely building and testing views right from Xcode, without the need to launch our app.</p><p>Xcode previews come with a set of protocols that let us declare and set the right context for each preview.</p><pre><code><span class="keyword">struct</span> FSWidget_Previews: <span class="type">PreviewProvider</span> {
  <span class="keyword">static var</span> previews: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Group</span> {
      <span class="type">LatestArticleWidgetView</span>(configuration: <span class="type">FSWidgetIntent</span>())
      <span class="type">LatestArticleWidgetView</span>(configuration: <span class="type">FSWidgetIntent</span>())
        .<span class="call">environment</span>(\.<span class="property">colorScheme</span>, .<span class="dotAccess">dark</span>)
    }
    .<span class="call">previewContext</span>(<span class="type">WidgetPreviewContext</span>(family: .<span class="dotAccess">systemSmall</span>))
  }
}
</code></pre><h2>Legacy bridges</h2><ul><li><code>UIViewControllerRepresentable</code> (iOS)</li><li><code>UIViewRepresentable</code> (iOS)</li><li><code>NSViewControllerRepresentable</code> (macOS)</li><li><code>NSViewRepresentable</code> (macOS)</li><li><code>WKInterfaceObjectRepresentable</code> (watchOS)</li></ul><p>While SwiftUI is incredibly powerful per se, at launch most Apple's frameworks did not offer a correspondent SwiftUI view: think <code>MFMailComposeViewController</code> or <code>UIImagePickerController</code> for example.</p><p>Instead of making it impossible to access such views, SwiftUI offers a set of protocols that functions as bridge to these legacy views that are not yet natively available in SwiftUI.</p><p>As a bonus, these protocols let us access our own legacy view definitions, making it easy to migrate to SwiftUI without having to re-write our own UI completely from scratch.</p><pre><code><span class="keyword">@available</span>(iOS, introduced: <span class="number">13</span>, deprecated: <span class="number">14</span>, message: <span class="string">"Use SwiftUI's ProgressView"</span>)
<span class="keyword">public struct</span> ActivityIndicator: <span class="type">UIViewRepresentable</span> {
  <span class="keyword">let</span> isAnimating: <span class="type">Bool</span>
  <span class="keyword">let</span> style: <span class="type">UIActivityIndicatorView</span>.<span class="type">Style</span>

  <span class="keyword">public init</span>(isAnimating: <span class="type">Bool</span>, style: <span class="type">UIActivityIndicatorView</span>.<span class="type">Style</span>) {
    <span class="keyword">self</span>.<span class="property">isAnimating</span> = isAnimating
    <span class="keyword">self</span>.<span class="property">style</span> = style
  }

  <span class="keyword">public func</span> makeUIView(
    context: <span class="type">UIViewRepresentableContext</span>&lt;<span class="type">ActivityIndicator</span>&gt;
  ) -&gt; <span class="type">UIActivityIndicatorView</span> {
    <span class="type">UIActivityIndicatorView</span>(style: style)
  }

  <span class="keyword">public func</span> updateUIView(
    <span class="keyword">_</span> uiView: <span class="type">UIActivityIndicatorView</span>,
    context: <span class="type">UIViewRepresentableContext</span>&lt;<span class="type">ActivityIndicator</span>&gt;
  ) {
    isAnimating ? uiView.<span class="call">startAnimating</span>() : uiView.<span class="call">stopAnimating</span>()
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/swiftui-protocols/activity.gif"/><h2>Responder chain</h2><ul><li><code>Commands</code></li><li><code>FocusedValueKey</code></li></ul><p>The responder chain is one of the many SwiftUI victims: there's no exact equivalent for this concept, on the other hand SwiftUI offers various replacements such as <code>Commands</code> and <code>FocusedValueKey</code>.</p><p>These protocols let us define our own ways to deal with various events coming from different parts of the app, this is something I look forward to see further explored at this year's WWDC.</p><pre><code><span class="keyword">@main
struct</span> FSApp: <span class="type">App</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">Scene</span> {
    <span class="type">WindowGroup</span> {
      <span class="type">ContentView</span>()
    }
    .<span class="call">commands</span> {
      <span class="type">CommandMenu</span>(<span class="string">"Five Stars menu"</span>) {
        <span class="type">Button</span>(<span class="string">"Secret action"</span>) {
          <span class="comment">// "Five Stars menu/Secret action" has been triggered.</span>
        }.<span class="call">keyboardShortcut</span>(<span class="string">"f"</span>)
      }
    }
  }
}
</code></pre><h2>Toolbar</h2><ul><li><code>ToolbarContent</code></li><li><code>CustomizableToolbarContent</code></li></ul><p>When declaring toolbars, we add items via one of the three <code>.toolbar(content:)</code> view modifiers:<br>while one of them accepts any view via a <code>@ViewBuilder content: () -&gt; Content</code> parameter, the other two accept a <code>@ToolbarContentBuilder content: () -&gt; Content</code> parameter instead, where the content is either conforming to <code>ToolbarContent</code> or <code>CustomizableToolbarContent</code>.</p><p>These last two view modifiers will probably let SwiftUI further optimize/organize the toolbar.</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">FSView</span>()
        .<span class="call">toolbar</span> {
          <span class="type">ToolbarItem</span>(id: <span class="string">"add-action"</span>) {
            <span class="type">Button</span>(<span class="string">"Add"</span>, action: {
              <span class="comment">// add action triggered</span>
            })
          }
        }
      .<span class="call">navigationTitle</span>(<span class="string">"FIVE STARS"</span>)
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/swiftui-protocols/add.png"/><h2>Documents (iOS, macOS)</h2><ul><li><code>FileDocument</code></li><li><code>ReferenceFileDocument</code></li></ul><p>Both protocols are used to define the supported file type in a document based app:</p><pre><code><span class="keyword">@main
struct</span> FSApp: <span class="type">App</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">Scene</span> {
    <span class="type">DocumentGroup</span>(newDocument: <span class="type">TextDocument</span>()) { file <span class="keyword">in</span>
      <span class="type">ContentView</span>(document: file.<span class="property">$document</span>)
    }
  }
}

<span class="keyword">struct</span> TextDocument: <span class="type">FileDocument</span> {
  <span class="keyword">static var</span> readableContentTypes = [<span class="type">UTType</span>.<span class="property">plainText</span>]
  <span class="keyword">var</span> text = <span class="string">""</span>

  <span class="keyword">init</span>(configuration: <span class="type">ReadConfiguration</span>) <span class="keyword">throws</span> {
    <span class="keyword">if let</span> data = configuration.<span class="property">file</span>.<span class="property">regularFileContents</span>{
      text = <span class="type">String</span>(data: data, encoding: .<span class="dotAccess">utf8</span>) ?? <span class="string">""</span>
    } <span class="keyword">else</span> {
      <span class="keyword">throw</span> <span class="type">CocoaError</span>(.<span class="dotAccess">fileReadCorruptFile</span>)
    }
  }

  <span class="keyword">func</span> fileWrapper(configuration: <span class="type">WriteConfiguration</span>) <span class="keyword">throws</span> -&gt; <span class="type">FileWrapper</span> {
    <span class="keyword">let</span> data = text.<span class="call">data</span>(using: .<span class="dotAccess">utf8</span>)!
    <span class="keyword">return</span> .<span class="keyword">init</span>(regularFileWithContents: data)
  }

  <span class="keyword">init</span>(initialText: <span class="type">String</span> = <span class="string">""</span>) {
    text = initialText
  }
}
</code></pre><h2>One-off protocols</h2><p>Lastly, we have a few protocols that do not belong to any particular group, let's have a run through all of them!</p><h3>Custom alignment</h3><ul><li><code>AlignmentID</code></li></ul><p>When <a href="https://www.fivestars.blog/articles/stack-spacer-alternatives/">playing with frames and spacers</a> doesn't cut it, SwiftUI lets us define our own powerful alignments, applicable among multiple views in different stacks.</p><pre><code><span class="keyword">public extension</span> <span class="type">VerticalAlignment</span> {
  <span class="keyword">struct</span> CustomAlignment: <span class="type">AlignmentID</span> {
    <span class="keyword">public static func</span> defaultValue(in context: <span class="type">ViewDimensions</span>) -&gt; <span class="type">CGFloat</span> {
      context[<span class="type">VerticalAlignment</span>.<span class="property">center</span>]
    }
  }

  <span class="keyword">static let</span> navigationTitle = <span class="type">VerticalAlignment</span>(<span class="type">CustomAlignment</span>.<span class="keyword">self</span>)
}


<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ZStack</span>(alignment: <span class="type">Alignment</span>(horizontal: .<span class="dotAccess">center</span>, vertical: .<span class="dotAccess">navigationTitle</span>)) {
      <span class="type">Color</span>.<span class="property">yellow</span>
        .<span class="call">frame</span>(width: <span class="number">200</span>, height: <span class="number">40</span>)
        .<span class="call">alignmentGuide</span>(.<span class="dotAccess">navigationTitle</span>) { $0[<span class="type">VerticalAlignment</span>.<span class="property">top</span>] }

      <span class="type">Text</span>(<span class="string">"FIVE STARS"</span>)
        .<span class="call">font</span>(.<span class="dotAccess">title</span>)
        .<span class="call">alignmentGuide</span>(.<span class="dotAccess">navigationTitle</span>) { $0[<span class="type">VerticalAlignment</span>.<span class="property">center</span>] }
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/swiftui-protocols/middle.png"/><h3>Reading child values</h3><ul><li><code>PreferenceKey</code></li></ul><p>While most SwiftUI communications are propagated from parent to child views, sometimes it's the children that need to communicate to their parents. In such cases there's <code>PreferenceKey</code>, which we took a deep dive on in <a href="https://www.fivestars.blog/articles/preferencekey-reduce/"><code>PreferenceKey</code>'s reduce method demystified</a>.</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">@warn_unqualified_access
  func</span> readSize(onChange: <span class="keyword">@escaping</span> (<span class="type">CGSize</span>) -&gt; <span class="type">Void</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">background</span>(
      <span class="type">GeometryReader</span> {
        <span class="type">Color</span>.<span class="property">clear</span>
          .<span class="call">preference</span>(key: <span class="type">SizePreferenceKey</span>.<span class="keyword">self</span>, value: $0.<span class="property">size</span>)
      }
    )
    .<span class="call">onPreferenceChange</span>(<span class="type">SizePreferenceKey</span>.<span class="keyword">self</span>, perform: onChange)
  }
}

<span class="keyword">private struct</span> SizePreferenceKey: <span class="type">PreferenceKey</span> {
  <span class="keyword">static var</span> defaultValue = <span class="type">CGSize</span>.<span class="property">zero</span>
  <span class="keyword">static func</span> reduce(value: <span class="keyword">inout</span> <span class="type">Value</span>, nextValue: () -&gt; <span class="type">Value</span>) { }
}
</code></pre><blockquote><p>We created this extension in <a href="https://www.fivestars.blog/articles/swiftui-share-layout-information/">Sharing layout information in SwiftUI</a>, with the addition of the <a href="https://www.fivestars.blog/articles/warn_unqualified_access/"><code>@warn_unqualified_access</code> attribute</a>.</p></blockquote><h3>Advanced drop management</h3><ul><li><code>DropDelegate</code></li></ul><p>While the delegation pattern has been replaced by closures in SwiftUI (e.g. <code>onAppear(perform:)</code>), there's one exception: <code>DropDelegate</code>.</p><p>This protocol lets us receive various drop events and even filter/stop drops in our views.</p><p><a href="https://twitter.com/SwiftUILab">Javer</a> has a <a href="https://swiftui-lab.com/drag-drop-with-swiftui/">great writeup</a> into this.</p><pre><code><span class="keyword">struct</span> FSDropDelegate: <span class="type">DropDelegate</span> {

  <span class="keyword">func</span> validateDrop(info: <span class="type">DropInfo</span>) -&gt; <span class="type">Bool</span> {
    <span class="keyword">true</span> <span class="comment">// Accept or reject the drop here.</span>
  }

  <span class="keyword">func</span> dropEntered(info: <span class="type">DropInfo</span>) {
    <span class="comment">// "Drop entered the view" event. Use this to shows that the view can accept
    // the object.</span>
  }

  <span class="keyword">func</span> performDrop(info: <span class="type">DropInfo</span>) -&gt; <span class="type">Bool</span> {
    <span class="keyword">true</span> <span class="comment">// Executed when the user "drops" their object into the view.</span>
  }
}
</code></pre><h3>SwiftUI property wrappers</h3><ul><li><code>DynamicProperty</code></li></ul><p>As covered in <a href="https://www.fivestars.blog/articles/swiftui-graph/">A sneak peek into SwiftUI's graph</a>, SwiftUI uses this protocol to find all views whose data needs to be managed and observed by SwiftUI itself.</p><p>Furthermore, as we've seen in <a href="articles/lets-build-state/">Let's build <code>@State</code></a>, we can use this protocol ourselves to define our own property wrappers recognized by SwiftUI.</p><pre><code><span class="keyword">@propertyWrapper
struct</span> AlternativeState&lt;V&gt;: <span class="type">DynamicProperty</span> {
  <span class="keyword">@State var</span> wrappedValue: <span class="type">V</span>

  <span class="keyword">var</span> projectedValue: <span class="type">Binding</span>&lt;<span class="type">V</span>&gt; {
    <span class="type">Binding</span>(
      get: { wrappedValue },
      set: { wrappedValue = $0 }
    )
  }
}

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@AlternativeState var</span> text = <span class="string">"Five Stars"</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">Text</span>(text)
      <span class="type">Button</span>(<span class="string">"change"</span>) {
        text = [<span class="string">"five"</span>, <span class="string">"stars"</span>].<span class="call">randomElement</span>()!
      }
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/swiftui-protocols/change.gif"/><h3>Data collection views</h3><ul><li><code>DynamicViewContent</code></li></ul><p><code>DynamicViewContent</code> is used by special views that come with an associated and generic <code>data</code> collection:<br>views that conform to this method gains various modifiers such as <code>onInsert</code> and others, letting SwiftUI change the underlying data.</p><p>So far it's conformed only by <code>ForEach</code>, but our own view definitions can conform to it, too.</p><pre><code><span class="type">ForEach</span>(items) { item <span class="keyword">in</span>
  <span class="type">Text</span>(item.<span class="property">title</span>)
}
.<span class="call">onDelete</span>(perform: { ... })
.<span class="call">onMove</span>(perform: { ... })
</code></pre><h3>Gestures</h3><ul><li><code>Gesture</code></li></ul><p>Last but not least, SwiftUI comes with many gestures, which are all grouped together under the <code>Gesture</code> protocol.<br>This protocol also comes with various events our views can subscribe to, such as <code>onEnded(_:)</code> and <code>updating(_:body:)</code>.</p><p>Just like <code>View</code>s, we can compose multiple gestures together, combining <a href="https://developer.apple.com/documentation/swiftui/gesture/simultaneously(with:)">simultaneous</a>, <a href="https://developer.apple.com/documentation/swiftui/gesture/sequenced(before:)">sequential</a>, or <a href="https://developer.apple.com/documentation/swiftui/gesture/exclusively(before:)">exclusive</a> gestures.</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="comment">// 👇🏻 a Gesture</span>
    <span class="keyword">let</span> longPressDragGesture = <span class="type">LongPressGesture</span>(minimumDuration: <span class="number">0.5</span>)
      .<span class="call">sequenced</span>(before: <span class="type">DragGesture</span>())
      .<span class="call">onEnded</span> { value <span class="keyword">in</span>
        <span class="comment">// gesture has ended event.</span>
      }

    <span class="type">Text</span>(<span class="string">"Five Stars"</span>)
      .<span class="call">gesture</span>(longPressDragGesture)
  }
}
</code></pre><h2>Conclusions</h2><p>There you have it! In this article we've covered all fifty-four public protocols that make SwiftUI what it is today.</p><p>We definitely don't need to know all of them in order to build amazing products, still it's quite handy to know what to reach for whenever we have a new challenge in front of us.</p><p>If I've missed anything or if you have any feedback please feel free to reach me out via <a href="mailto:hello@fivestars.blog">email</a> or <a href="https://twitter.com/zntfdr">Twitter</a>, thank you!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/five-stars-2</guid><title>Welcome to FIVE STARS 2.0!</title><description>Come and discover everything that's new in this first FIVE STARS overhaul, there's also an announcement for people that would like to support the website, and more!</description><link>https://www.fivestars.blog/articles/five-stars-2</link><pubDate>Tue, 6 Apr 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>The first FIVE STARS overhaul is here!</p><p>Since relaunching the website back in 2019, FIVE STARS went on with <a href="https://twitter.com/zntfdr/status/1123464490341761024?s=12">small iterations</a> here and there, in the meanwhile, the content has outgrown the simple jekyll template that was being used.</p><p>Instead of continuing iterating, I took this opportunity to rewrite the whole website from the ground up with <a href="https://github.com/johnsundell/publish"><code>Publish</code></a>: let's have a look at what's new!</p><h2>Home page</h2><p>The first challenge I wanted to tackle was articles navigation: the homepage has been completely redesigned and now comes with a <code>What's New</code> section, highlighting the latest six articles, followed by the five most popular categories, each showcasing their own most recent articles.</p><p>All sections come with a <code>Browse all</code> button, which filters and highlights all articles for said category.</p><h2>Article page</h2><p>The focus has always been the content, which now takes even more screen estate than before.</p><p>Every article displays its categories right under the title, making it clear from the start what the article is about.</p><p>These categories also function as buttons/filters: tapping on any of them will show all articles under that category.</p><p>Continuing with navigation enhancements, all articles come with a <code>Related articles</code> section at the bottom of their page, these articles are hand-picked and feature both older and newer articles, letting the reader go deeper into the topic covered in the main article.</p><p>After this section we have even more recommended articles, all from the same category (or categories) as the current one, allowing readers to explore other topics.</p><h2>Support &amp; sponsorship</h2><p>Something that has been asked more and more lately, and I'm very thankful for: FIVE STARS now accepts sponsors!</p><p>Whether you're a company or an individual, if you have ever benefited from my work and would like to support me, or if you would like to reach out the FIVE STARS audience with your awesome products, it's now possible:<br>please check out the new <a href="https://www.fivestars.blog/sponsor/">sponsor</a> page for more details and perks.</p><p>A big THANK YOU to everyone that has been asking for this!</p><h2>...and more!</h2><p>There are plenty of improvements scattered around, and I'm sure I have forgotten some as this overhaul has been ongoing for several months, here are some important updates:</p><ul><li>FIVE STARS has now a new twitter account! Follow <a href="https://twitter.com/fivestarsblog">@FiveStarsBlog</a> to be alerted on new articles, and more</li><li>@fivestars.blog emails! If you have any comments/feedback you can now reach out via email at <a href="mailto:hello@fivestars.blog">hello@fivestars.blog</a></li><li>FIVE STARS is a <a href="http://github.com/FiveStarsBlog/">GitHub organization</a>! All past and future projects will be hosted <a href="http://github.com/FiveStarsBlog/">here</a>.</li></ul><h2>Conclusions</h2><p>Despite having completely changed format, both <a href="https://www.fivestars.blog/feed.rss">feed RSS</a> and previous links to articles should be handled correctly.</p><p>With that being said, as this is a big change, things might have broken here and there: please feel free to reach out (via <a href="mailto:hello@fivestars.blog">email</a> or <a href="https://twitter.com/zntfdr">twitter</a>) and let me know of any issue you might encounter, I will be very happy to fix everything!</p><p>If there's anything else you would like to see, or if you have any feedback and/or suggestion to make this website even better, feel free to reach out as well!</p><p>This is just the beginning: I'm incredibly grateful for how much FIVE STARS has grown in just two years, and I cannot wait to see where it will bring us next, thank you for reading!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-graph</guid><title>A sneak peek into SwiftUI's graph</title><description>Let's explore how SwiftUI knows what and when to observe view-specific publishers at any given time!</description><link>https://www.fivestars.blog/articles/swiftui-graph</link><pubDate>Tue, 30 Mar 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>One performance tip that the SwiftUI team has given us <a href="https://www.wwdcnotes.com/notes/wwdc20/10040/">during last year's WWDC</a> is to make views initializers as light as possible: this is because our views can be created and destroyed multiple times every second.</p><p>From another perspective, our <code>View</code>s are not actual views, they should be seen more as recipes to how views look and behave, but their actual implementation is handled behind the scenes by SwiftUI.</p><p>Furthermore, this explains why we use property wrappers such as <code>@State</code> and <code>@StateObject</code>: our <code>View</code>s do not own/hold such values, it's their SwiftUI implementation counterparts that do so.</p><p>SwiftUI is a state-driven framework: things happens because something changes, and something else is observing that change.</p><p>In <a href="https://www.fivestars.blog/articles/lets-build-state/">Let's build @State</a> we've covered how we can create our own property wrappers observed by SwiftUI, in this new article let's explore how SwiftUI knows what to observe in the first place.</p><blockquote><p>As usual, I have no access to the actual SwiftUI code/implementation. What we find here is a best guess/mock of the original behavior, there’s probably much more to it in the real implementation.</p></blockquote><h2>The SwiftUI graph</h2><blockquote><p>In <a href="https://en.wikipedia.org/wiki/Graph_theory">graph theory</a> a tree is a specific type of graph. This article uses both 'graph' and 'tree': even when it's just written 'graph', we're talking about a 'tree graph'.</p></blockquote><p>When we display a SwiftUI view, SwiftUI creates and tracks its associated view graph.</p><p>Imagine that this is our app main view:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">HStack</span> {
        <span class="type">NavigationLink</span>(
          <span class="string">"Go to A"</span>,
          destination: <span class="type">ViewA</span>()
        )

        <span class="type">NavigationLink</span>(
          <span class="string">"Go to B"</span>,
          destination: <span class="type">ViewB</span>()
        )
      }
    }
  }
}
</code></pre><p>Where <code>ViewA</code> and <code>ViewB</code> are defined as following:</p><pre><code><span class="keyword">struct</span> ViewA: <span class="type">View</span> {
  <span class="keyword">@State var</span> stringState: <span class="type">String</span> = <span class="string">"A"</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">Text</span>(stringState)
      <span class="type">Button</span>(<span class="string">"Change string"</span>) {
        stringState = [<span class="string">"A"</span>, <span class="string">"AA"</span>, <span class="string">"AAA"</span>, <span class="string">"AAAA"</span>].<span class="call">randomElement</span>()!
      }
    }
  }
}

<span class="keyword">struct</span> ViewB: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Text</span>(<span class="string">"B"</span>)
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/swiftui-graph/demo.gif"/><p>Using the same approach in <a href="https://www.fivestars.blog/articles/inspecting-views/">Inspecting SwiftUI views</a>, we can explore the associated view tree, which roughly matches SwiftUI's internal graph:</p><pre><code><span class="type">NavigationView</span>&lt;
  <span class="type">HStack</span>&lt;
    <span class="type">TupleView</span>&lt;
      (
        <span class="type">NavigationLink</span>&lt;<span class="type">Text</span>, <span class="type">ViewA</span>&gt;, 
        <span class="type">NavigationLink</span>&lt;<span class="type">Text</span>, <span class="type">ViewB</span>&gt;
      )
    &gt;
  &gt;
&gt;
</code></pre><blockquote><p>Formatted for better readability.</p></blockquote><p>Where <code>NavigationView</code> is the <em>root</em> of our tree, and each <code>NavigationLink</code> is a <em>leaf node</em>.</p><p>When the user views <code>ContentView</code>, the tree above is all SwiftUI needs to worry about.</p><p>Let's assume that the user taps on <code>Go to A</code> next, at that point the <em>active</em> SwiftUI graph will expand to contain <code>ViewA</code>'s own tree:</p><pre><code><span class="type">VStack</span>&lt;
  <span class="type">TupleView</span>&lt;
    (
      <span class="type">Text</span>, 
      <span class="type">Button</span>&lt;<span class="type">Text</span>&gt;
    )
  &gt;
&gt;
</code></pre><p>The new active SwiftUI graph:</p><pre><code><span class="type">NavigationView</span>&lt;
  <span class="type">HStack</span>&lt;
    <span class="type">TupleView</span>&lt;
      (
        <span class="type">NavigationLink</span>&lt;
          <span class="type">Text</span>, 
          <span class="type">VStack</span>&lt;
            <span class="type">TupleView</span>&lt;
              (
                <span class="type">Text</span>, 
                <span class="type">Button</span>&lt;<span class="type">Text</span>&gt;
              )
            &gt;
          &gt;
        &gt;, 
        <span class="type">NavigationLink</span>&lt;<span class="type">Text</span>, <span class="type">ViewB</span>&gt;
      )
    &gt;
  &gt;
&gt;
</code></pre><p><code>ViewA</code> is not a <em>static</em> view, but comes with its own <code>@State</code> property wrapper, which in turn comes with its own storage and publisher:</p><ul><li>as long as <code>ViewA</code> is part of the <em>active</em> graph, SwiftUI will need to allocate, hold, and subscribe to <code>ViewA</code>'s dynamic properties.</li><li>when the user moves back to <code>ContentView</code>, <code>ViewA</code> will be removed from SwiftUI's active graph, and all <code>ViewA</code>'s dynamic properties and associated storage/publishers will need to be freed as well.</li></ul><p>How does SwiftUI know which storage/publishers is associated to each view? Let's answer that next.</p><h2>Dynamic vs. static views</h2><p>Imagine that our app needs to display <code>ViewA</code>:<br>before doing so, SwiftUI needs to figure out whether <code>ViewA</code> is dynamic (a.k.a. has its own storage/publishers) or static (a.k.a. it's a set of primitives such as <code>Int</code>, <code>String</code> etc).</p><p>The answer to this question lays on the view's property wrappers (or the lack of them).</p><p>It's important to note how SwiftUI's property wrappers do not come with the actual associated values, but only with a reference to them: these values are not part of the <code>View</code> itself.</p><p>This is to say, we can initialize a dynamic <code>View</code> even when its associated storage has not been allocated yet.</p><p>With this in mind, SwiftUI can use reflection to figure out which dynamic properties a view has, before the view becomes part of the active graph:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="comment">/// Finds and returns the dynamic properties of a view instance.</span>
  <span class="keyword">func</span> dynamicProperties() -&gt; [(<span class="type">String</span>, <span class="type">DynamicProperty</span>)] {
    <span class="type">Mirror</span>(reflecting: <span class="keyword">self</span>)
      .<span class="dotAccess">children</span>
      .<span class="call">compactMap</span> { child <span class="keyword">in
        if var</span> name = child.<span class="property">label</span>,
           <span class="keyword">let</span> property = child.<span class="property">value</span> <span class="keyword">as</span>? <span class="type">DynamicProperty</span> {

          <span class="comment">// Property wrappers have underscore-prefixed names.</span>
          name = <span class="type">String</span>(name.<span class="property">first</span> == <span class="string">"_"</span> ? name.<span class="call">dropFirst</span>(<span class="number">1</span>) : name.<span class="call">dropFirst</span>(<span class="number">0</span>))

          <span class="keyword">return</span> (name, property)
        }
        <span class="keyword">return nil</span>
      }
  }
}
</code></pre><p>This new <code>View</code> method:</p><ul><li>gets a <code>Mirror</code> representation of the view</li><li>extracts the view properties via the mirror's <code>children</code> property</li><li>filters and returns the name and and value of each <code>DynamicProperty</code> of the view.</li></ul><blockquote><p>As we've seen in <a href="https://www.fivestars.blog/articles/lets-build-state/">Let's build @State</a>, all SwiftUI's property wrappers conform to the <code>DynamicProperty</code> protocol.</p></blockquote><p>With <code>dynamicProperties()</code> (or a similar method) SwiftUI can determine whether a view is static or dynamic, and can add such findings to the associated view node in its internal graph.</p><p>Thanks to this knowledge SwiftUI has what it needs to let the user navigate within its graph, and knows what and when to instantiate and/or destroy at each movement.</p><h2>ViewA example</h2><p>To make things clearer, let's call <code>dynamicProperties()</code> on <code>ViewA</code>.</p><p>First, let's do so in <code>ViewA</code>'s initializer:</p><pre><code><span class="keyword">struct</span> ViewA: <span class="type">View</span> {
  <span class="keyword">@State var</span> stringState: <span class="type">String</span> = <span class="string">"A"</span>

  <span class="keyword">init</span>() {
    <span class="call">print</span>(<span class="call">dynamicProperties</span>())
  }

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    ...
  }
}
</code></pre><p>The first time this gets executed is when SwiftUI evaluates <code>ContentView</code>'s body, as <code>ViewA()</code> is part of one <code>NavigationLink</code> declaration. This is <code>dynamicProperties()</code>'s output:</p><pre><code>[
  (
    <span class="string">"stringState"</span>,
    <span class="type">SwiftUI</span>.<span class="type">State</span>&lt;<span class="type">Swift</span>.<span class="type">String</span>&gt;(_value: <span class="string">"A"</span>, _location: <span class="keyword">nil</span>)
  )
]
</code></pre><p><code>dynamicProperties()</code> correctly finds <code>ViewA</code>'s <code>@State</code> property (named <code>stringState</code> by us), however at this point <code>ViewA</code> is not part of SwiftUI's active graph, therefore there's no associated storage yet (a.k.a. <code>_location: nil</code>).</p><p>Let's now call <code>dynamicProperties()</code> anywhere during <code>ViewA</code>'s life cycle, for example on <code>onAppear</code>:</p><pre><code><span class="keyword">struct</span> ViewA: <span class="type">View</span> {
  <span class="keyword">@State var</span> stringState: <span class="type">String</span> = <span class="string">"A"</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      ...
    }
    .<span class="call">onAppear</span>(perform: {
      <span class="call">print</span>(<span class="call">dynamicProperties</span>())
    })
  }
}
</code></pre><p>The only way for <code>ViewA</code>'s <code>onAppear</code> to trigger is for the view to be about to be shown to the user, making <code>ViewA</code> part of SwiftUI's <em>active</em> graph, in this case <code>dynamicProperties()</code>'s output is as following:</p><pre><code>[
  (
    <span class="string">"stringState"</span>, 
    <span class="type">SwiftUI</span>.<span class="type">State</span>&lt;<span class="type">Swift</span>.<span class="type">String</span>&gt;(
      _value: <span class="string">"A"</span>, 
      _location: <span class="type">Optional</span>(<span class="type">SwiftUI</span>.<span class="type">StoredLocation</span>&lt;<span class="type">Swift</span>.<span class="type">String</span>&gt;)
    )
  )
]
</code></pre><p>Our <code>@State</code> property is found again however, as <code>ViewA</code> is now part of SwiftUI's active graph, its associated state is fully initialized and managed, which we can confirm from the new <code>location</code> value.</p><p>Once the user goes back to <code>ContentView</code>, this <code>@State</code> (and its <code>SwiftUI.StoredLocation</code>) will be destroyed and will not be re-created until <code>ViewA</code> will be part of active graph once more.</p><h2>Conclusions</h2><p>In this article we took another <em>behind the scenes</em> look at how SwiftUI works and manages our views:<br>most of the time we won't need to dive this deep, however it's great knowledge to have and might help us understand why things sometimes do not work the way we expect.</p><p>I have no "inside knowledge" beside my own experimentation/experience: there's no doubt that this article over-simplifies and neglects some aspects. Regardless, it's been very helpful during my own app development, and I hope it will be for you as well.</p><p>If there's anything that you'd like me to amend/add/further-clarify, <a href="https://twitter.com/zntfdr">feel free to reach me out</a>!</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles.</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/inspecting-views</guid><title>Inspecting SwiftUI views</title><description>SwiftUI composition on steroids: let's see how we can inspect and change any given view.</description><link>https://www.fivestars.blog/articles/inspecting-views</link><pubDate>Tue, 23 Mar 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>In <a href="https://www.fivestars.blog/articles/design-system-composing-views/">Composing SwiftUI views</a> and <a href="https://www.fivestars.blog/articles/swiftui-patter-passing-views/">SwiftUI patterns: passing &amp; accepting views</a> we've covered how composition is one of SwiftUI's most powerful tools at our disposal.</p><p>So far we've always accepted the given input, usually in the form of a <code>@ViewBuilder content: () -&gt; Content</code> parameter, as a black box: it's a view, that's all we needed to know.</p><p>But what if we wanted to know more about that view? In this article, let's explore how we can do so.</p><h2>A new picker component</h2><p>Let's imagine that we've been tasked to build a new picker/segmented control:</p><img src="https://www.fivestars.blog/assets/posts/inspecting-views/custom.gif"/><p>A good place to start is SwiftUI's <a href="https://developer.apple.com/documentation/swiftui/picker"><code>Picker</code></a>:</p><pre><code><span class="keyword">public struct</span> Picker&lt;Label: <span class="type">View</span>, SelectionValue: <span class="type">Hashable</span>, Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">public init</span>(
    selection: <span class="type">Binding</span>&lt;<span class="type">SelectionValue</span>&gt;,
    label: <span class="type">Label</span>,
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>
  )
}
</code></pre><p>Which we can use this way:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> selection = <span class="number">0</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Picker</span>(selection: $selection, label: <span class="type">Text</span>(<span class="string">""</span>)) {
      <span class="type">Text</span>(<span class="string">"First"</span>).<span class="call">tag</span>(<span class="number">0</span>)
      <span class="type">Text</span>(<span class="string">"Second"</span>).<span class="call">tag</span>(<span class="number">1</span>)
      <span class="type">Text</span>(<span class="string">"Third"</span>).<span class="call">tag</span>(<span class="number">2</span>)
    }
    .<span class="call">pickerStyle</span>(<span class="type">SegmentedPickerStyle</span>())
  }
}
</code></pre><blockquote><p>For simplicity's sake we will ignore the <code>label</code> parameter.</p></blockquote><img src="https://www.fivestars.blog/assets/posts/inspecting-views/original.gif"/><p><code>Picker</code> uses <a href="https://developer.apple.com/documentation/swiftui/view/tag(_:)"><code>tag(_:)</code></a> to identify which element corresponds to which <code>selection</code> value.</p><p>This is very important, as the picker needs to:</p><ul><li>highlight the selected element at any time</li><li>add a tap gesture to each element</li><li>update the current selection based on the user interaction.</li></ul><h2>FSPickerStyle</h2><p>At first we might try to create a new <a href="https://developer.apple.com/documentation/swiftui/pickerStyle"><code>PickerStyle</code></a>, as we did with <a href="https://www.fivestars.blog/articles/label/"><code>Label</code></a> and <a href="https://www.fivestars.blog/articles/button-styles/"><code>Button</code></a>, here's <code>PickerStyle</code> definition:</p><pre><code><span class="keyword">public protocol</span> PickerStyle {
}
</code></pre><p>Cool, no requirements! Let's create our picker style then:</p><pre><code><span class="keyword">struct</span> FSPickerStyle: <span class="type">PickerStyle</span> {
}
</code></pre><p>This won't build. While there are no public requirements, <code>PickerStyle</code> actually has some private/internal ones, which look like this:</p><pre><code><span class="keyword">protocol</span> PickerStyle {
  <span class="keyword">static func</span> _makeView&lt;SelectionValue&gt;(
    value: <span class="type">_GraphValue</span>&lt;<span class="type">_PickerValue</span>&lt;<span class="type">FSPickerStyle</span>, <span class="type">SelectionValue</span>&gt;&gt;, 
    inputs: <span class="type">_ViewInputs</span>
  ) -&gt; <span class="type">_ViewOutputs</span> <span class="keyword">where</span> <span class="type">SelectionValue</span>: <span class="type">Hashable</span>

  <span class="keyword">static func</span> _makeViewList&lt;SelectionValue&gt;(
    value: <span class="type">_GraphValue</span>&lt;<span class="type">_PickerValue</span>&lt;<span class="type">FSPickerStyle</span>, <span class="type">SelectionValue</span>&gt;&gt;,
    inputs: <span class="type">_ViewListInputs</span>
  ) -&gt; <span class="type">_ViewListOutputs</span> <span class="keyword">where</span> <span class="type">SelectionValue</span>: <span class="type">Hashable</span>
}
</code></pre><p>The alarming number of <code>_</code> underscores, a.k.a. "<em>private stuff</em>", should tell us that is the <em>not</em> the way we want to go:<br>exploring such APIs is left for the curious/adventurous (if you uncover anything cool, <a href="https://twitter.com/zntfdr">please let me know</a>!).</p><p>With <code>PickerStyle</code> not being a viable option, let's move to build our own SwiftUI picker from scratch.</p><h2>FSPicker</h2><p>Despite creating our own component, we still want to mimic SwiftUI's <code>Picker</code> APIs as close as possible:</p><pre><code><span class="keyword">public struct</span> FSPicker&lt;SelectionValue: <span class="type">Hashable</span>, Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">@Binding var</span> selection: <span class="type">SelectionValue</span>
  <span class="keyword">let</span> content: <span class="type">Content</span>

  <span class="keyword">public init</span>(
    selection: <span class="type">Binding</span>&lt;<span class="type">SelectionValue</span>&gt;, 
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>
  ) {
    <span class="keyword">self</span>.<span class="property">_selection</span> = selection
    <span class="keyword">self</span>.<span class="property">content</span> = <span class="call">content</span>()
  }

  <span class="keyword">public var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    ...
  }
}
</code></pre><p>So far so good, thanks to this declaration we can go back to original example, add a <code>FS</code> prefix to the <code>Picker</code>, and everything would build fine:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> selection = <span class="number">0</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="comment">// 👇🏻 our picker!</span>
    <span class="type">FSPicker</span>(selection: $selection) {
      <span class="type">Text</span>(<span class="string">"First"</span>).<span class="call">tag</span>(<span class="number">0</span>)
      <span class="type">Text</span>(<span class="string">"Second"</span>).<span class="call">tag</span>(<span class="number">1</span>)
      <span class="type">Text</span>(<span class="string">"Third"</span>).<span class="call">tag</span>(<span class="number">2</span>)
    }
  }
}
</code></pre><p>Now comes the challenge: implementing <code>FSPicker</code>'s <code>body</code>.</p><h3>FSPicker's body</h3><p>When we see a parameter such as <code>@ViewBuilder content: () -&gt; Content</code>, we normally treat it as something that we put somewhere in our own view <code>body</code>, however we can't do just that for our (and SwiftUI's) picker.</p><p>This is because our picker's <code>body</code> needs take this <code>content</code>, highlight the selected element, and add gestures to react to user selections.</p><p>A workaround for this challenge would be to replace our generic <code>Content</code> parameter with something that we can directly play with. For example we could replace <code>Content</code> with an array of tuples, where each tuple contains a <code>String</code> and an associated <code>SelectionValue</code>:</p><pre><code><span class="keyword">public struct</span> FSPicker&lt;SelectionValue: <span class="type">Hashable</span>&gt;: <span class="type">View</span> {
  <span class="keyword">@Binding var</span> selection: <span class="type">SelectionValue</span>
  <span class="keyword">let</span> content: [(<span class="type">String</span>, <span class="type">SelectionValue</span>)]

  <span class="keyword">public init</span>(
    selection: <span class="type">Binding</span>&lt;<span class="type">SelectionValue</span>&gt;,
    content: [(<span class="type">String</span>, <span class="type">SelectionValue</span>)]
  ) {
    <span class="keyword">self</span>.<span class="property">_selection</span> = selection
    <span class="keyword">self</span>.<span class="property">content</span> = content
  }

  <span class="keyword">public var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">HStack</span> {
      <span class="type">ForEach</span>(content, id: \.<span class="number">1</span>) { (title, value) <span class="keyword">in</span>
        <span class="type">Button</span>(title) { selection = value }
      }
    }
  }
}
</code></pre><p>However we wouldn't follow SwiftUI's <code>Picker</code> APIs anymore.</p><p>Instead, let's make things more interesting: let's embrace our "black box" <code>Content</code> and use Swift's reflection!</p><h3>Mirrors all the way down</h3><p>While it's not always possible for our picker to know the actual <code>content</code> at build time, Swift lets us inspect this value at run time via <a href="https://developer.apple.com/documentation/swift/mirror"><code>Mirror</code></a>.</p><p>Let's update our <code>FSPicker</code> declaration with the following:</p><pre><code><span class="keyword">public struct</span> FSPicker&lt;SelectionValue: <span class="type">Hashable</span>, Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  ...
  <span class="keyword">public init</span>(...) { ... }

  <span class="keyword">public var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">let</span> contentMirror = <span class="type">Mirror</span>(reflecting: content)
    <span class="keyword">let</span> _ = <span class="call">print</span>(contentMirror)
    <span class="type">EmptyView</span>()
  }
}
</code></pre><p>If we now run this with our original example, we will see the following logs in Xcode's Debug Area Console (formatted for better readability):</p><pre><code><span class="type">Mirror</span> <span class="keyword">for</span> <span class="type">TupleView</span>&lt;
  (
    <span class="type">ModifiedContent</span>&lt;<span class="type">Text</span>, <span class="type">_TraitWritingModifier</span>&lt;<span class="type">TagValueTraitKey</span>&lt;<span class="type">Int</span>&gt;&gt;&gt;, 
    <span class="type">ModifiedContent</span>&lt;<span class="type">Text</span>, <span class="type">_TraitWritingModifier</span>&lt;<span class="type">TagValueTraitKey</span>&lt;<span class="type">Int</span>&gt;&gt;&gt;, 
    <span class="type">ModifiedContent</span>&lt;<span class="type">Text</span>, <span class="type">_TraitWritingModifier</span>&lt;<span class="type">TagValueTraitKey</span>&lt;<span class="type">Int</span>&gt;&gt;&gt;
  )
&gt;
</code></pre><p>...which shouldn't surprise us too much, beside maybe some unfamiliar terms.<br>For context, here's our original <code>content</code>:</p><pre><code><span class="type">Text</span>(<span class="string">"First"</span>).<span class="call">tag</span>(<span class="number">0</span>)
<span class="type">Text</span>(<span class="string">"Second"</span>).<span class="call">tag</span>(<span class="number">1</span>)
<span class="type">Text</span>(<span class="string">"Third"</span>).<span class="call">tag</span>(<span class="number">2</span>)
</code></pre><p>We can see that:</p><ul><li><code>@ViewBuilder</code> took our three <code>Text</code>s and put them in a <code>TupleView</code> with three blocks.</li><li>each "block" is formed by a <code>ModifiedContent</code> instance, which is the result of applying a <code>tag(_:)</code> view modifier to each <code>Text</code>.</li></ul><p>These are already very good insights! Let's print the <code>content</code> instance next:</p><pre><code><span class="keyword">public struct</span> FSPicker&lt;SelectionValue: <span class="type">Hashable</span>, Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  ...
  <span class="keyword">public init</span>(...) { ... }

  <span class="keyword">public var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">let</span> contentMirrorValue = <span class="type">Mirror</span>(reflecting: content).<span class="call">descendant</span>(<span class="string">"value"</span>)!
    <span class="keyword">let</span> _ = <span class="call">print</span>(contentMirrorValue)
    <span class="type">EmptyView</span>()
  }
}
</code></pre><blockquote><p>We're force-unwrapping for brevity's sake: proper handling is left as a homework to the reader.</p></blockquote><p>This time the console shows:</p><pre><code>(
  <span class="type">ModifiedContent</span>(
    content: <span class="type">Text</span>(
      storage: <span class="type">Text</span>.<span class="type">Storage</span>.<span class="call">anyTextStorage</span>(
        <span class="type">LocalizedTextStorage</span>
      ), 
      modifiers: []
    ), 
    modifier: <span class="type">_TraitWritingModifier</span>(
      value: <span class="type">TagValueTraitKey</span>.<span class="type">Value</span>.<span class="call">tagged</span>(<span class="number">0</span>)
    )
  ), 
  <span class="type">ModifiedContent</span>(
    content: <span class="type">Text</span>(
      storage: <span class="type">Text</span>.<span class="type">Storage</span>.<span class="call">anyTextStorage</span>(
        <span class="type">LocalizedTextStorage</span>
      ), 
      modifiers: []
    ), 
    modifier: <span class="type">_TraitWritingModifier</span>(
      value: <span class="type">TagValueTraitKey</span>.<span class="type">Value</span>.<span class="call">tagged</span>(<span class="number">1</span>)
    )
  ), 
  <span class="type">ModifiedContent</span>(
    content: <span class="type">Text</span>(
      storage: <span class="type">Text</span>.<span class="type">Storage</span>.<span class="call">anyTextStorage</span>(
        <span class="type">LocalizedTextStorage</span>
      ), 
      modifiers: []
    ), 
    modifier: <span class="type">_TraitWritingModifier</span>(
      value: <span class="type">TagValueTraitKey</span>.<span class="type">Value</span>.<span class="call">tagged</span>(<span class="number">2</span>)
    )
  )
)
</code></pre><blockquote><p>Output formatted and slightly simplified for clarity's sake.</p></blockquote><p>...which also shouldn't surprise us too much: this is the same output as before, where instead of <code>TupleView</code>'s type we now see the actual value.</p><p>Note how everything we need is right there: all the <code>Text</code>s, and their associated <code>.tag</code> values.</p><p>Next we can use <code>Mirror</code> to navigate and pick each single <code>Text</code> and <code>tag</code> value separately:</p><pre><code><span class="keyword">public struct</span> FSPicker&lt;SelectionValue: <span class="type">Hashable</span>, Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  ...
  <span class="keyword">public init</span>(...) { ... }

  <span class="keyword">public var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">let</span> contentMirror = <span class="type">Mirror</span>(reflecting: content)
    <span class="comment">// 👇🏻 The number of `TupleView` blocks.</span>
    <span class="keyword">let</span> blocksCount = <span class="type">Mirror</span>(reflecting: contentMirror.<span class="call">descendant</span>(<span class="string">"value"</span>)!).<span class="property">children</span>.<span class="property">count</span>

    <span class="type">HStack</span> {
      <span class="type">ForEach</span>(<span class="number">0</span>..&lt;blocksCount) { index <span class="keyword">in</span>
        <span class="comment">// 👇🏻 A single `TupleView` block.</span>
        <span class="keyword">let</span> tupleBlock = contentMirror.<span class="call">descendant</span>(<span class="string">"value"</span>, <span class="string">".</span>\(index)<span class="string">"</span>)
        <span class="keyword">let</span> text: <span class="type">Text</span> = <span class="type">Mirror</span>(reflecting: tupleBlock!).<span class="call">descendant</span>(<span class="string">"content"</span>) <span class="keyword">as</span>! <span class="type">Text</span>
        <span class="keyword">let</span> tag: <span class="type">SelectionValue</span> = <span class="type">Mirror</span>(reflecting: tupleBlock!).<span class="call">descendant</span>(<span class="string">"modifier"</span>, <span class="string">"value"</span>, <span class="string">"tagged"</span>) <span class="keyword">as</span>! <span class="type">SelectionValue</span>

        ...
      }
    }
  }
}
</code></pre><p>At this point we have gained access to each original <code>Text</code> and <code>tag</code> value, which we can use to build our view:</p><pre><code><span class="keyword">struct</span> FSPicker&lt;SelectionValue: <span class="type">Hashable</span>, Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">@Namespace var</span> ns
  <span class="keyword">@Binding var</span> selection: <span class="type">SelectionValue</span>
  <span class="keyword">@ViewBuilder let</span> content: <span class="type">Content</span>

  <span class="keyword">public var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">let</span> contentMirror = <span class="type">Mirror</span>(reflecting: content)
    <span class="keyword">let</span> blocksCount = <span class="type">Mirror</span>(reflecting: contentMirror.<span class="call">descendant</span>(<span class="string">"value"</span>)!).<span class="property">children</span>.<span class="property">count</span> <span class="comment">// How many children?</span>
    <span class="type">HStack</span> {
      <span class="type">ForEach</span>(<span class="number">0</span>..&lt;blocksCount) { index <span class="keyword">in
        let</span> tupleBlock = contentMirror.<span class="call">descendant</span>(<span class="string">"value"</span>, <span class="string">".</span>\(index)<span class="string">"</span>)
        <span class="keyword">let</span> text = <span class="type">Mirror</span>(reflecting: tupleBlock!).<span class="call">descendant</span>(<span class="string">"content"</span>) <span class="keyword">as</span>! <span class="type">Text</span>
        <span class="keyword">let</span> tag = <span class="type">Mirror</span>(reflecting: tupleBlock!).<span class="call">descendant</span>(<span class="string">"modifier"</span>, <span class="string">"value"</span>, <span class="string">"tagged"</span>) <span class="keyword">as</span>! <span class="type">SelectionValue

        Button</span> {
          selection = tag
        } label: {
          text
            .<span class="call">padding</span>(.<span class="dotAccess">vertical</span>, <span class="number">16</span>)
        }
        .<span class="call">background</span>(
          <span class="type">Group</span> {
            <span class="keyword">if</span> tag == selection {
              <span class="type">Color</span>.<span class="property">purple</span>.<span class="call">frame</span>(height: <span class="number">2</span>)
                .<span class="call">matchedGeometryEffect</span>(id: <span class="string">"selector"</span>, in: ns)
            }
          },
          alignment: .<span class="dotAccess">bottom</span>
        )
        .<span class="call">accentColor</span>(tag == selection ? .<span class="dotAccess">purple</span> : .<span class="dotAccess">black</span>)
        .<span class="call">animation</span>(.<span class="dotAccess">easeInOut</span>)
      }
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/inspecting-views/custom.gif"/><p>Thanks to this definition <code>FSPicker</code> works with any <code>SelectionValue</code> and matches <code>Picker</code>'s APIs.</p><h3>Further improvements</h3><p>As it currently stands, <code>FSPicker</code> works great as long as the given content follows the same format as our example (a.k.a. a list of two or more <code>Text</code>s + <code>tag</code>s).</p><p>This could be actually what we wanted: instead of trying to support every possible SwiftUI component, we can consider other components as bad inputs and ignore them as long as it's clearly documented.</p><p>If we'd like to support more components (e.g. <code>Image</code>s), we could do so by expanding our inspection to also handle such elements, or even create our own view builder.</p><p>Of course, this is just the tip of the iceberg:<br>handling any <code>content</code> means handling more components, edge cases, multiple levels of <code>ModifiedContent</code>, and much, much more.</p><h2>Conclusions</h2><p>While Swift is known for being a <em>statically typed</em> language, it doesn't mean that we can only play with our build-time knowledge: thanks to <code>Mirror</code> we took another sneak peek into how dynamic both Swift and SwiftUI actually are.</p><p>Have you ever had to do this kind of inspection yourself? In which scenarios? <a href="https://twitter.com/zntfdr">Please let me know</a>!</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles.</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/codable-swift-dictionaries</guid><title>Encoding and decoding Swift dictionaries with custom key types</title><description>Sometimes even very well known APIs can still surprise us.</description><link>https://www.fivestars.blog/articles/codable-swift-dictionaries</link><pubDate>Tue, 16 Mar 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Pretty much every project out there nowadays uses <code>Codable</code>, introduced in 2017 with Swift 4.</p><p>Fast forward to 2021 and <code>Codable</code> can still surprise us:<br>in this article let's have a look at a little known behavior when it comes to dictionaries with custom key types.</p><blockquote><p>We will focus on json examples, the same can be applied to plists.</p></blockquote><h2>Dictionary 101</h2><p>Swift dictionaries are a generic collection of key-value elements, where the <code>Key</code> type needs to conform to <code>Hashable</code> (for performance reasons), and the <code>Value</code> type has no restrictions.</p><p>Here's an example with four key-value pairs:</p><pre><code><span class="keyword">let</span> dictionary: [<span class="type">String</span>: <span class="type">Int</span>] = [
  <span class="string">"f"</span>: <span class="number">1</span>,
  <span class="string">"i"</span>: <span class="number">2</span>,
  <span class="string">"v"</span>: <span class="number">3</span>,
  <span class="string">"e"</span>: <span class="number">4</span>
]
</code></pre><p>When it comes to json, the same dictionary above will have the following format:</p><pre><code>{
  <span class="string">"f"</span>: <span class="number">1</span>,
  <span class="string">"i"</span>: <span class="number">2</span>,
  <span class="string">"v"</span>: <span class="number">3</span>,
  <span class="string">"e"</span>: <span class="number">4</span>
}
</code></pre><p>Where the keys must be quoted.</p><h2>The surprise</h2><p>Let's imagine that we're building a small app where each model has different names in different languages, all to be displayed to the user at will.</p><p>A way to store all name variants is to introduce a new <code>Codable</code> <code>Language</code> type, to be used as the dictionary key for the name of our models:</p><pre><code><span class="keyword">enum</span> Language: <span class="type">String</span>, <span class="type">Codable</span> {
  <span class="keyword">case</span> english
  <span class="keyword">case</span> japanese
  <span class="keyword">case</span> thai
}
</code></pre><p>Then we'd use it this way:</p><pre><code><span class="keyword">let</span> names: [<span class="type">Language</span>: <span class="type">String</span>] = [
  .<span class="dotAccess">english</span>:  <span class="string">"Victory Monument"</span>,
  .<span class="dotAccess">japanese</span>: <span class="string">"戦勝記念塔"</span>,
  .<span class="dotAccess">thai</span>:     <span class="string">"อนุสาวรีย์ชัยสมรภูมิ"</span>
]
</code></pre><p>This is all great...until it's time to encode it to json:</p><pre><code><span class="keyword">let</span> encodedDictionary = <span class="keyword">try</span> <span class="type">JSONEncoder</span>().<span class="call">encode</span>(names)
<span class="comment">// ["english", "Victory Monument", "japanese", "戦勝記念塔", "thai", "อนุสาวรีย์ชัยสมรภูมิ"]</span>
</code></pre><p>That's...an array, not exactly a dictionary.</p><p>What about decoding it?</p><pre><code><span class="keyword">let</span> jsonData = <span class="string">"""
{
 "english":  "Victory Monument",
 "japanese": "戦勝記念塔",
 "thai":     "อนุสาวรีย์ชัยสมรภูมิ"
}
"""</span>.<span class="call">data</span>(using: .<span class="dotAccess">utf8</span>)!

<span class="keyword">let</span> decoded = <span class="keyword">try</span> <span class="type">JSONDecoder</span>().<span class="call">decode</span>([<span class="type">Language</span>: <span class="type">String</span>].<span class="keyword">self</span>, from: jsonData)
<span class="comment">// typeMismatch(
//   Swift.Array&lt;Any&gt;, 
//   Swift.DecodingError.Context(
//     codingPath: [],
//     debugDescription: "Expected to decode Array&lt;Any&gt; but found a dictionary instead.", 
//     underlyingError: nil
//    )
//  )</span>
</code></pre><p>The decoding fails: despite trying to decode into a dictionary (<code>[Language: String]</code>), Swift was expecting to decode an array.</p><p>If we replace the key type <code>Language</code> with <code>String</code>, then everything works as expected: why is that?</p><p>After some digging it turns out that <a href="https://github.com/apple/swift/blob/d2085d8b0ed69e40a10e555669bb6cc9b450d0b3/stdlib/public/core/Codable.swift.gyb#L1967">this behavior is actually expected</a>:<br><br>- as a <code>Codable</code> type can encode into anything (including another dictionary), Swift will encode Swift dictionaries into json/plist dictionaries only when the <code>Key</code> type is <code>String</code> or <code>Int</code>.</p><ul><li>all other Swift dictionaries with non-<code>String</code> or non-<code>Int</code> <code>Key</code> types will be encoded into an array of alternating keys and values.</li></ul><p>This explain both the error in the decoding above and the "unexpected" encoding.</p><h2>Four solutions</h2><p>At this point we know that Swift dictionaries will encode into/decode from json dictionaries only when its key is either a <code>String</code> or a <code>Int</code>, how can we overcome this in our example? Let's see a few solutions.</p><h3>Bend to Swift's way</h3><p>The first is to give up on the json dictionary representation of our model, and embrace the expected <em>alternating keys and values</em> array:</p><pre><code><span class="comment">// Encoding</span>
<span class="keyword">let</span> names: [<span class="type">Language</span>: <span class="type">String</span>] = [
  .<span class="dotAccess">english</span>:  <span class="string">"Victory Monument"</span>,
  .<span class="dotAccess">japanese</span>: <span class="string">"戦勝記念塔"</span>,
  .<span class="dotAccess">thai</span>:     <span class="string">"อนุสาวรีย์ชัยสมรภูมิ"</span>
]

<span class="keyword">let</span> encoded = <span class="keyword">try</span> <span class="type">JSONEncoder</span>().<span class="call">encode</span>(names)
<span class="comment">// ["english", "Victory Monument", "japanese", "戦勝記念塔", "thai", "อนุสาวรีย์ชัยสมรภูมิ"]

// Decoding</span>
<span class="keyword">let</span> jsonData = <span class="string">"""
[
 "english",  "Victory Monument",
 "japanese", "戦勝記念塔",
 "thai",     "อนุสาวรีย์ชัยสมรภูมิ"
]
"""</span>.<span class="call">data</span>(using: .<span class="dotAccess">utf8</span>)!

<span class="keyword">let</span> decoded = <span class="keyword">try</span> <span class="type">JSONDecoder</span>().<span class="call">decode</span>([<span class="type">Language</span>: <span class="type">String</span>].<span class="keyword">self</span>, from: jsonData)
<span class="comment">// [
//   .english: "Victory Monument",
//   .japanese: "戦勝記念塔", 
//   .thai: "อนุสาวรีย์ชัยสมรภูมิ"
// ]</span>
</code></pre><p>This works great when it's the app that both stores and reads the data:<br>at this point we do not really care how the data is stored, Swift will take care of both the encoding and decoding for us.</p><h3>Use Int/String</h3><p>While the above solution works great when the data is used only locally, for probably most uses the json object will come from a server, meaning that we will receive a json dictionary.</p><p>The easiest way to patch this without changing the expected json structure is to use <code>String</code> or <code>Int</code> instead of our custom type:</p><pre><code><span class="comment">// Encoding</span>
<span class="keyword">let</span> names: [<span class="type">String</span>: <span class="type">String</span>] = [
  <span class="string">"english"</span>:  <span class="string">"Victory Monument"</span>,
  <span class="string">"japanese"</span>: <span class="string">"戦勝記念塔"</span>,
  <span class="string">"thai"</span>:     <span class="string">"อนุสาวรีย์ชัยสมรภูมิ"</span>
]
<span class="keyword">let</span> encodedDictionary = <span class="keyword">try</span> <span class="type">JSONEncoder</span>().<span class="call">encode</span>(names)
<span class="comment">// {
//   "english":  "Victory Monument",
//   "japanese": "戦勝記念塔",
//   "thai":     "อนุสาวรีย์ชัยสมรภูมิ"
// }

// Decoding</span>
<span class="keyword">let</span> jsonData = <span class="string">"""
{
 "english":  "Victory Monument",
 "japanese": "戦勝記念塔",
 "thai":     "อนุสาวรีย์ชัยสมรภูมิ"
}
"""</span>.<span class="call">data</span>(using: .<span class="dotAccess">utf8</span>)!
<span class="keyword">let</span> decoded = <span class="keyword">try</span> <span class="type">JSONDecoder</span>().<span class="call">decode</span>([<span class="type">String</span>: <span class="type">String</span>].<span class="keyword">self</span>, from: jsonData)
<span class="comment">// [
//   "english":  "Victory Monument",
//   "japanese": "戦勝記念塔",
//   "thai":     "อนุสาวรีย์ชัยสมรภูมิ"
// ]</span>
</code></pre><p>However this would mean:</p><ul><li>giving up on our clear declaration/expectation, a.k.a. the list of possible cases in the <code>Language</code> example</li><li>introducing the possibility of invalid/unexpected keys (both from and to the server).</li></ul><p>So far neither solution is ideal in the general case, let's see what we can do next.</p><h3>Custom encoding/Decoding</h3><p>There's no way around it:<br>if we want to encode-into/decode-from a json dictionary, we will have to go through a Swift dictionary with either <code>String</code> or <code>Int</code> as its <code>Key</code> type.</p><p>With this being said, we could use such key types just for encoding/decoding, but then store it in Swift using our custom type.</p><p>Let's create a new wrapper around our dictionary, <em>lazily</em> named <code>DictionaryWrapper</code>, which will:</p><ul><li>turn our keys into <code>String</code>s before encoding them</li><li>decode a <code>[String: String]</code> dictionary and then turn into a <code>[Language: String]</code> dictionary</li></ul><pre><code><span class="keyword">public struct</span> DictionaryWrapper: <span class="type">Codable</span> {
  <span class="keyword">var</span> dictionary: [<span class="type">Language</span>: <span class="type">String</span>]

  <span class="keyword">init</span>(dictionary: [<span class="type">Language</span>: <span class="type">String</span>]) {
    <span class="keyword">self</span>.<span class="property">dictionary</span> = dictionary
  }

  <span class="keyword">public init</span>(from decoder: <span class="type">Decoder</span>) <span class="keyword">throws</span> {
    <span class="keyword">let</span> container = <span class="keyword">try</span> decoder.<span class="call">singleValueContainer</span>()
    <span class="keyword">let</span> stringDictionary = <span class="keyword">try</span> container.<span class="call">decode</span>([<span class="type">String</span>: <span class="type">String</span>].<span class="keyword">self</span>)

    dictionary = [:]
    <span class="keyword">for</span> (stringKey, value) <span class="keyword">in</span> stringDictionary {
      <span class="keyword">guard let</span> key = <span class="type">Language</span>(rawValue: stringKey) <span class="keyword">else</span> {
        <span class="keyword">throw</span> <span class="type">DecodingError</span>.<span class="call">dataCorruptedError</span>(
          in: container,
          debugDescription: <span class="string">"Invalid key '</span>\(stringKey)<span class="string">'"</span>
        )
      }
      dictionary[key] = value
    }
  }

  <span class="keyword">public func</span> encode(to encoder: <span class="type">Encoder</span>) <span class="keyword">throws</span> {
    <span class="keyword">let</span> stringDictionary: [<span class="type">String</span>: <span class="type">String</span>] = <span class="type">Dictionary</span>(
      uniqueKeysWithValues: dictionary.<span class="call">map</span> { ($0.<span class="property">rawValue</span>, $1) }
    )
    <span class="keyword">var</span> container = encoder.<span class="call">singleValueContainer</span>()
    <span class="keyword">try</span> container.<span class="call">encode</span>(stringDictionary)
  }
}
</code></pre><p>Thanks to this definition:</p><ul><li>we can no longer encode/decode invalid keys</li><li>match our original expectations</li></ul><p>We can now go back to the original example and see that this new definition will work as expected:</p><pre><code><span class="comment">// Encoding</span>
<span class="keyword">let</span> names = <span class="type">DictionaryWrapper</span>(
  dictionary: [
    .<span class="dotAccess">english</span>:  <span class="string">"Victory Monument"</span>,
    .<span class="dotAccess">japanese</span>: <span class="string">"戦勝記念塔"</span>,
    .<span class="dotAccess">thai</span>:     <span class="string">"อนุสาวรีย์ชัยสมรภูมิ"</span>
  ]
)
<span class="keyword">let</span> encodedDictionary = <span class="keyword">try</span> <span class="type">JSONEncoder</span>().<span class="call">encode</span>(names)
<span class="comment">// {
//   "english":  "Victory Monument",
//   "japanese": "戦勝記念塔",
//   "thai":     "อนุสาวรีย์ชัยสมรภูมิ"
// }

// Decoding</span>
<span class="keyword">let</span> jsonData = <span class="string">"""
{
 "english":  "Victory Monument",
 "japanese": "戦勝記念塔",
 "thai":     "อนุสาวรีย์ชัยสมรภูมิ"
}
"""</span>.<span class="call">data</span>(using: .<span class="dotAccess">utf8</span>)!
<span class="keyword">let</span> decoded = <span class="keyword">try</span> <span class="type">JSONDecoder</span>().<span class="call">decode</span>(<span class="type">DictionaryWrapper</span>.<span class="keyword">self</span>, from: jsonData)
<span class="comment">// [
//   .english: "Victory Monument",
//   .japanese: "戦勝記念塔", 
//   .thai: "อนุสาวรีย์ชัยสมรภูมิ"
// ]</span>
</code></pre><h3>RawRapresentable</h3><p>The last solution worked perfectly, however it was hard coded for a specific dictionary type. A way to expand it to the more general case is to use Swift's <a href="https://developer.apple.com/documentation/swift/rawrepresentable"><code>RawRepresentable</code></a> protocol, which represents a type that can be converted to and from an associated raw value:</p><pre><code><span class="keyword">public protocol</span> RawRepresentable {
  <span class="comment">/// The raw type that can be used to represent all values of the conforming type.</span>
  <span class="keyword">associatedtype</span> RawValue

  <span class="comment">/// Creates a new instance with the specified raw value.</span>
  <span class="keyword">init</span>?(rawValue: <span class="type">Self</span>.<span class="type">RawValue</span>)

  <span class="comment">/// The corresponding value of the raw type.</span>
  <span class="keyword">var</span> rawValue: <span class="type">Self</span>.<span class="type">RawValue</span> { <span class="keyword">get</span> }
}
</code></pre><p>We can define a generic <code>DictionaryWrapper</code> where we require the dictionary <code>Key</code> to conform to <code>RawRepresentable</code>, and where we will use its associated <code>RawValue</code> type for the dictionary encoding/decoding:</p><pre><code><span class="keyword">public struct</span> DictionaryWrapper&lt;Key: <span class="type">Hashable</span> &amp; <span class="type">RawRepresentable</span>, Value: <span class="type">Codable</span>&gt;: <span class="type">Codable</span> <span class="keyword">where</span> <span class="type">Key</span>.<span class="type">RawValue</span>: <span class="type">Codable</span> &amp; <span class="type">Hashable</span> {
  <span class="keyword">public var</span> dictionary: [<span class="type">Key</span>: <span class="type">Value</span>]

  <span class="keyword">public init</span>(dictionary: [<span class="type">Key</span>: <span class="type">Value</span>]) {
    <span class="keyword">self</span>.<span class="property">dictionary</span> = dictionary
  }

  <span class="keyword">public init</span>(from decoder: <span class="type">Decoder</span>) <span class="keyword">throws</span> {
    <span class="keyword">let</span> container = <span class="keyword">try</span> decoder.<span class="call">singleValueContainer</span>()
    <span class="keyword">let</span> rawKeyedDictionary = <span class="keyword">try</span> container.<span class="call">decode</span>([<span class="type">Key</span>.<span class="type">RawValue</span>: <span class="type">Value</span>].<span class="keyword">self</span>)

    dictionary = [:]
    <span class="keyword">for</span> (rawKey, value) <span class="keyword">in</span> rawKeyedDictionary {
      <span class="keyword">guard let</span> key = <span class="type">Key</span>(rawValue: rawKey) <span class="keyword">else</span> {
        <span class="keyword">throw</span> <span class="type">DecodingError</span>.<span class="call">dataCorruptedError</span>(
          in: container,
          debugDescription: <span class="string">"Invalid key: cannot initialize '</span>\(<span class="type">Key</span>.<span class="keyword">self</span>)<span class="string">' from invalid '</span>\(<span class="type">Key</span>.<span class="type">RawValue</span>.<span class="keyword">self</span>)<span class="string">' value '</span>\(rawKey)<span class="string">'"</span>)
      }
      dictionary[key] = value
    }
  }

  <span class="keyword">public func</span> encode(to encoder: <span class="type">Encoder</span>) <span class="keyword">throws</span> {
    <span class="keyword">let</span> rawKeyedDictionary = <span class="type">Dictionary</span>(uniqueKeysWithValues: dictionary.<span class="call">map</span> { ($0.<span class="property">rawValue</span>, $1) })
    <span class="keyword">var</span> container = encoder.<span class="call">singleValueContainer</span>()
    <span class="keyword">try</span> container.<span class="call">encode</span>(rawKeyedDictionary)
  }
}
</code></pre><blockquote><p>I couldn't find a way to restrict this exclusively for <code>Key.RawValue == String</code> or <code>Key.RawValue == Int</code>, mainly because of Swift's ban on <a href="https://github.com/apple/swift-evolution/blob/5752a43ba5ddd6c53a5fd48d1551aa89864df827/proposals/0143-conditional-conformances.md#overlapping-conformances">overlapping conformances</a>, if you find a way, please <a href="https://twitter.com/zntfdr">let me know</a>.</p></blockquote><p>Thanks to this new definition <code>DictionaryWrapper</code> will encode any Swift dictionary into a json/plist dictionary as long as the key used has an associated <code>RawValue</code> of type <code>String</code> or <code>Int</code>.</p><h3>Bonus: a property wrapper!</h3><blockquote><p>Credits to <a href="https://jarrodldavis.com">Jarrod Davis</a> for <a href="https://gist.github.com/jarrodldavis/8c9e7e6e487991279c2df2d452baaf16">this solution</a>.</p></blockquote><p>Most likely our dictionary will be part of a model that needs to be encoded/decoded. Instead of adding <code>DictionaryWrapper</code> as part of each property type definition, we can also update <code>DictionaryWrapper</code> definition by:</p><ul><li>adding the <code>@propertyWrapper</code> attribute to our <code>DictionaryWrapper</code> declaration</li><li>replacing the internal <code>dictionary</code> property name with <code>wrappedValue</code></li></ul><p>That's all it's needed to make <code>DictionaryWrapper</code> a property wrapper:</p><pre><code><span class="keyword">@propertyWrapper
public struct</span> DictionaryWrapper&lt;Key: <span class="type">Hashable</span> &amp; <span class="type">RawRepresentable</span>, Value: <span class="type">Codable</span>&gt;: <span class="type">Codable</span> <span class="keyword">where</span> <span class="type">Key</span>.<span class="type">RawValue</span>: <span class="type">Codable</span> &amp; <span class="type">Hashable</span> {
  <span class="keyword">public var</span> wrappedValue: [<span class="type">Key</span>: <span class="type">Value</span>]

  <span class="keyword">public init</span>() {
    wrappedValue = [:]
  }

  <span class="keyword">public init</span>(wrappedValue: [<span class="type">Key</span>: <span class="type">Value</span>]) {
    <span class="keyword">self</span>.<span class="property">wrappedValue</span> = wrappedValue
  }

  <span class="keyword">public init</span>(from decoder: <span class="type">Decoder</span>) <span class="keyword">throws</span> {
    <span class="keyword">let</span> container = <span class="keyword">try</span> decoder.<span class="call">singleValueContainer</span>()
    <span class="keyword">let</span> rawKeyedDictionary = <span class="keyword">try</span> container.<span class="call">decode</span>([<span class="type">Key</span>.<span class="type">RawValue</span>: <span class="type">Value</span>].<span class="keyword">self</span>)

    wrappedValue = [:]
    <span class="keyword">for</span> (rawKey, value) <span class="keyword">in</span> rawKeyedDictionary {
      <span class="keyword">guard let</span> key = <span class="type">Key</span>(rawValue: rawKey) <span class="keyword">else</span> {
        <span class="keyword">throw</span> <span class="type">DecodingError</span>.<span class="call">dataCorruptedError</span>(
          in: container,
          debugDescription: <span class="string">"Invalid key: cannot initialize '</span>\(<span class="type">Key</span>.<span class="keyword">self</span>)<span class="string">' from invalid '</span>\(<span class="type">Key</span>.<span class="type">RawValue</span>.<span class="keyword">self</span>)<span class="string">' value '</span>\(rawKey)<span class="string">'"</span>)
      }
      wrappedValue[key] = value
    }
  }

  <span class="keyword">public func</span> encode(to encoder: <span class="type">Encoder</span>) <span class="keyword">throws</span> {
    <span class="keyword">let</span> rawKeyedDictionary = <span class="type">Dictionary</span>(uniqueKeysWithValues: wrappedValue.<span class="call">map</span> { ($0.<span class="property">rawValue</span>, $1) })
    <span class="keyword">var</span> container = encoder.<span class="call">singleValueContainer</span>()
    <span class="keyword">try</span> container.<span class="call">encode</span>(rawKeyedDictionary)
  }
}
</code></pre><p>With this change, we can go on and declare any model with this new property wrapper:</p><pre><code><span class="keyword">struct</span> FSModel: <span class="type">Codable</span> {
  <span class="keyword">@DictionaryWrapper var</span> names: [<span class="type">Language</span>: <span class="type">String</span>]
  ...
}
</code></pre><p>...and then access directly to the dictionary without having to go thorough <code>DictionaryWrapper</code>:</p><pre><code><span class="keyword">let</span> jsonData = <span class="string">"""
{
 "names": {
   "english": "Victory Monument",
   "japanese": "戦勝記念塔",
   "thai":     "อนุสาวรีย์ชัยสมรภูมิ"
  },
  ...
}
"""</span>.<span class="call">data</span>(using: .<span class="dotAccess">utf8</span>)!

<span class="keyword">let</span> model = <span class="keyword">try</span> <span class="type">JSONDecoder</span>().<span class="call">decode</span>(<span class="type">FSModel</span>.<span class="keyword">self</span>, from: jsonData)
model.<span class="property">names</span> <span class="comment">// [Language: String]
// [
//   .english: "Victory Monument",
//   .japanese: "戦勝記念塔", 
//   .thai: "อนุสาวรีย์ชัยสมรภูมิ"
// ]</span>
</code></pre><h2>Conclusions</h2><p>The introduction of <code>Codable</code> in Swift was a blessing: despite having a few shortcoming here and there, it's also flexible enough for us to extend it and make it work for our needs.</p><p>What challenges have you faced while working with <code>Codable</code>? How did you solve it? Please <a href="https://twitter.com/zntfdr">let me know</a>!</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p><h2>Further reading</h2><ul><li><a href="https://bugs.swift.org/browse/SR-7788">(SR-7788) Enum with String or Int RawRepresentable not encoded / decoded properly</a></li><li><a href="https://forums.swift.org/t/json-encoding-decoding-weird-encoding-of-dictionary-with-enum-values/12995/5">(Swift forums) JSON Encoding / Decoding weird encoding of dictionary with enum values</a></li><li><a href="https://forums.swift.org/t/decoding-a-dictionary-with-a-custom-type-not-string-as-key/35290/3">(Swift forums) Decoding a dictionary with a custom type (not String) as key</a></li><li><a href="https://forums.swift.org/t/bug-or-pebkac/33796">(Swift forums) Bug or PEBKAC?</a></li></ul>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/app-state</guid><title>App-wide state in SwiftUI</title><description>An introduction to a common app architecture used in SwiftUI, and how to avoid one of its most common pitfalls.</description><link>https://www.fivestars.blog/articles/app-state</link><pubDate>Tue, 9 Mar 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>With the adoption of SwiftUI, a new trend of creating an app-wide state has been gaining momentum. This is very justified, as it's one of the few ways to properly handle deep linking, <a href="https://www.fivestars.blog/articles/swiftui-hud/">HUDs</a>, and more.</p><p>In this article, let's have a look at this approach, and how to avoid one of its most common pitfalls.</p><h2>The app</h2><p>We will build a small app with just two tabs: a home and settings tab.</p><blockquote><p>While we will use a <code>TabView</code> as an example, the same approach can be used for navigation, sheets, all other SwiftUI view presentations, and much more.</p></blockquote><img src="https://www.fivestars.blog/assets/posts/app-state/simple.gif"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
   <span class="type">TabView</span> {
      <span class="type">HomeView</span>()
        .<span class="call">tabItem</span> { <span class="type">Label</span>(<span class="string">"Home"</span>, systemImage: <span class="string">"house.fill"</span>) }

      <span class="type">SettingsView</span>()
        .<span class="call">tabItem</span> { <span class="type">Label</span>(<span class="string">"Settings"</span>, systemImage: <span class="string">"gear"</span>) }
    }
  }
}

<span class="keyword">struct</span> HomeView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Text</span>(<span class="string">"Home"</span>)
  }
}

<span class="keyword">struct</span> SettingsView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Text</span>(<span class="string">"Settings"</span>)
  }
}
</code></pre><p>Here we have declared a <code>TabView</code> with our two views, <code>HomeView</code> and <code>SettingsView</code>, each with an associated <code>Text</code>.</p><h2>Controlling the selected tab</h2><p>Our app has the requirement to select the active tab programmatically: for example we'd like to have a shortcut from Home allowing the user to jump into the settings tab, or perhaps we'd like to switch tabs as a response of a deep link, etc.</p><p>We will need to manage the <code>TabView</code> state ourselves, which we can do via the <code>TabView</code> initializer offering a <code>Binding</code> parameter (we covered this and more in <a href="https://www.fivestars.blog/articles/swiftui-patterns-bindings/">SwiftUI patterns: @Bindings</a>):</p><img src="https://www.fivestars.blog/assets/posts/app-state/goto.gif"/><pre><code><span class="keyword">enum</span> Tab: <span class="type">Hashable</span> {
  <span class="keyword">case</span> home
  <span class="keyword">case</span> settings
}

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> selectedTab: <span class="type">Tab</span> = .<span class="dotAccess">home</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TabView</span>(selection: $selectedTab) {
      <span class="type">HomeView</span>(selectedTab: $selectedTab)
        .<span class="call">tabItem</span> { <span class="type">Label</span>(<span class="string">"Home"</span>, systemImage: <span class="string">"house.fill"</span>) }
        .<span class="call">tag</span>(<span class="type">Tab</span>.<span class="property">home</span>)

      <span class="type">SettingsView</span>()
        .<span class="call">tabItem</span> { <span class="type">Label</span>(<span class="string">"Settings"</span>, systemImage: <span class="string">"gear"</span>) }
        .<span class="call">tag</span>(<span class="type">Tab</span>.<span class="property">settings</span>)
    }
  }
}

<span class="keyword">struct</span> HomeView: <span class="type">View</span> {
  <span class="keyword">@Binding var</span> selectedTab: <span class="type">Tab</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">Button</span>(<span class="string">"go to settings"</span>) {
        selectedTab = .<span class="dotAccess">settings</span>
      }
      <span class="type">Text</span>(<span class="string">"Home"</span>)
        .<span class="call">onAppear</span>(perform: { <span class="call">print</span>(<span class="string">"home on appear"</span>)})
    }
  }
}

<span class="keyword">struct</span> SettingsView: <span class="type">View</span> {
  ... <span class="comment">// same as before</span>
}
</code></pre><p>In this update we have..</p><ul><li>..declared a <code>Tab</code> enum with all possible <code>TabView</code> states</li><li>..added a new <code>ContentView</code> <code>selectedTab</code> property, controlling the <code>TabView</code> state</li><li>..added a button in Home that will let us jump to the settings tab</li></ul><p>While this works great and it lets us change the tab state programmatically, we still need to have direct access to <code>ContentView</code>'s <code>selectedTab</code> property before we can change the tab bar state.</p><p>More importantly, as the app grows, it won't be practical to pass the tab bar state from view to view: this is where we move the state out of any view, and put it in the environment via our app-wide state.</p><h2>App-wide state</h2><p>The current limitation is the <code>TabView</code> state accessibility, we can overcome this challenge by creating a global state and set it in the environment:</p><pre><code><span class="keyword">class</span> AppWideState: <span class="type">ObservableObject</span> {
  <span class="keyword">@Published var</span> selectedTab: <span class="type">Tab</span> = .<span class="dotAccess">home</span>
}
</code></pre><p><code>AppWideState</code> holds just the tab state and sends a new publish event every time <code>selectedTab</code> is about to change.</p><p>We want this state to be accessible from anywhere, we will attach it to our main <code>App</code> (or scene delegate) and then inject it in the environment:</p><pre><code><span class="keyword">@main
struct</span> FiveStarsApp: <span class="type">App</span> {
  <span class="keyword">@StateObject var</span> appWideState = <span class="type">AppWideState</span>()

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">Scene</span> {
    <span class="type">WindowGroup</span> {
      <span class="type">ContentView</span>()
        .<span class="call">environmentObject</span>(appWideState) <span class="comment">// injected in the environment</span>
    }
  }
}
</code></pre><p>Everything is ready, let's update our views to take advantage of this new state:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@EnvironmentObject var</span> state: <span class="type">AppWideState</span> <span class="comment">// environment object</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TabView</span>(selection: $state.<span class="property">selectedTab</span>) { <span class="comment">// state from the environment object</span>
      <span class="type">HomeView</span>()
        .<span class="call">tabItem</span> { <span class="type">Label</span>(<span class="string">"Home"</span>, systemImage: <span class="string">"house.fill"</span>) }
        .<span class="call">tag</span>(<span class="type">Tab</span>.<span class="property">home</span>)

      <span class="type">SettingsView</span>()
        .<span class="call">tabItem</span> { <span class="type">Label</span>(<span class="string">"Settings"</span>, systemImage: <span class="string">"gear"</span>) }
        .<span class="call">tag</span>(<span class="type">Tab</span>.<span class="property">settings</span>)
    }
  }
}

<span class="keyword">struct</span> HomeView: <span class="type">View</span> {
  <span class="keyword">@EnvironmentObject var</span> state: <span class="type">AppWideState</span> <span class="comment">// environment object</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">Button</span>(<span class="string">"go to settings"</span>) {
        state.<span class="property">selectedTab</span> = .<span class="dotAccess">settings</span> <span class="comment">// sets the state from the environment object</span>
      }
      <span class="type">Text</span>(<span class="string">"Home"</span>)
    }
  }
}

<span class="keyword">struct</span> SettingsView: <span class="type">View</span> {
  ... <span class="comment">// same as before</span>
}
</code></pre><blockquote><p><code>HomeView</code> could have still used a <code>@Binding</code>, however imagine that this view was a few levels deep.</p></blockquote><p>This new structure works exactly as before, however we can now change the selected tab from the main <code>App</code> and anywhere else the environment reaches:<br>we're very happy with this solution and we move on to the build other features.</p><h2>A few weeks later (a.k.a. The pitfall)</h2><p>A few weeks pass, our <code>AppWideState</code> has gained some new <code>@Published</code> properties, more views observe this object...and our app starts to slow down: every time we change the app-wide state we notice a slight lag before changing tabs or a new navigation is pushed, etc.</p><p>What's happening?</p><p>We can investigate this mystery with just our example. Let's add a small side effect in our <code>HomeView</code>, a <code>print</code> statement telling us when the <code>HomeView</code> body is executed:</p><pre><code><span class="keyword">struct</span> HomeView: <span class="type">View</span> {
  <span class="keyword">@EnvironmentObject var</span> state: <span class="type">AppWideState</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">let</span> _ = <span class="call">print</span>(<span class="string">"HomeView body"</span>) <span class="comment">// side effect</span>
    <span class="type">VStack</span> {
      <span class="type">Button</span>(<span class="string">"go to settings"</span>) {
        state.<span class="property">selectedTab</span> = .<span class="dotAccess">settings</span>
      }
      <span class="type">Text</span>(<span class="string">"Home"</span>)
    }
  }
}
</code></pre><blockquote><p><em>"Coincidentally"</em> we covered this kind of side effects in <a href="https://www.fivestars.blog/articles/embracing-viewbuilder/">Quick tips on embracing @ViewBuilder</a>, alternatively we can use breakpoints as well.</p></blockquote><p>With this small change, we can run the app again and we will notice that every time we change tabs (by tapping the tab bar or via the <code>go to settings</code> button) the <code>HomeView</code> <code>body</code> is recomputed: this is true despite <code>HomeView</code> not actually reading the state but just setting it.</p><img src="https://www.fivestars.blog/assets/posts/app-state/print.gif"/><p>If we remove the button action, thus not doing anything at all with the <code>@EnvironmentObject</code> state, this will still happen:</p><pre><code><span class="keyword">struct</span> HomeView: <span class="type">View</span> {
  <span class="keyword">@EnvironmentObject var</span> state: <span class="type">AppWideState</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">let</span> _ = <span class="call">print</span>(<span class="string">"HomeView body"</span>) <span class="comment">// side effect</span>
    <span class="type">VStack</span> {
      <span class="type">Button</span>(<span class="string">"go to settings"</span>) {
        <span class="comment">// does nothing</span>
      }
      <span class="type">Text</span>(<span class="string">"Home"</span>)
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/app-state/print.gif"/><p>Let's now imagine that:</p><ul><li>we have a navigation stack (or multiple navigation stacks)</li><li>some of the screens in the stack(s) use our <code>AppWideState</code> environment object</li><li>the user is multiple levels deep into the stack(s)</li></ul><p>Every change we make to <code>AppWideState</code> will trigger a new <code>body</code> evaluation for <strong>all</strong> views observing <code>AppWideState</code> that are part of our stack(s), not just the last view the user is currently viewing.</p><p>It's now easy to tell why the app is getting slower and slower: the more we use and expand <code>AppWideState</code>, the more views will have their <code>body</code> re-evaluated at every change.</p><p>This pitfall is actually to be expected, as <code>EnvironmentObject</code> is just another <code>ObservableObject</code> instance that our views subscribe to, SwiftUI is doing just what it has always promised to be doing: automatically subscribing and reacting to state changes.</p><h2>The solution</h2><p>While most views will probably need to reach for <code>AppWideState</code> to set it, very few will actually need to observe its state: taking the tab bar state as an example, only <code>TabView</code> needs to observe it, all other views only need to change it.</p><p>A way to make this happen is to create a container of app-wide states, where the container itself doesn't publish anything:</p><pre><code><span class="keyword">class</span> AppStateContainer: <span class="type">ObservableObject</span> {
  ...
}
</code></pre><p>Instead of declaring each state as a <code>@Published</code> property of this container, each state will be nested into its own <code>ObservableObject</code>, which then will be part of the container:</p><pre><code><span class="keyword">class</span> TabViewState: <span class="type">ObservableObject</span> {
  <span class="keyword">@Published var</span> selectedTab: <span class="type">Tab</span> = .<span class="dotAccess">home</span>
}

<span class="keyword">class</span> AppStateContainer: <span class="type">ObservableObject</span> {
  <span class="keyword">var</span> tabViewState = <span class="type">TabViewState</span>()
}
</code></pre><p>The container conforms to <code>ObservableObject</code> because it's a requirement for environment objects, however it doesn't publish anything, meanwhile we moved the <code>selectedTab</code> state to its own <code>TabViewState</code>.</p><p>This approach also isolates each <em>app-wise</em> state into its own "mini-container" (<code>TabViewState</code> above), therefore we can focus all the logic (if any) for each specific state to its own class, instead of having it all shared in one big class.</p><p>With this in place, we now can set both <code>AppStateContainer</code> and <code>TabViewState</code> into the environment:</p><pre><code><span class="keyword">@main
struct</span> FiveStarsApp: <span class="type">App</span> {
  <span class="keyword">@StateObject var</span> appStateContainer = <span class="type">AppStateContainer</span>()

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">Scene</span> {
    <span class="type">WindowGroup</span> {
      <span class="type">ContentView</span>()
        .<span class="call">environmentObject</span>(appStateContainer)
        .<span class="call">environmentObject</span>(appStateContainer.<span class="property">tabViewState</span>)
    }
  }
}
</code></pre><p>Views that need to observe the state change will directly observe <code>TabViewState</code>, while views that only need to change its state will reach for the container:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@EnvironmentObject var</span> state: <span class="type">TabViewState</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TabView</span>(selection: $state.<span class="property">selectedTab</span>) {
      <span class="type">HomeView</span>()
        .<span class="call">tabItem</span> {
          <span class="type">Label</span>(<span class="string">"Home"</span>, systemImage: <span class="string">"house.fill"</span>)
        }
        .<span class="call">tag</span>(<span class="type">Tab</span>.<span class="property">home</span>)

      <span class="type">SettingsView</span>()
        .<span class="call">tabItem</span> {
          <span class="type">Label</span>(<span class="string">"Settings"</span>, systemImage: <span class="string">"gear"</span>)
        }
        .<span class="call">tag</span>(<span class="type">Tab</span>.<span class="property">settings</span>)
    }
  }
}

<span class="keyword">struct</span> HomeView: <span class="type">View</span> {
  <span class="keyword">@EnvironmentObject var</span> container: <span class="type">AppStateContainer</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">let</span> _ = <span class="call">print</span>(<span class="string">"Home body"</span>)
    <span class="type">VStack</span> {
      <span class="type">Button</span>(<span class="string">"go to settings"</span>) {
        container.<span class="property">tabViewState</span>.<span class="property">selectedTab</span> = .<span class="dotAccess">settings</span>
      }
      <span class="type">Text</span>(<span class="string">"Home"</span>)
        .<span class="call">onAppear</span>(perform: { <span class="call">print</span>(<span class="string">"home on appear"</span>)})
    }
  }
}
</code></pre><p>And with this change we have our app performance back, no extra view <code>body</code> unnecessarily computed.</p><p>In this example we have <code>HomeView</code> directly reach for and change the <code>TabView</code> state itself, however we could also add some convenience API on top of our container to make this more straightforward (similarly to what we did in <a href="https://www.fivestars.blog/articles/swiftui-hud/">Custom HUDs in SwiftUI</a>).</p><p>The final gist can be found <a href="https://github.com/zntfdr/FiveStarsCodeSamples/tree/main/App-State-In-SwiftUI">here</a>.</p><h2>Conclusions</h2><p>Along with SwiftUI we have new paradigms, while it's very good to explore and experiment with new architectures, we always need to be aware of "hidden" pitfalls that we might encounter.</p><p>Do you use a "global" state in your apps? What approach do you use? Please <a href="https://twitter.com/zntfdr">let me know</a>!</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/embracing-viewbuilder</guid><title>Quick tips on embracing @ViewBuilder</title><description>With Swift 5.3, @ViewBuilder is now more powerful than ever! Let's see how we can take advantage of the latest new features.</description><link>https://www.fivestars.blog/articles/embracing-viewbuilder</link><pubDate>Tue, 2 Mar 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>With the release of Xcode 12 and Swift 5.3, <a href="https://docs.swift.org/swift-book/LanguageGuide/AdvancedOperators.html#ID630"><code>@resultBuilder</code></a>, at the time called <code>@_functionBuilder</code>, has learned quite a few tricks:</p><ul><li><a href="https://github.com/apple/swift/pull/30045">Support for <code>if let</code> &amp; <code>if case</code></a></li><li><a href="https://github.com/apple/swift/pull/29409">Support for multiple Boolean conditions in 'if' statements</a></li><li><a href="https://github.com/apple/swift/pull/30174"><code>switch</code> support</a></li><li><a href="https://github.com/apple/swift/pull/29419"><code>if #available</code> support</a></li><li><a href="https://github.com/apple/swift/pull/28606"><code>#warning</code> and <code>#error</code> handling</a></li><li><a href="https://github.com/apple/swift/pull/29786">Support for <code>let</code>/<code>var</code> declarations</a></li><li>...probably more (please <a href="https://twitter.com/zntfdr">let me know</a> if I missed anything!)</li></ul><p>As <code>@ViewBuilder</code> is SwiftUI's <code>@resultBuilder</code> dedicated to building views, all these enhancements have helped immensely with views's declaration expressiveness.</p><p>Another gain is that SwiftUI's <a href="https://developer.apple.com/documentation/swiftui/anyview"><code>AnyView</code></a> is no longer necessary in most cases:<br>in this article, let's see how we can improve our codebase thanks to these advances.</p><blockquote><p>This article goes hand in hand with <a href="https://twitter.com/johnsundell">John Sundell</a>`s <a href="https://swiftbysundell.com/articles/avoiding-anyview-in-swiftui/">Avoiding SwiftUI’s AnyView</a>, I suggest to read John's article first.</p></blockquote><h2>The road to @ViewBuilder</h2><p>As an example, let's take a function returning a sheet, named <code>presentSheet</code>:</p><pre><code><span class="keyword">enum</span> SettingsSheet: <span class="type">Identifiable</span> {
  <span class="keyword">case</span> languagePicker
  <span class="keyword">case</span> securityPin
  <span class="keyword">case</span> signIn

  <span class="keyword">var</span> id: <span class="type">Int</span> { hashValue }
}

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> showingSheet: <span class="type">SettingsSheet</span>?

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    content
      .<span class="call">sheet</span>(item: $showingSheet, content: presentSheet)
  }

  <span class="keyword">var</span> content: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="comment">// ...</span>
  }

  <span class="keyword">func</span> presentSheet(<span class="keyword">_</span> sheet: <span class="type">SettingsSheet</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="comment">// ...</span>
  }
}
</code></pre><p>When <code>presentSheet(_:)</code> is called, we need to return a view for the given <code>SettingsSheet</code>.<br>The most direct thing that we'd do is probably use a <code>switch</code> statement and return a different view for each case:</p><pre><code><span class="keyword">func</span> presentSheet(<span class="keyword">_</span> sheet: <span class="type">SettingsSheet</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
   <span class="keyword">switch</span> sheet {
     <span class="keyword">case</span> .<span class="dotAccess">languagePicker</span>:
       <span class="keyword">return</span> <span class="type">LanguagePicker</span>()
     <span class="keyword">case</span> .<span class="dotAccess">securityPin</span>:
       <span class="keyword">return</span> <span class="type">SecurityPinView</span>()
     <span class="keyword">case</span> .<span class="dotAccess">signIn</span>:
       <span class="keyword">return</span> <span class="type">SignInScreen</span>()
   }
}
</code></pre><p>However this is not possible and won't build, as the function promises to return <code>some View</code>, a.k.a. the same opaque type in each case of the <code>switch</code>, but we're returning different views instead.</p><p>Prior to Xcode 12 (and the new <code>@resultBuilder</code> enhancements) we had mainly two solutions, the most straightforward was to wrap each returned view with <code>AnyView</code>:</p><pre><code><span class="keyword">func</span> presentSheet(<span class="keyword">_</span> sheet: <span class="type">SettingsSheet</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
  <span class="keyword">switch</span> sheet {
    <span class="keyword">case</span> .<span class="dotAccess">languagePicker</span>:
      <span class="keyword">return</span> <span class="type">AnyView</span>(<span class="type">LanguagePicker</span>())
    <span class="keyword">case</span> .<span class="dotAccess">securityPin</span>:
      <span class="keyword">return</span> <span class="type">AnyView</span>(<span class="type">SecurityPinView</span>())
    <span class="keyword">case</span> .<span class="dotAccess">signIn</span>:
      <span class="keyword">return</span> <span class="type">AnyView</span>(<span class="type">SignInScreen</span>())
  }
}
</code></pre><p>This works, as we're type-erasing everything and returning <code>AnyView</code> in all possible paths (keeping the same opaque type promise).</p><p>Another solution, that would avoid using <code>AnyView</code>, was to attach <code>@ViewBuilder</code> to the function, and replace the switch statement with a long list of <code>if-else</code> statements:</p><pre><code><span class="keyword">@ViewBuilder
func</span> presentSheet(<span class="keyword">_</span> sheet: <span class="type">SettingsSheet</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
  <span class="keyword">if</span> sheet == .<span class="dotAccess">languagePicker</span> {
    <span class="type">LanguagePicker</span>()
  } <span class="keyword">else if</span> sheet == .<span class="dotAccess">securityPin</span> {
    <span class="type">SecurityPinView</span>()
  } <span class="keyword">else if</span> sheet == .<span class="dotAccess">signIn</span> {
    <span class="type">SignInScreen</span>()
  }
}
</code></pre><p>This works because simple single boolean conditions were supported by <code>@ViewBuilder</code> before Swift 5.3, however this solution is not going to work when our enum also has associated types:<br>due to this limitation, most projects stuck with the <code>AnyView</code> solution instead.</p><p>Speaking of <code>@ViewBuilder</code> and moving to Xcode 12 and Swift 5.3, things got better as we can now go back to the our original attempt, remove the <code>return</code> statements, and things will work right away:</p><pre><code><span class="keyword">@ViewBuilder
func</span> presentSheet(<span class="keyword">_</span> sheet: <span class="type">SettingsSheet</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
  <span class="keyword">switch</span> sheet {
    <span class="keyword">case</span> .<span class="dotAccess">languagePicker</span>:
      <span class="type">LanguagePicker</span>()
    <span class="keyword">case</span> .<span class="dotAccess">securityPin</span>:
      <span class="type">SecurityPinView</span>()
    <span class="keyword">case</span> .<span class="dotAccess">signIn</span>:
      <span class="type">SignInScreen</span>()
  }
}
</code></pre><p>This is much better and would work even when our enum has associated types.</p><h2>Side effects</h2><p>Let's imagine that we need to add some side effects into the function, like pushing an analytics event:</p><pre><code><span class="keyword">@ViewBuilder
func</span> presentSheet(<span class="keyword">_</span> sheet: <span class="type">SettingsSheet</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
  <span class="type">Analytics</span>.<span class="call">log</span>(.<span class="call">presenting</span>(sheet)) <span class="comment">// &lt;-- side effect</span>
  <span class="keyword">switch</span> sheet {
    <span class="keyword">case</span> .<span class="dotAccess">languagePicker</span>:
      <span class="type">LanguagePicker</span>()
    <span class="keyword">case</span> .<span class="dotAccess">securityPin</span>:
      <span class="type">SecurityPinView</span>()
    <span class="keyword">case</span> .<span class="dotAccess">signIn</span>:
      <span class="type">SignInScreen</span>()
  }
}
</code></pre><p>This won't work, as <code>@ViewBuilder</code> doesn't know how to handle the return type of our analytics log (likely <code>Void</code>).</p><blockquote><p>The preferred solution is to <em>not</em> have this kind of side effects in the view at all, however sometimes we will face such challenges, the analytics one is just an example.</p></blockquote><p>In this case we can overcome the challenge by separating the side effect logic from the view presentation, and return the view presentation result in <code>presentSheet(_:)</code>:</p><pre><code><span class="keyword">func</span> presentSheet(<span class="keyword">_</span> sheet: <span class="type">SettingsSheet</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
  <span class="type">Analytics</span>.<span class="call">log</span>(.<span class="call">presenting</span>(sheet)) <span class="comment">// &lt;-- side effect</span>
  <span class="keyword">return</span> <span class="call">_presentSheet</span>(sheet)
}

<span class="comment">// 👇🏻 Our original implementation</span>
<span class="keyword">@ViewBuilder
func</span> _presentSheet(<span class="keyword">_</span> sheet: <span class="type">SettingsSheet</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
  <span class="keyword">switch</span> sheet {
    <span class="keyword">case</span> .<span class="dotAccess">languagePicker</span>:
      <span class="type">LanguagePicker</span>()
    <span class="keyword">case</span> .<span class="dotAccess">securityPin</span>:
      <span class="type">SecurityPinView</span>()
    <span class="keyword">case</span> .<span class="dotAccess">signIn</span>:
      <span class="type">SignInScreen</span>()
  }
}
</code></pre><p>The new <code>presentSheet(_:)</code> function still returns what our original <code>presentSheet(_:)</code> returned, but now we can add any amount of arbitrary logic beside it.</p><p>What if the side effect is in the middle of our switch statement?</p><pre><code><span class="keyword">@ViewBuilder
func</span> presentSheet(<span class="keyword">_</span> sheet: <span class="type">SettingsSheet</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
  <span class="keyword">switch</span> sheet {
    <span class="keyword">case</span> .<span class="dotAccess">languagePicker</span>:
      <span class="type">LanguagePicker</span>()
    <span class="keyword">case</span> .<span class="dotAccess">securityPin</span>:
      <span class="call">doSomething</span>() <span class="comment">// &lt;-- side effect</span> 
      <span class="type">SecurityPinView</span>()
    <span class="keyword">case</span> .<span class="dotAccess">signIn</span>:
      <span class="type">SignInScreen</span>()
  }
}
</code></pre><p>Once again we can separate this logic by splitting the <code>presentSheet(_:)</code> presentation from the side effects, similarly to how we did above.<br>In this case we might need to refactor the side effects logic into a new function (with a new switch statement) and call that from the <code>presentSheet(_:)</code>.</p><p>However when these side effects have actually something to do with the view presentation, we might not wish to separate the logic, in such scenarios we can use a small trick by remembering that:</p><ul><li><code>@resultBuilder</code> has gained support for <code>let</code> and <code>var</code> declarations</li><li>Swift functions are first-class citizens</li></ul><p>...which is a fancy way to say that we can declare a new variable with the result of our side effect, and just not use it:</p><pre><code><span class="keyword">@ViewBuilder
func</span> presentSheet(<span class="keyword">_</span> sheet: <span class="type">SettingsSheet</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
  <span class="keyword">switch</span> sheet {
    <span class="keyword">case</span> .<span class="dotAccess">languagePicker</span>:
      <span class="type">LanguagePicker</span>()
    <span class="keyword">case</span> .<span class="dotAccess">securityPin</span>:
      <span class="keyword">let</span> _ = <span class="call">doSomething</span>() <span class="comment">// &lt;-- side effect</span> 
      <span class="type">SecurityPinView</span>()
    <span class="keyword">case</span> .<span class="dotAccess">signIn</span>:
      <span class="type">SignInScreen</span>()
  }
}
</code></pre><p>This is completely legal swift code and will work as expected.</p><h2>Is AnyView still needed?</h2><p>We've seen how thanks to the latest advancements in <code>@resultBuilder</code> we can get rid of most workarounds we needed prior to Xcode 12, however there are still some scenarios where <code>AnyView</code> is necessary:</p><ul><li>when using iOS availability with a pre-iOS 14 condition, as <a href="https://developer.apple.com/documentation/swiftui/viewbuilder/buildlimitedavailability(_:)"><code>buildLimitedAvailability(_:)</code></a> is iOS 14+ only:</li></ul><pre><code><span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
  <span class="keyword">if #available</span>(iOS <span class="number">13.4</span>, *) {
    <span class="keyword">return</span> <span class="type">AnyView</span>(
      ... <span class="comment">// A view for iOS 13.4+</span>
    )
  } <span class="keyword">else</span> {
    <span class="keyword">return</span> <span class="type">AnyView</span>(
      ... <span class="comment">// A different view for iOS 13.0 - 13.3</span>
    )
  }
}
</code></pre><ul><li>when type erasure is necessary: we've covered an example in <a href="https://www.fivestars.blog/articles/custom-view-styles/">Custom SwiftUI view styles</a></li><li>...and probably more: if you're aware of any other situation, please <a href="https://twitter.com/zntfdr">let me know</a>!</li></ul><h2>Conclusions</h2><p>SwiftUI has completely revolutionized how we declare UI in our apps, with Xcode 12 we've made big steps forward for even more elegant expressiveness, and I'm sure this trend will continue this year with new API allowing us to do more, with less code.</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/function-builder-attribute-can-only-be-applied-to-a-property-if-it-defines-a-getter</guid><title>Function builder attribute 'ViewBuilder' can only be applied to a property if it defines a getter</title><description>A quick follow-up and a sneak peek of a new Swift 5.4/Xcode 12.5 @resultBuilder feature.</description><link>https://www.fivestars.blog/articles/function-builder-attribute-can-only-be-applied-to-a-property-if-it-defines-a-getter</link><pubDate>Fri, 26 Feb 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<blockquote><p>This is a quick follow up on the last two entries, as I've received multiple feedbacks around this error.</p></blockquote><p>New in Swift 5.4/Xcode 12.5, <code>@resultBuilder</code> has gained <a href="https://github.com/apple/swift/pull/34097">support for stored properties</a>.</p><p>This makes it possible to associate <code>@ViewBuilder</code>, SwiftUI's <code>@resultBuilder</code> for building views, to view properties:</p><pre><code><span class="keyword">struct</span> FSView&lt;Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="comment">// 👇🏻 new in Swift 5.4</span>
  <span class="keyword">@ViewBuilder let</span> content: <span class="type">Content</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    ...
  }
}
</code></pre><p>As this declaration requires Swift 5.4, trying to build it with Xcode 12.4/Swift 5.3 or earlier will trigger a <code>Function builder attribute 'ViewBuilder' can only be applied to a property if it defines a getter</code> build error.</p><blockquote><p>Function builder, a.k.a. <code>@_functionBuilder</code>, was the <a href="https://github.com/apple/swift-evolution/blob/main/proposals/0289-result-builders.md">former name of <code>@resultBuilder</code></a>, which has been renamed on the <a href="https://github.com/apple/swift-evolution/blob/main/proposals/0289-result-builders.md">latest Swift evolution proposal</a>.</p></blockquote><p>If we'd like to build this code with previous versions of Xcode/Swift, we need to remove the property <code>@ViewBuilder</code> association, and declare a new initializer with a <code>@ViewBuilder</code> parameter instead:</p><pre><code><span class="comment">// Before Swift 5.4</span>
<span class="keyword">struct</span> FSView&lt;Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="comment">// 👇🏻 We can't add @ViewBuilder in stored properties before Swift 5.4</span>
  <span class="keyword">let</span> content: <span class="type">Content</span>

  <span class="comment">// 👇🏻 Explicit init with @ViewBuilder parameter</span>
  <span class="keyword">init</span>(<span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>) {
    <span class="keyword">self</span>.<span class="property">content</span> = <span class="call">content</span>()
  }

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    ...
  }
}
</code></pre><p>Beside the different declaration, the views will look and behave exactly the same.</p><p>In other words, from Xcode 12.5, Swift 5.4 will automatically synthesize this initializer, allowing us to write even more compact views.</p><p>Hope this clear things up! Thank you for reading and please <a href="https://twitter.com/zntfdr">let me know</a> if you have any other question :)</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-hud</guid><title>Custom HUDs in SwiftUI</title><description>Let's see how we can create our own app-wide HUDs in SwiftUI!</description><link>https://www.fivestars.blog/articles/swiftui-hud</link><pubDate>Tue, 23 Feb 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>iOS has always had some kind of system HUD:<br>we can see one every time we send an email from the Mail.app, when we toggle iOS's Silent Mode, and in many other ways.</p><p>Unfortunately, <a href="https://www.fivestars.blog/articles/large-content-viewer/">beside for accessibility</a>, iOS has yet to open an API for developers to display those HUDs at will (FB6534210).</p><p>Fortunately, Apple has also brought us SwiftUI, which makes recreating such HUDs not too troublesome: in this article, let's see how we can build one ourselves!</p><h2>The HUD</h2><img src="https://www.fivestars.blog/assets/posts/swiftui-hud/HUDImage.png"/><p>We will recreate the "Silent Mode" HUD (or the "<em>airpods are connected</em>" HUD, or..), for maximum flexibility we will let the developers input the HUD content, while the HUD definition will take care of the shape and shadow:</p><pre><code><span class="keyword">struct</span> HUD&lt;Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">@ViewBuilder let</span> content: <span class="type">Content</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    content
      .<span class="call">padding</span>(.<span class="dotAccess">horizontal</span>, <span class="number">12</span>)
      .<span class="call">padding</span>(<span class="number">16</span>)
      .<span class="call">background</span>(
        <span class="type">Capsule</span>()
          .<span class="call">foregroundColor</span>(<span class="type">Color</span>.<span class="property">white</span>)
          .<span class="call">shadow</span>(color: <span class="type">Color</span>(.<span class="dotAccess">black</span>).<span class="call">opacity</span>(<span class="number">0.16</span>), radius: <span class="number">12</span>, x: <span class="number">0</span>, y: <span class="number">5</span>)
      )
  }
}
</code></pre><p>For example we can create the preview above with:</p><pre><code><span class="type">HUD</span> {
  <span class="type">Label</span>(<span class="string">"Five stars"</span>, systemImage: <span class="string">"star.fill"</span>)
}
</code></pre><p>Great! Now that we have our design, let's add it in a view and implement its show/hide animation.</p><h2>Showing &amp; hiding the HUD</h2><p>We will embed the HUD in a <code>ZStack</code>, where it will be the top-most element:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ZStack</span>(alignment: .<span class="dotAccess">top</span>) {
      <span class="type">NavigationView</span> {
        <span class="type">Button</span>(<span class="string">"Show/hide HUD"</span>) { }
      }

      <span class="type">HUD</span> {
        <span class="type">Label</span>(<span class="string">"Five stars"</span>, systemImage: <span class="string">"star.fill"</span>)
      }
      .<span class="call">zIndex</span>(<span class="number">1</span>)
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/swiftui-hud/HUDalwaysshown.png"/><p>At this point we have our HUD always displayed, let's introduce a new state controlling whether the HUD is shown:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> showingHUD = <span class="keyword">false

  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ZStack</span>(alignment: .<span class="dotAccess">top</span>) {
      <span class="type">NavigationView</span> {
        <span class="type">Button</span>(<span class="string">"Show/hide HUD"</span>) {
          showingHUD.<span class="call">toggle</span>()
        }
      }

      <span class="keyword">if</span> showingHUD {
        <span class="type">HUD</span> {
          <span class="type">Label</span>(<span class="string">"Five stars"</span>, systemImage: <span class="string">"star.fill"</span>)
        }
        .<span class="call">zIndex</span>(<span class="number">1</span>)
      }
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/swiftui-hud/showHideHUD.gif"/><p>Great, this works, however we probably would like some fancier animation, we can achieve that by:</p><ul><li>using the <code>withAnimation</code> modifier in the button action</li><li>declaring a transition where we display our HUD from the top of the screen, along with the default opacity effect</li></ul><p>Lastly, the HUD needs to have an automatic dismissal after a few seconds, we can trigger so by adding a countdown via the <code>onAppear</code> modifier:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> showingHUD = <span class="keyword">false

  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ZStack</span>(alignment: .<span class="dotAccess">top</span>) {
      <span class="type">NavigationView</span> {
        <span class="type">Button</span>(<span class="string">"Show/hide HUD"</span>) {
          <span class="call">withAnimation</span> {
            showingHUD.<span class="call">toggle</span>()
          }
        }
      }

      <span class="keyword">if</span> showingHUD {
        <span class="type">HUD</span> {
          <span class="type">Label</span>(<span class="string">"Five stars"</span>, systemImage: <span class="string">"star.fill"</span>)
        }
        .<span class="call">transition</span>(<span class="type">AnyTransition</span>.<span class="call">move</span>(edge: .<span class="dotAccess">top</span>).<span class="call">combined</span>(with: .<span class="dotAccess">opacity</span>))
        .<span class="call">onAppear</span> {
          <span class="type">DispatchQueue</span>.<span class="property">main</span>.<span class="call">asyncAfter</span>(deadline: .<span class="call">now</span>() + <span class="number">3</span>) {
            <span class="call">withAnimation</span> {
              showingHUD = <span class="keyword">false</span>
            }
          }
        }
        .<span class="call">zIndex</span>(<span class="number">1</span>)
      }
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/swiftui-hud/GoodAnimationHUD.gif"/><p>From the user point of view, this is so much better! However our relatively simple view code is now overwhelmed with HUD logic, let's tackle that by creating a new <code>hud</code> view modifier:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">func</span> hud&lt;Content: <span class="type">View</span>&gt;(
    isPresented: <span class="type">Binding</span>&lt;<span class="type">Bool</span>&gt;,
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ZStack</span>(alignment: .<span class="dotAccess">top</span>) {
      <span class="keyword">self

      if</span> isPresented.<span class="property">wrappedValue</span> {
        <span class="type">HUD</span>(content: content)
          .<span class="call">transition</span>(<span class="type">AnyTransition</span>.<span class="call">move</span>(edge: .<span class="dotAccess">top</span>).<span class="call">combined</span>(with: .<span class="dotAccess">opacity</span>))
          .<span class="call">onAppear</span> {
            <span class="type">DispatchQueue</span>.<span class="property">main</span>.<span class="call">asyncAfter</span>(deadline: .<span class="call">now</span>() + <span class="number">3</span>) {
              <span class="call">withAnimation</span> {
                isPresented.<span class="property">wrappedValue</span> = <span class="keyword">false</span>
              }
            }
          }
          .<span class="call">zIndex</span>(<span class="number">1</span>)
      }
    }
  }
}
</code></pre><p>Thanks to this new definition our main view declaration focuses solely on the actual content:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> showingHUD = <span class="keyword">false

  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">Button</span>(<span class="string">"Show/hide HUD"</span>) {
        <span class="call">withAnimation</span> {
          showingHUD.<span class="call">toggle</span>()
        }
      }
    }
    .<span class="call">hud</span>(isPresented: $showingHUD) {
      <span class="type">Label</span>(<span class="string">"Five stars"</span>, systemImage: <span class="string">"star.fill"</span>)
    }
  }
}
</code></pre><p>Note how we've put the <code>.hud(isPresented:content:)</code> view modifier on top of <code>NavigationView</code>, meaning that our HUD will persist even when navigating multiple screens:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-hud/HUDNavigation.gif"/><h2>App-wise HUD</h2><p>Since the HUD content is a parameter, it can be swapped whenever we please. This is especially useful if we would like our app to have a <em>global</em> HUD:<br>thanks to this dynamicity we can create an environment object for our HUD, and only views that need or want to trigger the HUD will reach for it.</p><p>For example let's make it possible to change both the title and the image of our HUD <code>Label</code>, we do so by creating a new <code>HUDState</code> <code>ObservableObject</code>:</p><pre><code><span class="keyword">final class</span> HUDState: <span class="type">ObservableObject</span> {
  <span class="keyword">@Published var</span> isPresented: <span class="type">Bool</span> = <span class="keyword">false
  var</span> title: <span class="type">String</span> = <span class="string">""</span>
  <span class="keyword">var</span> systemImage: <span class="type">String</span> = <span class="string">""</span>
}
</code></pre><p>Then we move our <code>.hud(isPresented:content:)</code> modifier to the <code>App</code> level (or the <code>SceneDelegate</code> if we're still using the UIKit life cycle), along with the new state:</p><pre><code><span class="keyword">@main
struct</span> RatingApp: <span class="type">App</span> {
  <span class="keyword">@StateObject var</span> hudState = <span class="type">HUDState</span>()

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">Scene</span> {
    <span class="type">WindowGroup</span> {
      <span class="type">ContentView</span>()
        .<span class="call">environmentObject</span>(hudState)
        .<span class="call">hud</span>(isPresented: $hudState.<span class="property">isPresented</span>) {
          <span class="type">Label</span>(hudState.<span class="property">title</span>, systemImage: hudState.<span class="property">systemImage</span>)
        }
    }
  }
}
</code></pre><p>At this point <code>HUDState</code> is part of the environment, which makes it possible to trigger the HUD from any view, for example, we can go back to our original view and update it with these new changes:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@EnvironmentObject var</span> hudState: <span class="type">HUDState</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">Button</span>(<span class="string">"Show/hide HUD"</span>) {
        hudState.<span class="property">title</span> = <span class="string">"Five stars"</span>
        hudState.<span class="property">systemImage</span> = <span class="string">"star.fill"</span>
        <span class="call">withAnimation</span> {
          hudState.<span class="property">isPresented</span>.<span class="call">toggle</span>()
        }
      }
    }
  }
}
</code></pre><p>There's still room for improvement at call site, as the developer may forget to update the hud image for example, or to wrap the <code>isPresented</code> change within <code>withAnimation</code>.</p><p>To avoid such scenarios, instead of asking developers to manually change each parameter by themselves, we can let them trigger the HUD via a convenience <code>show(title:systemImage:)</code> function:</p><pre><code><span class="keyword">final class</span> HUDState: <span class="type">ObservableObject</span> {
  <span class="keyword">@Published var</span> isPresented: <span class="type">Bool</span> = <span class="keyword">false
  private(set) var</span> title: <span class="type">String</span> = <span class="string">""</span>
  <span class="keyword">private(set) var</span> systemImage: <span class="type">String</span> = <span class="string">""</span>

  <span class="keyword">func</span> show(title: <span class="type">String</span>, systemImage: <span class="type">String</span>) {
    <span class="keyword">self</span>.<span class="property">title</span> = title
    <span class="keyword">self</span>.<span class="property">systemImage</span> = systemImage
    <span class="call">withAnimation</span> {
      isPresented = <span class="keyword">true</span>
    }
  }
}
</code></pre><p>Going back to our original view:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@EnvironmentObject private var</span> hud: <span class="type">HUDState</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">Button</span>(<span class="string">"Show/hide HUD"</span>) {
        hud.<span class="call">show</span>(title: <span class="string">"Five stars"</span>, systemImage: <span class="string">"star.fill"</span>)
      }
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/swiftui-hud/GoodAnimationHUD.gif"/><p>Now we truly have a global HUD and almost no HUD logic if not the bare minimum to trigger it from any view. Of course there's still room for improvements (you win Internet points if you spot some of these and <a href="https://twitter.com/zntfdr">let me know</a>), however this is already a good setup.</p><p>The final gist, both for the local and global example, can be found <a href="https://github.com/zntfdr/FiveStarsCodeSamples/tree/main/SwiftUI-HUD">here</a>.</p><h2>Conclusions</h2><p>HUDs have been part of the iOS visual language for many years, while we don't have access to the native ones, SwiftUI lets us create elegant solutions that can go well beyond what a system API would ever offer.</p><p>While we focused on just one kind of HUD, the same flow can be applied to any other kind, and can even be applied for custom alerts, and much more.</p><p>Do you display HUDs in your apps? What approach do you use? Please <a href="https://twitter.com/zntfdr">let me know</a>!</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-patter-passing-views</guid><title>SwiftUI patterns: passing &amp; accepting views</title><description>Let's see how SwiftUI itself uses composition in its own APIs.</description><link>https://www.fivestars.blog/articles/swiftui-patter-passing-views</link><pubDate>Tue, 16 Feb 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>When building advanced screens, at some point our view definitions will need to accept external views as parameters for maximum flexibility.</p><p>This is something SwiftUI solves beautifully via composition, which we covered in <a href="https://www.fivestars.blog/articles/design-system-composing-views/">Composing SwiftUI views</a>.</p><p>In this article let's explore how SwiftUI itself use this pattern, and what kind of variants there are.</p><h2>Explicit types</h2><p>When a view wants to offer some flexibility but still expect a certain view instance, this can be achieved by explicitly asking for that instance type.</p><p>Some views use <a href="https://developer.apple.com/documentation/swiftui/Text"><code>Text</code></a> as labels and for accessibility, in such cases it doesn't make sense to accept <em>any</em> view, as <a href="https://www.apple.com/voiceover/info/guide/_1121.html">VoiceOver</a> needs to read <em>text</em> out loud:</p><pre><code><span class="comment">// Definition</span>
<span class="keyword">extension</span> <span class="type">Image</span> {
  <span class="keyword">public init</span>(<span class="keyword">_</span> name: <span class="type">String</span>, bundle: <span class="type">Bundle</span>? = <span class="keyword">nil</span>, label: <span class="type">Text</span>)
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Image</span>(
      <span class="string">"wwdc20-10040-header"</span>, 
      label: <span class="type">Text</span>(<span class="string">"Session title: Data Essentials in SwiftUI"</span>)
    )
  }
}
</code></pre><blockquote><p>Kind reminder to use the <code>init(decorative:bundle:)</code> <a href="https://developer.apple.com/documentation/swiftui/Image"><code>Image</code></a> initializer when an image should be ignored for accessibility purposes.</p></blockquote><p>A view might also offer some extra features only when specific views are passed, for example new in iOS 14 we can inline images within <a href="https://developer.apple.com/documentation/swiftui/Text"><code>Text</code></a>:</p><pre><code><span class="comment">// Definition</span>
<span class="keyword">extension</span> <span class="type">Text</span> {
  <span class="keyword">@available</span>(iOS <span class="number">14.0</span>, macOS <span class="number">11.0</span>, tvOS <span class="number">14.0</span>, watchOS <span class="number">7.0</span>, *)
  <span class="keyword">public init</span>(<span class="keyword">_</span> image: <span class="type">Image</span>)
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Text</span>(<span class="string">"Five"</span>)
    +
    <span class="type">Text</span>(<span class="type">Image</span>(systemName: <span class="string">"star.circle.fill"</span>)) 
    + 
    <span class="type">Text</span>(<span class="string">"Stars"</span>)
  }
}
</code></pre><h2>Generic views</h2><p>Similarly to above, when a view expects just one "simple" generic view, this is achieved by asking for a generic view instance:</p><pre><code><span class="comment">// Definition</span>
<span class="keyword">struct</span> Picker&lt;Label: <span class="type">View</span>, SelectionValue: <span class="type">Hashable</span>, Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">init</span>(
    selection: <span class="type">Binding</span>&lt;<span class="type">SelectionValue</span>&gt;, 
    label: <span class="type">Label</span>, <span class="comment">// &lt;- here</span>
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>
  )
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">enum</span> PizzaTopping: <span class="type">String</span>, <span class="type">CaseIterable</span>, <span class="type">Identifiable</span> {
    <span class="keyword">case</span> 🍍, 🍄, 🫒, 🐓
    <span class="keyword">var</span> id: <span class="type">String</span> { rawValue }
  }

  <span class="keyword">@State var</span> flavor: <span class="type">PizzaTopping</span> = .🍍

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">Form</span> {
        <span class="type">Picker</span>(
          selection: $flavor,
          <span class="comment">// 👇🏻 Our simple view.</span>
          label: <span class="type">Label</span>(<span class="string">"Pick your topping:"</span>, systemImage: <span class="string">"plus.circle"</span>)
        ) {
          <span class="type">ForEach</span>(<span class="type">PizzaTopping</span>.<span class="property">allCases</span>) { topping <span class="keyword">in</span>
            <span class="type">Text</span>(topping.<span class="property">rawValue</span>.<span class="property">capitalized</span>)
              .<span class="call">tag</span>(topping)
          }
        }
      }
    }
  }
}
</code></pre><p>Since <a href="https://developer.apple.com/documentation/swiftui/Picker"><code>Picker</code></a> accepts a generic <code>label</code> instance, nothing stops us to pass a <a href="https://developer.apple.com/documentation/swiftui/ScrollView"><code>ScrollView</code></a>, or a <a href="https://developer.apple.com/documentation/swiftui/Button"><code>Button</code></a>, etc, or even a combination of all of them together: SwiftUI will try its best to make it work (and it does!).<br>However, when the view asks for a view instance, this is probably meant to be a single simple view.</p><blockquote><p>Views using this pattern: <a href="https://developer.apple.com/documentation/swiftui/Picker">Picker</a>, <a href="https://developer.apple.com/documentation/swiftui/GroupBox">GroupBox</a>.</p></blockquote><h2>@ViewBuilder</h2><p>This is by far the most popular way to pass views in SwiftUI. <code>@ViewBuilder</code> is SwiftUI's <code>@resultBuilder</code>, enabling us to construct views from closures:<br>when a view accepts a <code>@ViewBuilder</code> parameter, in most cases said parameter will be a core part of the new view.</p><p>The most generic definition of <a href="https://developer.apple.com/documentation/swiftui/Button"><code>Button</code></a> uses this pattern, where the button <code>label</code> can be anything we desire:</p><pre><code><span class="comment">// Definition</span>
<span class="keyword">struct</span> Button&lt;Label&gt; : <span class="type">View</span> <span class="keyword">where</span> <span class="type">Label</span> : <span class="type">View</span> {
  <span class="keyword">init</span>(action: <span class="keyword">@escaping</span> () -&gt; <span class="type">Void</span>, <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>)
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(action: { <span class="call">print</span>(<span class="string">"button tapped"</span>) }) {
      ... <span class="comment">// Anything goes!</span>
    }
  }
}
</code></pre><p>All SwiftUI stacks use this approach as well, where our content will be distributed in a different axis based on the stack used:</p><pre><code><span class="comment">// Definition</span>
<span class="keyword">struct</span> VStack&lt;Content&gt; : <span class="type">View</span> <span class="keyword">where</span> <span class="type">Content</span> : <span class="type">View</span> {
  <span class="keyword">init</span>(
    alignment: <span class="type">HorizontalAlignment</span> = .<span class="dotAccess">center</span>, 
    spacing: <span class="type">CGFloat</span>? = <span class="keyword">nil</span>, 
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>
  )
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      ... <span class="comment">// Anything goes!</span>
    }
  }
}
</code></pre><blockquote><p>Views using this pattern: <a href="https://developer.apple.com/documentation/swiftui/ColorPicker"><code>ColorPicker</code></a>, <a href="https://developer.apple.com/documentation/swiftui/CommandGroup"><code>CommandGroup</code></a>, <a href="https://developer.apple.com/documentation/swiftui/CommandMenu"><code>CommandMenu</code></a>, <a href="https://developer.apple.com/documentation/swiftui/DatePicker"><code>DatePicker</code></a>, <a href="https://developer.apple.com/documentation/swiftui/Form"><code>Form</code></a>, <a href="https://developer.apple.com/documentation/swiftui/Group"><code>Group</code></a>, <a href="https://developer.apple.com/documentation/swiftui/HStack"><code>HStack</code></a>, <a href="https://developer.apple.com/documentation/swiftui/LazyHStack"><code>LazyHStack</code></a>, <a href="https://developer.apple.com/documentation/swiftui/LazyVStack"><code>LazyVStack</code></a>, <a href="https://developer.apple.com/documentation/swiftui/Link"><code>Link</code></a>, <a href="https://developer.apple.com/documentation/swiftui/List"><code>List</code></a>, <a href="https://developer.apple.com/documentation/swiftui/NavigationView"><code>NavigationView</code></a>, <a href="https://developer.apple.com/documentation/swiftui/Picker"><code>Picker</code></a>, <a href="https://developer.apple.com/documentation/swiftui/ProgressView"><code>ProgressView</code></a>, <a href="https://developer.apple.com/documentation/swiftui/ScrollView"><code>ScrollView</code></a>, <a href="https://developer.apple.com/documentation/swiftui/Section"><code>Section</code></a>, <a href="https://developer.apple.com/documentation/swiftui/Slider"><code>Slider</code></a>, <a href="https://developer.apple.com/documentation/swiftui/Stepper"><code>Stepper</code></a>, <a href="https://developer.apple.com/documentation/swiftui/TabView"><code>TabView</code></a>, <a href="https://developer.apple.com/documentation/swiftui/Toggle"><code>Toggle</code></a>, <a href="https://developer.apple.com/documentation/swiftui/VStack"><code>VStack</code></a>, <a href="https://developer.apple.com/documentation/swiftui/ZStack"><code>ZStack</code></a>.</p></blockquote><h3>Double @ViewBuilder</h3><p>Some views sport separate generic components, where each can be as complicated as needed. In such scenarios the view will ask for a separate <code>@ViewBuilders</code> parameter for each component:</p><pre><code><span class="comment">// Definition</span>
<span class="keyword">struct</span> Label&lt;Title, Icon&gt; : <span class="type">View</span> <span class="keyword">where</span> <span class="type">Title</span> : <span class="type">View</span>, <span class="type">Icon</span> : <span class="type">View</span> {
  <span class="keyword">init</span>(<span class="keyword">@ViewBuilder</span> title: () -&gt; <span class="type">Title</span>, <span class="keyword">@ViewBuilder</span> icon: () -&gt; <span class="type">Icon</span>)
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Label</span>(
      title: { <span class="type">VStack</span> { ... } },
      icon: { <span class="type">ScrollView</span> { ... } }
    )
  }
}
</code></pre><blockquote><p>Views using this pattern: <a href="https://developer.apple.com/documentation/swiftui/Label">Label</a>, <a href="https://developer.apple.com/documentation/swiftui/Menu">Menu</a>, <a href="https://developer.apple.com/documentation/swiftui/ProgressView">ProgressView</a>.</p></blockquote><h3>@escaping @Viewbuilder</h3><p>We have a particular <code>@Viewbuilder</code> use case when the closure is marked as <code>@escaping</code>, meaning that it won't be used right during the view initialization.</p><p>Most of these cases will also pass a parameter to the closure, allowing us to change the view with this parameter.</p><pre><code><span class="comment">// Definition</span>
<span class="keyword">extension</span> <span class="type">ForEach</span> <span class="keyword">where</span> <span class="type">Content</span>: <span class="type">View</span>  {
  <span class="keyword">public init</span>(
    <span class="keyword">_</span> data: <span class="type">Data</span>, 
    id: <span class="type">KeyPath</span>&lt;<span class="type">Data</span>.<span class="type">Element</span>, <span class="type">ID</span>&gt;, 
    <span class="keyword">@ViewBuilder</span> content: <span class="keyword">@escaping</span> (<span class="type">Data</span>.<span class="type">Element</span>) -&gt; <span class="type">Content</span>
  )
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">HStack</span> {
      <span class="type">ForEach</span>((<span class="number">0</span>...<span class="number">9</span>), id: \.<span class="keyword">self</span>) { number <span class="keyword">in</span>
        <span class="type">Text</span>(<span class="string">"</span>\(number)<span class="string">"</span>)
      }
    }
  }
}
</code></pre><p><code>ForEach</code>, similarly to <a href="https://developer.apple.com/documentation/swiftui/List"><code>List</code></a>, shows how this closure can be called multiple times when building the view body, therefore our closure will (potentially) be used to add multiple views to the final view.</p><p>Another interesting use case of <code>@escaping</code> is in SwiftUI readers, namely <a href="https://developer.apple.com/documentation/swiftui/GeometryReader">GeometryReader</a> and <a href="https://developer.apple.com/documentation/swiftui/ScrollViewReader">ScrollViewReader</a>, where our closure is called every time a redraw is needed, giving us access to runtime information that is not available at build time:</p><pre><code><span class="comment">// Definition</span>
<span class="keyword">struct</span> GeometryReader&lt;Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">@inlinable init</span>(<span class="keyword">@ViewBuilder</span> content: <span class="keyword">@escaping</span> (<span class="type">GeometryProxy</span>) -&gt; <span class="type">Content</span>)
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">GeometryReader</span> { proxy <span class="keyword">in</span>
      <span class="type">Text</span>(
        <span class="string">"The parent proposed size is</span> \(proxy.<span class="property">size</span>.<span class="property">width</span>)<span class="string">x</span>\(proxy.<span class="property">size</span>.<span class="property">height</span>)<span class="string">"</span>
      )
      .<span class="call">frame</span>(maxWidth: .<span class="dotAccess">infinity</span>, maxHeight: .<span class="dotAccess">infinity</span>)
    }
  }
}
</code></pre><blockquote><p>Views using this pattern: <a href="https://developer.apple.com/documentation/swiftui/ForEach">ForEach</a>, <a href="https://developer.apple.com/documentation/swiftui/List">List</a>, <a href="https://developer.apple.com/documentation/swiftui/GeometryReader">GeometryReader</a>, <a href="https://developer.apple.com/documentation/swiftui/ScrollViewReader">ScrollViewReader</a>, <a href="https://developer.apple.com/documentation/swiftui/OutlineGroup">OutlineGroup</a>.</p></blockquote><h3>One-off exceptions</h3><p>There are a couple of views that have their own pattern not used by any other view, they both are interesting so let's highlight them here.</p><h4>@ViewBuilder &amp; @escaping @ViewBuilder</h4><p><a href="https://developer.apple.com/documentation/swiftui/DisclosureGroup"><code>DisclosureGroup</code></a>, which we built from scratch in <a href="https://www.fivestars.blog/articles/optional-binding/">Adding optional @Bindings to SwiftUI views</a>, is the only view that accepts both a non-escaping <code>@ViewBuilder</code> and an escaping one.</p><p>This is to make the view as light as possible, and only generate the content when (and if) it's shown/disclosed:</p><pre><code><span class="comment">// Definition</span>
<span class="keyword">struct</span> DisclosureGroup&lt;Label: <span class="type">View</span>, Content: <span class="type">View</span>&gt; : <span class="type">View</span> {
  <span class="keyword">init</span>(
    <span class="comment">// 👇🏻 This closure will run only when/if the user taps on the disclosure group.</span>
    <span class="keyword">@ViewBuilder</span> content: <span class="keyword">@escaping</span> () -&gt; <span class="type">Content</span>, 
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>
  )
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">List</span> {
      <span class="type">DisclosureGroup</span> {
        <span class="comment">// This closure will run only when/if the user taps on the disclosure group.</span>
        <span class="type">Text</span>(<span class="string">"Lazy-loaded content"</span>)
      } label: {
        <span class="type">Text</span>(<span class="string">"Tap to show content"</span>)
      }
    }
  }
}
</code></pre><p><code>List</code> uses the same approach for its hierarchy variants, which we covered and built from scratch in <a href="https://www.fivestars.blog/articles/swiftui-hierarchy-list/">SwiftUI Hierarchy List</a>:<br>in <a href="https://developer.apple.com/documentation/swiftui/List"><code>List</code></a>'s case there's only one <code>@escaping @ViewBuilder</code> parameter, as the "label" is also generated with that same parameter (from an item in the level above in the hierarchy).</p><h4>Generic view + @ViewBuilder</h4><p>The last exception is <a href="https://developer.apple.com/documentation/swiftui/NavigationLink"><code>NavigationLink</code></a> which, funnily enough, contradicts everything we've seen above. Let's look at its simplest definition:</p><pre><code><span class="comment">// Definition</span>
<span class="keyword">struct</span> NavigationLink&lt;Label: <span class="type">View</span>, Destination: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">init</span>(destination: <span class="type">Destination</span>, <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>)
}
</code></pre><p><a href="https://developer.apple.com/documentation/swiftui/NavigationLink"><code>NavigationLink</code></a> asks for two parameters:</p><ul><li>a <code>destination</code> generic view, which is the view that will be shown when the link is triggered</li><li>a <code>@ViewBuilder label</code>, which, similar to <a href="https://developer.apple.com/documentation/swiftui/Button"><code>Button</code></a>, is used to actually compose the link</li></ul><p>This view contradicts everything above for three main reasons:</p><ul><li>the <code>destination</code>, which probably is the most complicated component (vs. the <code>label</code>), is just a generic view instead of a <code>@ViewBuilder</code>: this can be probably seen as an invitation from the SwiftUI team to define this view separately, and not having the actual implementation in the <a href="https://developer.apple.com/documentation/swiftui/NavigationLink"><code>NavigationLink</code></a> definition</li></ul><ul><li>the <code>destination</code> should not be used until the link is triggered, yet we need to pass a fully initialized instance as a parameter in the <a href="https://developer.apple.com/documentation/swiftui/NavigationLink"><code>NavigationLink</code></a> (this is one of the reasons why the smart people at <a href="https://www.objc.io/">objc.io</a> have created <a href="https://www.objc.io/blog/2019/07/02/lazy-loading/"><code>LazyView</code></a>, which work wonders in <code>NavigationLink</code>s).</li></ul><ul><li>the <code>label</code> component is the one that comes with a <code>@ViewBuilder</code> modifier, this is consistent with <a href="https://developer.apple.com/documentation/swiftui/Button"><code>Button</code></a>'s definition, however the difference here is that <code>label</code> should be probably be considered the <em>secondary</em> component, with <code>destination</code> being the most important one.</li></ul><p><a href="https://www.fivestars.blog/articles/hashable-bindings/">Once</a> <a href="https://www.fivestars.blog/articles/swiftui-patterns-bindings/">again</a> <a href="https://developer.apple.com/documentation/swiftui/NavigationLink"><code>NavigationLink</code></a> reveals itself as an <a href="https://www.fivestars.blog/articles/swift-protocols/">exception</a> among all SwiftUI definitions:<br>as we covered in <a href="https://www.fivestars.blog/articles/programmatic-navigation/">The future of SwiftUI navigation (?)</a>, I don't think it's long for this world.</p><h2>Main takeaways</h2><p>In this article we’ve explored how all SwiftUI native components accept an external view, here are some of the most important takeaways:</p><ul><li>If a view needs a specific type instance, ask for that type directly</li><li>If a view needs a secondary, simple view instance (mainly used as a <code>label</code>), ask for a generic instance.</li><li>If a view asks for a core part of the final view, which can be as complex as needed, use <code>@ViewBuilder</code>.</li><li>If a view needs to build multiple parts of the final view via closures, maybe not even all at once, use <code>@escaping @Viewbuilder</code><ul><li>it's totally fine to pass parameters to the closure when/if needed</li></ul></li></ul><h2>Conclusions</h2><p>Accepting external views is probably one of the most powerful patterns that we can use to make our apps and design truly composable:<br>SwiftUI itself uses this pattern a lot, sometime it's even hidden via convenience API, so developers don't even need to be aware of it happening.</p><p>Do you use composition in your apps? Where do you find it working (or not) well? <a href="https://twitter.com/zntfdr">Please let me know</a>!</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p><h2>Bonus track: Swift 5.4</h2><blockquote><p>Credits to <a href="https://twitter.com/kingatarthur/">Matt Young</a> for the tip!</p></blockquote><p>New in Swift 5.4 we have <code>@resultBuilder</code> support for stored properties, which makes our internal views more convenient to write, take the following view for example:</p><pre><code><span class="keyword">struct</span> CardView&lt;Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="comment">// 👇🏻 New in Swift 5.4</span>
  <span class="keyword">@ViewBuilder let</span> content: <span class="type">Content</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    content
  }
}
</code></pre><p>Thanks to this definition Swift now synthesizes the following initializer:</p><pre><code><span class="keyword">internal init</span>(<span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>) {
  <span class="keyword">self</span>.<span class="property">content</span> = <span class="call">content</span>()
}
</code></pre><p>The only way to obtain the same initializer before Swift 5.4 was to explicitly declare it ourselves:</p><pre><code><span class="comment">// Before Swift 5.4</span>
<span class="keyword">struct</span> CardView&lt;Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="comment">// 👇🏻 We can't add @ViewBuilder in stored properties before Swift 5.4</span>
  <span class="keyword">let</span> content: <span class="type">Content</span>

  <span class="keyword">init</span>(<span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>) {
    <span class="keyword">self</span>.<span class="property">content</span> = <span class="call">content</span>()
  }

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    content
  }
}
</code></pre><blockquote><p>As the rest of Swift synthesization, this generates an <code>internal</code> initializer: anything <code>public</code> will still need to have an explicit initializer.</p></blockquote>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-keyboard</guid><title>SwiftUI keyboard avoidance</title><description></description><link>https://www.fivestars.blog/articles/swiftui-keyboard</link><pubDate>Tue, 9 Feb 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>One of the things I was most surprised with when starting my iOS career was the lack of a baked-in, system-wide keyboard avoidance:<br>every app needs to show a keyboard at some point, and every app has to either <a href="https://www.vadimbulavin.com/how-to-move-swiftui-view-when-keyboard-covers-text-field/">re-invent the wheel</a> or pick one of the <a href="https://github.com/hackiftekhar/IQKeyboardManager">de-facto standard open source solutions</a> out there.</p><p>Fortunately, in iOS 14, SwiftUI ends it all by gaining automatic keyboard avoidance.</p><p>In this article, let's have a rundown on how to manage this long-awaited, very welcome feature.</p><h2>Opting-out</h2><p>This feature is opt-out, meaning it's on by default.<br><br>Unlike for system appearances (a.k.a dark/light mode), there's no way to entirely opt out of this feature via an <code>info.plist</code> key (FB8756741), instead, we will need to use the new <a href="https://developer.apple.com/documentation/swiftui/roundedrectangle/ignoressafearea(_:edges:)?changes=latest_beta"><code>ignoresSafeArea(_:edges:)</code></a> view modifier when needed:</p><pre><code><span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
  myView
    .<span class="call">ignoresSafeArea</span>(.<span class="dotAccess">keyboard</span>)
}
</code></pre><blockquote><p>More examples below.</p></blockquote><p>However I recommend against this and to embrace SwiftUI's new feature:<br>let the SwiftUI team deal with it, while we can focus on delivering the best app to our users!</p><h2>iOS 13 compatibility</h2><p>As this is an iOS 14+ feature, we still need to implement our own keyboard avoidance when/if targeting iOS 13.<br><br>This is mostly a matter of enabling the custom/3rd-party keyboard avoidance only on iOS 13:</p><pre><code><span class="keyword">if #available</span>(iOS <span class="number">14.0</span>, *) {
  <span class="comment">// do nothing, use SwiftUI's keyboard avoidance.</span>
} <span class="keyword">else</span> {
  <span class="comment">// enable custom/3rd-party keyboard avoidance.</span>
}
</code></pre><blockquote><p>To be found in your app/scene delegate.</p></blockquote><h2>How keyboard avoidance works</h2><p>Every device has a safe area: when a system keyboard is shown, this safe area will grow, reducing the space proposed to our SwiftUI views, which will need to compress in order to make room for the keyboard.</p><p>In the following example we can see how activating the <code>TextField</code> compresses the colors heights:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-keyboard/colorCompressionImage.gif"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> redSize: <span class="type">CGSize</span> = .<span class="dotAccess">zero</span>
  <span class="keyword">@State private var</span> yellowSize: <span class="type">CGSize</span> = .<span class="dotAccess">zero</span>
  <span class="keyword">@State private var</span> name: <span class="type">String</span> = <span class="string">""</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">Color</span>.<span class="property">red</span>
        .<span class="call">overlay</span>(<span class="type">Text</span>(verbatim: <span class="string">"</span>\(redSize)<span class="string">"</span>))
        .<span class="call">readSize</span> {
          redSize = $0
        }

      <span class="type">TextField</span>(<span class="string">"Name:"</span>, text: $name)

      <span class="type">Color</span>.<span class="property">yellow</span>
        .<span class="call">overlay</span>(<span class="type">Text</span>(verbatim: <span class="string">"</span>\(yellowSize)<span class="string">"</span>))
        .<span class="call">readSize</span> {
          yellowSize = $0
        }
    }
    .<span class="call">padding</span>()
  }
}
</code></pre><blockquote><p>We're using our <a href="https://www.fivestars.blog/articles/swiftui-share-layout-information/"><code>.readSize(onChange:)</code> view modifier</a> to get the child view size.</p></blockquote><p>This is possible because we had two "compressible" views (our colors), if all views are not flexible, then SwiftUI will shift the whole view up:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-keyboard/colorNoCompressionImage.gif"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> name: <span class="type">String</span> = <span class="string">""</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">Color</span>.<span class="property">red</span>
        .<span class="call">frame</span>(height: <span class="number">314</span>)

      <span class="type">TextField</span>(<span class="string">"Name:"</span>, text: $name)

      <span class="type">Color</span>.<span class="property">yellow</span>
        .<span class="call">frame</span>(height: <span class="number">314</span>)
    }
    .<span class="call">padding</span>()
  }
}
</code></pre><p>This happens because our view height exceeds the parent proposed size:<br>in these situations SwiftUI will center our view in the middle of the available area, and our view will vertically leak this area both at the bottom and at the top.</p><p>What if we move the text field to the top of the colors?</p><img src="https://www.fivestars.blog/assets/posts/swiftui-keyboard/colorNoCompressionFailImage.gif"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> name: <span class="type">String</span> = <span class="string">""</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">TextField</span>(<span class="string">"Name:"</span>, text: $name)

      <span class="type">Color</span>.<span class="property">red</span>
        .<span class="call">frame</span>(height: <span class="number">314</span>)

      <span class="type">Color</span>.<span class="property">yellow</span>
        .<span class="call">frame</span>(height: <span class="number">314</span>)
    }
    .<span class="call">padding</span>()
  }
}
</code></pre><p>Wops, the text field is completely hidden!</p><p>The easiest (and recommended) solution is to use wrap our views in a <a href="https://www.fivestars.blog/articles/scrollview-offset/">scroll view</a> (<code>ScrollView</code>, <code>List</code>, <code>Form</code>) instead of a <code>VStack</code>:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-keyboard/colorScrollImage.gif"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> name: <span class="type">String</span> = <span class="string">""</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollView</span> {
      <span class="type">TextField</span>(<span class="string">"Name:"</span>, text: $name)

      <span class="type">Color</span>.<span class="property">red</span>
        .<span class="call">frame</span>(height: <span class="number">314</span>)

      <span class="type">Color</span>.<span class="property">yellow</span>
        .<span class="call">frame</span>(height: <span class="number">314</span>)
    }
    .<span class="call">padding</span>()
  }
}
</code></pre><p>Scroll views, <a href="https://www.fivestars.blog/articles/scrollview-offset/">as we've seen before</a>, are composed by two layers:</p><ul><li>a frame layer, used to position the scroll view itself in the view hierarchy</li><li>a "scrollable" content layer, where all its content is placed</li></ul><p>When a keyboard is shown, only the frame layer is affected, our content (<code>TextField</code> and colors) is not.</p><p>SwiftUI manages the scroll view offset for us, and it will make sure that our text input is visible, regardless of where it is located:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-keyboard/colorScrollBottomImage.gif"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> name: <span class="type">String</span> = <span class="string">""</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollView</span> {
      <span class="type">Color</span>.<span class="property">red</span>
        .<span class="call">frame</span>(height: <span class="number">314</span>)

      <span class="type">Color</span>.<span class="property">yellow</span>
        .<span class="call">frame</span>(height: <span class="number">314</span>)

      <span class="type">TextField</span>(<span class="string">"Name:"</span>, text: $name)
    }
    .<span class="call">padding</span>()
  }
}
</code></pre><h2>Ignoring the keyboard</h2><p>Let's say that, for some reason we don't want our view to move when the keyboard shows up, going back to our original example we can add the view modifier <code>.ignoresSafeArea(.keyboard)</code> and it will work as expected:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-keyboard/ignoreCompressableColorImage.gif"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> name: <span class="type">String</span> = <span class="string">""</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">Color</span>.<span class="property">red</span>

      <span class="type">TextField</span>(<span class="string">"Name:"</span>, text: $name)

      <span class="type">Color</span>.<span class="property">yellow</span>
    }
    .<span class="call">padding</span>()
    .<span class="call">ignoresSafeArea</span>(.<span class="dotAccess">keyboard</span>)
  }
}
</code></pre><p>If we make all our views heights not flexible, the whole view will shift up once again, despite <code>.ignoresSafeArea(.keyboard)</code> being there:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-keyboard/colorNoCompressionImage.gif"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> name: <span class="type">String</span> = <span class="string">""</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">Color</span>.<span class="property">red</span>
        .<span class="call">frame</span>(height: <span class="number">314</span>)

      <span class="type">TextField</span>(<span class="string">"Name:"</span>, text: $name)

      <span class="type">Color</span>.<span class="property">yellow</span>
        .<span class="call">frame</span>(height: <span class="number">314</span>)
    }
    .<span class="call">padding</span>()
    .<span class="call">ignoresSafeArea</span>(.<span class="dotAccess">keyboard</span>)
  }
}
</code></pre><p>After some experimentation it seems like SwiftUI shifts the view up when the view body has not enough compressible vertical space:<br>in other words, if our content height cannot be reduced to accommodate the keyboard, <code>.ignoresSafeArea(.keyboard)</code> won't work.</p><blockquote><p>If you figure out the right formula, please <a href="https://twitter.com/zntfdr">let me know</a>!</p></blockquote><p>In either case, no components will be actually compressed with <code>.ignoresSafeArea(.keyboard)</code>:<br>the only difference is whether the view is shifted up or not.</p><p>The recommended way to avoid such issues is by using a scroll view, which makes the previous example work as expected:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-keyboard/ignoreCompressableColorImage.gif"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> name: <span class="type">String</span> = <span class="string">""</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollView</span> {  <span class="comment">// this is now a ScrollView instead of a VStack.</span>
      <span class="type">Color</span>.<span class="property">red</span>
        .<span class="call">frame</span>(height: <span class="number">314</span>)

      <span class="type">TextField</span>(<span class="string">"Name:"</span>, text: $name)

      <span class="type">Color</span>.<span class="property">yellow</span>
        .<span class="call">frame</span>(height: <span class="number">314</span>)
    }
    .<span class="call">padding</span>()
    .<span class="call">ignoresSafeArea</span>(.<span class="dotAccess">keyboard</span>)
  }
}
</code></pre><h2>Conclusions</h2><p>Avoiding the keyboard is something pretty much every iOS developer has had to deal with at some point, thanks to iOS 14 and SwiftUI this is now a thing of the past, allowing us developers to focus even more on app features instead.</p><p>Have you found the transition to iOS 14 smooth? Any pitfalls? <a href="https://twitter.com/zntfdr">Please let me know</a>!</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles.</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/custom-view-styles</guid><title>How to create custom SwiftUI view styles</title><description></description><link>https://www.fivestars.blog/articles/custom-view-styles</link><pubDate>Tue, 2 Feb 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>When building new SwiftUI components, it's common to offer variants for different purposes/screens.</p><p>One way to manage such variants is via styles, for example by using instances conforming to <a href="https://www.fivestars.blog/articles/label/"><code>LabelStyle</code></a> or <a href="https://www.fivestars.blog/articles/button-styles/"><code>ButtonStyle</code></a>:</p><pre><code><span class="type">HStack</span> {
  <span class="type">Label</span>(<span class="string">"The title will be hidden"</span>, systemImage: <span class="string">"moon.circle.fill"</span>) 
    .<span class="call">labelStyle</span>(<span class="type">IconOnlyLabelStyle</span>())

  <span class="type">Button</span>(<span class="string">"Tap me"</span>) {
    <span class="comment">// handle button tap</span>
  }
  .<span class="call">buttonStyle</span>(<span class="type">BorderlessButtonStyle</span>())
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/custom-view-styles/labelButtonImage.png"/><blockquote><p>We took deep dives on both components styling <a href="https://www.fivestars.blog/articles/label/">here</a> (for <a href="https://www.fivestars.blog/articles/label/"><code>Label</code></a>) and <a href="https://www.fivestars.blog/articles/button-styles/">here</a> (for <a href="https://www.fivestars.blog/articles/button-styles/"><code>Button</code></a>).</p></blockquote><p>These styles are set in SwiftUI's environment. Instead of declaring them for each component, we could set them on a parent view, making its children inherit the styles automatically:</p><pre><code><span class="type">VStack</span> {
  <span class="comment">// All `Label`s declared here will have the title hidden.</span>

  <span class="type">Label</span>(<span class="string">"Sun"</span>, systemImage: <span class="string">"sun.min.fill"</span>) 
  <span class="type">Label</span>(<span class="string">"Moon"</span>, systemImage: <span class="string">"moon.fill"</span>) 

  ...
}
.<span class="call">labelStyle</span>(<span class="type">IconOnlyLabelStyle</span>())
</code></pre><img src="https://www.fivestars.blog/assets/posts/custom-view-styles/labelSunMoonImage.png"/><p>Moreover, as each view will have its environment passed down from the parent, we can always override a style when needed:</p><pre><code><span class="type">VStack</span> {
  <span class="type">Label</span>(<span class="string">"Sun"</span>, systemImage: <span class="string">"sun.min.fill"</span>) 
  <span class="type">Label</span>(<span class="string">"Moon"</span>, systemImage: <span class="string">"moon.fill"</span>) 

  <span class="comment">// This `Label` will display both title and icon.</span>
  <span class="type">Label</span>(<span class="string">"Rainy &amp; cloudy"</span>, systemImage: <span class="string">"cloud.sun.rain.fill"</span>)
    .<span class="call">labelStyle</span>(<span class="type">TitleAndIconLabelStyle</span>())

  ...
}
.<span class="call">labelStyle</span>(<span class="type">IconOnlyLabelStyle</span>())
</code></pre><img src="https://www.fivestars.blog/assets/posts/custom-view-styles/labelRainyImage.png"/><p>Wouldn't it be cool if we could have the same elasticity in our own SwiftUI views? In this article, let's learn how to do just that!</p><p>Here's a sneak peek of the view and styles we will build:</p><img src="https://www.fivestars.blog/assets/posts/custom-view-styles/final.png"/><h2>The view</h2><p>We're going to build a generic <code>Card</code> component:</p><pre><code><span class="keyword">struct</span> Card&lt;Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">var</span> content: () -&gt; <span class="type">Content</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">content</span>()
  }
}
</code></pre><blockquote><p>We've covered this technique in <a href="https://www.fivestars.blog/articles/design-system-composing-views/">Composing SwiftUI views</a>.</p></blockquote><p><code>Card</code> takes in a method returning a <code>View</code>, this view is our <code>Card</code>'s body (for the moment). Here are a couple of examples:</p><pre><code><span class="type">Card</span> {
  <span class="type">Text</span>(<span class="string">"Hello"</span>)
}

<span class="type">Card</span> {
  <span class="type">RoundedRectangle</span>(cornerRadius: <span class="number">8</span>)
    .<span class="call">fill</span>(<span class="type">Color</span>.<span class="property">orange</span>)
    .<span class="call">frame</span>(width: <span class="number">44</span>, height: <span class="number">44</span>)
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/custom-view-styles/cardMirrorImage.png"/><p>No magic yet, let's move on to the next step.</p><h2>The view style protocol</h2><p>With our view declared it's time to define its style, we will call it <code>CardStyle</code>, consistent with SwiftUI's styles naming. <code>CardStyle</code> is going to be the <code>protocol</code> all our styles will conform to.</p><p><code>CardStyle</code> contains only one requirement: a <code>makeBody(configuration:)</code> function returning the original <code>Card</code> with our style applied.</p><p>As we're defining a protocol, we will use an <code>associatedtype</code> to require the function to return a <code>View</code> (we can't directly use generics in protocol definitions):</p><pre><code><span class="keyword">protocol</span> CardStyle {
  <span class="keyword">associatedtype</span> Body: <span class="type">View</span>
  <span class="keyword">typealias</span> Configuration = <span class="type">CardStyleConfiguration</span>

  <span class="keyword">func</span> makeBody(configuration: <span class="type">Self</span>.<span class="type">Configuration</span>) -&gt; <span class="type">Self</span>.<span class="type">Body</span>
}
</code></pre><blockquote><p>Every customizable SwiftUI style follows the same signature as above, here are all of them:<a href="https://developer.apple.com/documentation/swiftui/ButtonStyle"><code>ButtonStyle</code></a>, <a href="https://developer.apple.com/documentation/swiftui/GroupBoxStyle"><code>GroupBoxStyle</code></a>, <a href="https://developer.apple.com/documentation/swiftui/LabelStyle"><code>LabelStyle</code></a>, <a href="https://developer.apple.com/documentation/swiftui/MenuStyle"><code>MenuStyle</code></a>, <a href="https://developer.apple.com/documentation/swiftui/PrimitiveButtonStyle"><code>PrimitiveButtonStyle</code></a>, <a href="https://developer.apple.com/documentation/swiftui/ProgressViewStyle"><code>ProgressViewStyle</code></a>, <a href="https://developer.apple.com/documentation/swiftui/ToggleStyle"><code>ToggleStyle</code></a>.</p></blockquote><p><code>makeBody(configuration:)</code> needs a way to access to the original <code>Card</code> <code>body</code>:<br>this is why the method accepts a <code>CardStyleConfiguration</code> parameter, let's define that next.</p><blockquote><p>The <code>typealias</code> is there mainly to have a consistent signature between all <code>makeBody(configuration:)</code> across all styles of all views. We could remove it, but our style implementations would become more verbose and inconsistent with the rest of SwiftUI styles.</p></blockquote><h2>The view style configuration</h2><p>The style configuration has two main goals:</p><ol><li>give us access to the original view <code>body</code></li><li>give us access to any relevant property of the view</li></ol><p>In <code>CardStyleConfiguration</code> we don't need any relevant property (as it's a very generic view), however here are some examples from other SwiftUI views:</p><ul><li><code>ToggleStyleConfiguration</code> exposes the (<code>Toggle</code>'s) <code>isOn</code> state</li><li><code>ButtonStyleConfiguration</code> exposes the (<code>Button</code>'s) <code>isPressed</code> state</li><li><code>ProgressViewStyleConfiguration</code> exposes the (<code>ProgressView</code>'s) <code>fractionCompleted</code> value</li></ul><p>In most configurations the original <code>body</code> is exposed via a <code>label</code> property, we will follow the same pattern here.</p><p>It's not possible for <code>label</code> (a.k.a. our original <code>Card</code> body) to know in advance what its type is going to be, for this reason <code>label</code> will be exposed as a type-erased <code>View</code> (we cannot store a generic, non-computed <code>some View</code> value):</p><pre><code><span class="keyword">struct</span> CardStyleConfiguration {
  <span class="comment">/// A type-erased label of a Card.</span>
  <span class="keyword">struct</span> Label: <span class="type">View</span> {
    <span class="keyword">init</span>&lt;Content: <span class="type">View</span>&gt;(content: <span class="type">Content</span>) {
      body = <span class="type">AnyView</span>(content)
    }

    <span class="keyword">var</span> body: <span class="type">AnyView</span>
  }

  <span class="keyword">let</span> label: <span class="type">CardStyleConfiguration</span>.<span class="type">Label</span>
}
</code></pre><blockquote><p>Once again, this signature follows other SwiftUI configurations.</p></blockquote><h2>View styles</h2><blockquote><p>Our view is not ready to accept styles yet, however for clarity's sake there will be some images showing the final outcome when the style is applied.</p></blockquote><p>We now have everything we need to start defining our own styles! Let's waste no time:</p><pre><code><span class="keyword">struct</span> RoundedRectangleCardStyle: <span class="type">CardStyle</span> {
  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    configuration.<span class="property">label</span>
      .<span class="call">font</span>(.<span class="dotAccess">title</span>)
      .<span class="call">padding</span>()
      .<span class="call">background</span>(<span class="type">RoundedRectangle</span>(cornerRadius: <span class="number">16</span>).<span class="call">strokeBorder</span>())
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/custom-view-styles/RoundedRectangleCardStyle.png"/><pre><code><span class="keyword">struct</span> CapsuleCardStyle: <span class="type">CardStyle</span> {
  <span class="keyword">var</span> color: <span class="type">Color</span>

  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    configuration.<span class="property">label</span>
      .<span class="call">font</span>(.<span class="dotAccess">title</span>)
      .<span class="call">foregroundColor</span>(.<span class="dotAccess">white</span>)
      .<span class="call">padding</span>()
      .<span class="call">background</span>(
        <span class="type">Capsule</span>().<span class="call">fill</span>(color)
      )
      .<span class="call">background</span>(
        <span class="type">Capsule</span>().<span class="call">fill</span>(color.<span class="call">opacity</span>(<span class="number">0.4</span>)).<span class="call">rotationEffect</span>(.<span class="keyword">init</span>(degrees: -<span class="number">8</span>))
      )
      .<span class="call">background</span>(
        <span class="type">Capsule</span>().<span class="call">fill</span>(color.<span class="call">opacity</span>(<span class="number">0.4</span>)).<span class="call">rotationEffect</span>(.<span class="keyword">init</span>(degrees: <span class="number">4</span>))
      )
      .<span class="call">background</span>(
        <span class="type">Capsule</span>().<span class="call">fill</span>(color.<span class="call">opacity</span>(<span class="number">0.4</span>)).<span class="call">rotationEffect</span>(.<span class="keyword">init</span>(degrees: <span class="number">8</span>))
      )
      .<span class="call">background</span>(
        <span class="type">Capsule</span>().<span class="call">fill</span>(color.<span class="call">opacity</span>(<span class="number">0.4</span>)).<span class="call">rotationEffect</span>(.<span class="keyword">init</span>(degrees: -<span class="number">4</span>))
      )
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/custom-view-styles/CapsuleCardStyle.png"/><pre><code><span class="keyword">struct</span> ShadowCardStyle: <span class="type">CardStyle</span> {
  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    configuration.<span class="property">label</span>
      .<span class="call">font</span>(.<span class="dotAccess">title</span>)
      .<span class="call">foregroundColor</span>(.<span class="dotAccess">black</span>)
      .<span class="call">padding</span>()
      .<span class="call">background</span>(<span class="type">Color</span>.<span class="property">white</span>.<span class="call">cornerRadius</span>(<span class="number">16</span>))
      .<span class="call">shadow</span>(color: <span class="type">Color</span>.<span class="property">black</span>.<span class="call">opacity</span>(<span class="number">0.2</span>), radius: <span class="number">8</span>, x: <span class="number">0</span>, y: <span class="number">4</span>)
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/custom-view-styles/ShadowCardStyle.png"/><pre><code><span class="keyword">struct</span> ColorfulCardStyle: <span class="type">CardStyle</span> {
  <span class="keyword">var</span> color: <span class="type">Color</span>

  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    configuration.<span class="property">label</span>
      .<span class="call">font</span>(.<span class="dotAccess">title</span>)
      .<span class="call">foregroundColor</span>(.<span class="dotAccess">white</span>)
      .<span class="call">shadow</span>(color: <span class="type">Color</span>.<span class="property">white</span>.<span class="call">opacity</span>(<span class="number">0.8</span>), radius: <span class="number">4</span>, x: <span class="number">0</span>, y: <span class="number">2</span>)
      .<span class="call">padding</span>()
      .<span class="call">background</span>(color.<span class="call">cornerRadius</span>(<span class="number">16</span>))
      .<span class="call">shadow</span>(color: color, radius: <span class="number">8</span>, x: <span class="number">0</span>, y: <span class="number">4</span>)
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/custom-view-styles/ColorfulCardStyle.png"/><p>These are just a few examples but <em>sky's the limit</em>: we can always add more <code>CardStyle</code> implementations (or let other developers using <code>Card</code> do so).</p><h3>The default view style</h3><p>We should provide a default style that all <code>Card</code>s will reach for when no other style has been applied.</p><p>We could pick one of the current styles, however we probably would like to have different default styles based on <a href="https://www.fivestars.blog/articles/adaptive-swiftui-views/">the current context</a>, here's an example where we pick a different style based on whether we're on a mac or on an iOS device:</p><pre><code><span class="keyword">struct</span> DefaultCardStyle: <span class="type">CardStyle</span> {
  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="preprocessing">#if os(iOS)</span>
      <span class="keyword">return</span> <span class="type">ShadowCardStyle</span>().<span class="call">makeBody</span>(configuration: configuration)
    <span class="preprocessing">#else</span>
      <span class="keyword">return</span> <span class="type">RoundedRectangleCardStyle</span>().<span class="call">makeBody</span>(configuration: configuration)
    <span class="preprocessing">#endif</span>
  }
}
</code></pre><p>This definition is just a proxy to other styles we have defined earlier, however it shows how we can dynamically change styles if/when necessary.</p><h2>The environment style value</h2><p><code>CardStyle</code> is not directly handed to <code>Card</code> via a parameter: instead, the style is passed down to the view via SwiftUI's environment.</p><p>There are two steps that we must take in order to add <code>CardStyle</code> in the environment:</p><ol><li>Create a card style <a href="https://developer.apple.com/documentation/swiftui/environmentkey"><code>EnvironmentKey</code></a></li><li>Add a card style among the <a href="https://developer.apple.com/documentation/swiftui/environmentvalues"><code>EnvironmentValues</code></a></li></ol><p><code>EnvironmentValues</code> is the collection of environment values that a given view sees, as these values are propagated through the view hierarchy, different views will potentially see different values.</p><p>Internally each value is accessed via an <code>EnvironmentKey</code>, which also declares the default value (and type) associated to the key.</p><p>Let's start by defining our <code>EnvironmentKey</code>, these are the protocol requirements:</p><pre><code><span class="keyword">public protocol</span> EnvironmentKey {
  <span class="keyword">associatedtype</span> Value
  <span class="keyword">static var</span> defaultValue: <span class="type">Self</span>.<span class="type">Value</span> { <span class="keyword">get</span> }
}
</code></pre><p>Ideally we would like to declare the following <code>CardStyle</code> key:</p><pre><code><span class="keyword">struct</span> CardStyleKey: <span class="type">EnvironmentKey</span> {
  <span class="keyword">static var</span> defaultValue: <span class="type">CardStyle</span> = <span class="type">DefaultCardStyle</span>()
}
</code></pre><p>However, similarly to <code>CardStyleConfiguration</code>, we cannot declare a stored property with a protocol type, instead, <code>CardStyleKey</code> needs a concrete type (and value) for its <code>defaultValue</code> property.</p><p><code>CardStyleKey</code> not only will be used to propagate <em>any</em> card style in the view hierarchy, but it also needs to accept <em>any</em> card style as well. The solution lies once more with type erasure, which we apply by defining a new <code>AnyCardStyle</code>:</p><pre><code><span class="keyword">struct</span> AnyCardStyle: <span class="type">CardStyle</span> {
  <span class="keyword">private var</span> _makeBody: (<span class="type">Configuration</span>) -&gt; <span class="type">AnyView</span>

  <span class="keyword">init</span>&lt;S: <span class="type">CardStyle</span>&gt;(style: <span class="type">S</span>) {
    _makeBody = { configuration <span class="keyword">in</span>
      <span class="type">AnyView</span>(style.<span class="call">makeBody</span>(configuration: configuration))
    }
  }

  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">_makeBody</span>(configuration)
  }
}
</code></pre><p><code>AnyCardStyle</code> is a special style that piggy-backs on another <code>CardStyle</code> implementation:<br>to create an <code>AnyCardStyle</code> instance we need to pass another <code>CardStyle</code> instance, which then will be used internally by <code>AnyCardStyle</code> to define its own <code>makeBody(configuration:)</code>.</p><p>In other words, <code>AnyCardStyle</code> is a type-erased <code>CardStyle</code>, which is exactly what we wanted to achieve. Thanks to this new definition we have a concrete <code>CardStyle</code> that we can pass in the environment:</p><pre><code><span class="keyword">struct</span> CardStyleKey: <span class="type">EnvironmentKey</span> {
  <span class="keyword">static var</span> defaultValue = <span class="type">AnyCardStyle</span>(style: <span class="type">DefaultCardStyle</span>())
}
</code></pre><p>We can now add <code>CardStyle</code> among the <code>EnvironmentValues</code>:</p><pre><code><span class="keyword">extension</span> <span class="type">EnvironmentValues</span> {
  <span class="keyword">var</span> cardStyle: <span class="type">AnyCardStyle</span> {
    <span class="keyword">get</span> { <span class="keyword">self</span>[<span class="type">CardStyleKey</span>.<span class="keyword">self</span>] }
    <span class="keyword">set</span> { <span class="keyword">self</span>[<span class="type">CardStyleKey</span>.<span class="keyword">self</span>] = newValue }
  }
}
</code></pre><p>With this extension our environment setup is complete!</p><h2>The convenience view modifier</h2><p>Thanks to our new definitions we can set <code>cardStyle</code> anywhere by using the <a href="https://developer.apple.com/documentation/swiftui/view/environment(_:_:)"><code>environment(_:_:)</code></a> view modifier:</p><pre><code><span class="type">MyView</span>()
  .<span class="call">environment</span>(\.<span class="property">cardStyle</span>, <span class="type">AnyCardStyle</span>(style: <span class="type">BorderCardStyle</span>(color: .<span class="dotAccess">blue</span>)))
</code></pre><p>And we can read it in any view via the <a href="https://developer.apple.com/documentation/swiftui/environment"><code>@Environment</code></a> property wrapper:</p><pre><code><span class="type">MyView</span>: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">cardStyle</span>) <span class="keyword">var</span> cardStyle

  ...
}
</code></pre><p>While the reading side is great, the writing side requires developers to know about our <code>AnyCardStyle</code> type erasure, which really is an implementation detail.</p><p>This is one of the reasons why SwiftUI defines some <code>xxxStyle(_:)</code> convenience view modifiers (e.g. <code>myView.buttonStyle(BorderlessButtonStyle())</code>), let's do the same for our style:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">func</span> cardStyle&lt;S: <span class="type">CardStyle</span>&gt;(<span class="keyword">_</span> style: <span class="type">S</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">environment</span>(\.<span class="property">cardStyle</span>, <span class="type">AnyCardStyle</span>(style: style))
  }
}
</code></pre><p>Thanks to this new view modifier, which can applied to any view, we can set the <code>cardStyle</code> environment via:</p><pre><code><span class="type">MyView</span>()
  .<span class="call">cardStyle</span>(<span class="type">ColorfulCardStyle</span>(color: .<span class="dotAccess">red</span>))
</code></pre><p>Which is more concise, clear, and doesn't require knowing any details of the original implementation.</p><h2>Updating our view</h2><p>The full infrastructure is complete, the last thing left is to make sure the correct style is applied to our view, here's how we can update our <code>Card</code> definition:</p><pre><code><span class="keyword">struct</span> Card&lt;Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">cardStyle</span>) <span class="keyword">var</span> style
  <span class="keyword">var</span> content: () -&gt; <span class="type">Content</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    style
      .<span class="call">makeBody</span>(
        configuration: <span class="type">CardStyleConfiguration</span>(
          label: <span class="type">CardStyleConfiguration</span>.<span class="type">Label</span>(content: <span class="call">content</span>())
        )
      )
  }
}
</code></pre><p>Beside fetching the card style from the environment, the view body has completely changed:<br>instead of <code>content()</code>, <code>body</code> now returns the environment style's <code>makeBody(configuration:)</code> output, where we pass the original body <code>content()</code> as a parameter of the style configuration.</p><p>With this last step we're now ready to use all our styles in any <code>Card</code> we'd like in any way we please, here's the code for the sneak peek at the beginning of the article:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollView</span> {
      <span class="type">LazyVStack</span> {
        <span class="comment">// Default style</span>
        <span class="type">Section</span> {
          sectionContent
        }

        <span class="comment">// RoundedRectangleCardStyle</span>
        <span class="type">Section</span> {
          sectionContent
        }
        .<span class="call">cardStyle</span>(<span class="type">RoundedRectangleCardStyle</span>())

        <span class="comment">// CapsuleCardStyle - green</span>
        <span class="type">Section</span> {
          sectionContent
        }
        .<span class="call">cardStyle</span>(<span class="type">CapsuleCardStyle</span>(color: .<span class="dotAccess">green</span>))

        <span class="comment">// CapsuleCardStyle - blue</span>
        <span class="type">Section</span> {
          sectionContent
        }
        .<span class="call">cardStyle</span>(<span class="type">CapsuleCardStyle</span>(color: .<span class="dotAccess">blue</span>))

        <span class="comment">// ColorfulCardStyle - purple</span>
        <span class="type">Section</span> {
          sectionContent
        }
        .<span class="call">cardStyle</span>(<span class="type">ColorfulCardStyle</span>(color: .<span class="dotAccess">purple</span>))

        <span class="comment">// ColorfulCardStyle - pink</span>
        <span class="type">Section</span> {
          sectionContent
        }
        .<span class="call">cardStyle</span>(<span class="type">ColorfulCardStyle</span>(color: .<span class="dotAccess">pink</span>))

        <span class="comment">// ColorfulCardStyle - red</span>
        <span class="type">Section</span> {
          sectionContent
        }
        .<span class="call">cardStyle</span>(<span class="type">ColorfulCardStyle</span>(color: .<span class="dotAccess">red</span>))
      }
    }
  }

  <span class="keyword">var</span> sectionContent: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollView</span>(.<span class="dotAccess">horizontal</span>) {
      <span class="type">LazyHStack</span> {
        <span class="type">ForEach</span>(<span class="number">1</span>..&lt;<span class="number">5</span>) { <span class="keyword">_ in</span>
          <span class="type">Card</span> {
            <span class="type">Text</span>(verbatim: <span class="string">"Five Stars"</span>)
          }
        }
      }
      .<span class="call">padding</span>()
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/custom-view-styles/final.png"/><h2>Steps recap</h2><p>It took us a while to complete the whole picture, but both the final API and data flow matches SwiftUI's without compromises.</p><p>In short, these are the steps we took:</p><ol><li>Create view</li><li>Create view style protocol</li><li>Create style configuration</li><li>Implement base view styles</li><li>Define view default style</li><li>Setup style environment (key + environment value + style eraser)</li><li>Define <code>.xxxStyle(_:)</code> convenience view modifier</li><li>Update view to take advantage of environment style</li></ol><p>The final gist, with the steps above highlighted along the way, can be found <a href="https://github.com/zntfdr/FiveStarsCodeSamples/tree/main/Custom-SwiftUI-Styles">here</a>.</p><h2>Conclusions</h2><p>Regardless of whether we're planning to build our own styles or not, it's hard to not appreciate how much work is hidden behind just an <code>@Environment</code> property wrapper and a <code>.xxxStyle(_:)</code> view modifier:<br>at the end of the day this is all developers using SwiftUI's styles (or ours) will see, and really this is all they should need to know.</p><p>Have you built or are you planning to build a component with styles this way? Have you see any other interesting approaches? <a href="https://twitter.com/zntfdr">I'd love to hear</a>!</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles.</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/button-styles</guid><title>Exploring SwiftUI's Button styles</title><description></description><link>https://www.fivestars.blog/articles/button-styles</link><pubDate>Tue, 26 Jan 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p><a href="https://developer.apple.com/documentation/swiftui/button"><code>Button</code></a> is no doubt one of the most popular SwiftUI elements, it's also very special, as it is the only component with two different style protocols: <a href="https://developer.apple.com/documentation/swiftui/buttonstyle"><code>ButtonStyle</code></a> and <a href="https://developer.apple.com/documentation/swiftui/primitivebuttonstyle"><code>PrimitiveButtonStyle</code></a>.</p><p>In this article, let's explore everything there's to know about button styling, and more.</p><blockquote><p>As usual the focus is on iOS, the same concepts can be applied to all platforms with little change.</p></blockquote><h2>Starting up</h2><p>SwiftUI comes with three built-in styles: <a href="https://developer.apple.com/documentation/swiftui/defaultbuttonstyle"><code>DefaultButtonStyle</code></a>, <a href="https://developer.apple.com/documentation/swiftui/borderlessbuttonstyle"><code>BorderlessButtonStyle</code></a>, and <a href="https://developer.apple.com/documentation/swiftui/plainbuttonstyle"><code>PlainButtonStyle</code></a>.</p><p>When declaring a simple button, <a href="https://developer.apple.com/documentation/swiftui/defaultbuttonstyle"><code>DefaultButtonStyle</code></a> is applied:</p><pre><code><span class="type">Button</span>(<span class="string">"Simple button"</span>) { 
  <span class="comment">// button tapped</span>
  ...
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/button-styles/simplebutton.gif"/><p><code>DefaultButtonStyle</code> is not a style per se: it's our way to let SwiftUI pick the style for us (based on the context, platform, parent views, and more).</p><p>The actual <em>default</em> style is <a href="https://developer.apple.com/documentation/swiftui/borderlessbuttonstyle"><code>BorderlessButtonStyle</code></a>, which applies a blue tint on top of our button, or the app accent color if we're on iOS 14, along with some visual effects when tapped, focused, etc.</p><p>Unless we are in an exception (I haven't found one yet, <a href="https://twitter.com/zntfdr">please let me know if you do</a>), the following three declarations are equivalent:</p><pre><code><span class="type">Button</span>(<span class="string">"Simple button"</span>) { 
  ...
}

<span class="type">Button</span>(<span class="string">"Simple button"</span>) { 
  ...
}
.<span class="call">buttonStyle</span>(<span class="type">DefaultButtonStyle</span>())

<span class="type">Button</span>(<span class="string">"Simple button"</span>) { 
  ...
}
.<span class="call">buttonStyle</span>(<span class="type">BorderlessButtonStyle</span>())
</code></pre><img src="https://www.fivestars.blog/assets/posts/button-styles/simplebutton.gif"/><blockquote><p>In iOS 13 the (blue) tint is applied to images declared within our button <code>label</code>, to avoid so we need to either add a rendering modifier to our images (e.g. <code>Image("image").renderingMode(.original)</code>) or declare <a href="https://developer.apple.com/library/archive/documentation/Xcode/Reference/xcode_ref-Asset_Catalog_Format/ImageSetType.html">the proper rendering in the image asset catalog</a>.From iOS 14 only template images will be tinted by default.</p></blockquote><p>Lastly, SwiftUI offers <a href="https://developer.apple.com/documentation/swiftui/plainbuttonstyle"><code>PlainButtonStyle</code></a>, which displays the button label without a tint, but still applies visual effects in different states:</p><pre><code><span class="type">Button</span>(<span class="string">"Plain style button"</span>) { 
  <span class="comment">// button tapped</span>
  ...
}
.<span class="call">buttonStyle</span>(<span class="type">PlainButtonStyle</span>())
</code></pre><img src="https://www.fivestars.blog/assets/posts/button-styles/plainbutton.gif"/><p>These are all the styles SwiftUI provides us in iOS: thankfully we can create new ones with <a href="https://developer.apple.com/documentation/swiftui/buttonstyle"><code>ButtonStyle</code></a> and <a href="https://developer.apple.com/documentation/swiftui/primitivebuttonstyle"><code>PrimitiveButtonStyle</code></a>, let's start with <code>ButtonStyle</code>.</p><h2>ButtonStyle</h2><p><a href="https://developer.apple.com/documentation/swiftui/button">The documentation</a> suggests us to use <code>ButtonStyle</code> when we're declaring the button appearance ourselves, but the button interaction behaves as any other standard button (a.k.a. its action is triggered when tapped).</p><pre><code><span class="keyword">public protocol</span> ButtonStyle {
  <span class="keyword">associatedtype</span> Body: <span class="type">View</span>

  <span class="keyword">func</span> makeBody(configuration: <span class="type">Self</span>.<span class="type">Configuration</span>) -&gt; <span class="type">Self</span>.<span class="type">Body</span>

  <span class="keyword">typealias</span> Configuration = <span class="type">ButtonStyleConfiguration</span>
}
</code></pre><p>The only requirement of <code>ButtonStyle</code> is to return a view from <code>makeBody(configuration:)</code>, the function takes in a <a href="https://developer.apple.com/documentation/swiftui/buttonstyleconfiguration"><code>ButtonStyleConfiguration</code></a> instance:</p><pre><code><span class="keyword">public struct</span> ButtonStyleConfiguration {
  <span class="keyword">public let</span> label: <span class="type">ButtonStyleConfiguration</span>.<span class="type">Label</span>
  <span class="keyword">public let</span> isPressed: <span class="type">Bool</span>
}
</code></pre><p>This configuration comes with two properties:</p><ul><li><code>label</code> is the button <code>label</code>, for example if our button is <code>Button(action: {}, label: { Text("Hello world") })</code>, then <code>Text("Hello world")</code> will be our <code>label</code></li></ul><ul><li><code>isPressed</code> is the current state of the button, which can be used in <code>ButtonStyle</code>'s <code>makeBody(configuration:)</code> for visual effects</li></ul><p>Let's define a few examples:</p><pre><code><span class="keyword">struct</span> RoundedRectangleButtonStyle: <span class="type">ButtonStyle</span> {
  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">HStack</span> {
      <span class="type">Spacer</span>()
      configuration.<span class="property">label</span>.<span class="call">foregroundColor</span>(.<span class="dotAccess">black</span>)
      <span class="type">Spacer</span>()
    }
    .<span class="call">padding</span>()
    .<span class="call">background</span>(<span class="type">Color</span>.<span class="property">yellow</span>.<span class="call">cornerRadius</span>(<span class="number">8</span>))
    .<span class="call">scaleEffect</span>(configuration.<span class="property">isPressed</span> ? <span class="number">0.95</span> : <span class="number">1</span>)
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/button-styles/roundedRectangle.gif"/><pre><code><span class="keyword">struct</span> ShadowButtonStyle: <span class="type">ButtonStyle</span> {
  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    configuration.<span class="property">label</span>
      .<span class="call">shadow</span>(
        color: configuration.<span class="property">isPressed</span> ? <span class="type">Color</span>.<span class="property">blue</span> : <span class="type">Color</span>.<span class="property">black</span>,
        radius: <span class="number">4</span>, x: <span class="number">0</span>, y: <span class="number">5</span>
      )
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/button-styles/shadow.gif"/><p>Note how these new buttons do not come with the default effects when tapped, focused, etc: it's now up to us to add such effects in out buttons.</p><p>This is all there is to know about <code>ButtonStyle</code>. It lets us customize any button appearance, the main advantages are:</p><ul><li>can apply the same style to multiple buttons without code duplication</li><li>access to the <code>isPressed</code> event</li><li>keeps the standard interaction/behavior</li></ul><h3>Applying and composing multiple styles</h3><p><code>Button</code> doesn't have an initializer accepting a <code>ButtonStyleConfiguration</code> instance (FB8979053), making things complicated when composing multiple styles together.<br><br>As per our current declarations, applying multiple <code>ButtonStyle</code>s has no effect, and only the closest style will be used (the <code>makeBody(configuration:)</code> of other styles won't even be called):</p><pre><code><span class="comment">// Only RoundedRectangleButtonStyle is applied</span>
<span class="type">Button</span>(<span class="string">"Rounded rectangle button style"</span>) {
  <span class="comment">// button tapped</span>
  ...
}
.<span class="call">buttonStyle</span>(<span class="type">RoundedRectangleButtonStyle</span>())
.<span class="call">buttonStyle</span>(<span class="type">ShadowButtonStyle</span>())
.<span class="call">buttonStyle</span>(<span class="type">BorderlessButtonStyle</span>())
.<span class="call">buttonStyle</span>(<span class="type">DefaultButtonStyle</span>())
</code></pre><img src="https://www.fivestars.blog/assets/posts/button-styles/roundedRectangle.gif"/><p>A "workaround" for this limitation is to return a new <code>Button</code> in our <code>ButtonStyle</code> <code>makeBody(configuration:)</code> function, for example we could update <code>RoundedRectangleButtonStyle</code> as following:</p><pre><code><span class="keyword">struct</span> RoundedRectangleButtonStyle: <span class="type">ButtonStyle</span> {
  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(action: {}, label: {
      <span class="type">HStack</span> {
        <span class="type">Spacer</span>()
        configuration.<span class="property">label</span>.<span class="call">foregroundColor</span>(.<span class="dotAccess">black</span>)
        <span class="type">Spacer</span>()
      }
    })
    <span class="comment">// 👇🏻 makes all taps go to the original button</span>
    .<span class="call">allowsHitTesting</span>(<span class="keyword">false</span>)
    .<span class="call">padding</span>()
    .<span class="call">background</span>(<span class="type">Color</span>.<span class="property">yellow</span>.<span class="call">cornerRadius</span>(<span class="number">8</span>))
    .<span class="call">scaleEffect</span>(configuration.<span class="property">isPressed</span> ? <span class="number">0.95</span> : <span class="number">1</span>)
  }
}
</code></pre><p>With this new definition the previous example <em>almost</em> works:</p><pre><code><span class="type">Button</span>(<span class="string">"Rounded rectangle + shadow button style"</span>) {
  <span class="comment">// button tapped</span>
  ...
}
.<span class="call">buttonStyle</span>(<span class="type">RoundedRectangleButtonStyle</span>())
.<span class="call">buttonStyle</span>(<span class="type">ShadowButtonStyle</span>())
</code></pre><img src="https://www.fivestars.blog/assets/posts/button-styles/roundedShadow.gif"/><p>The main drawback is that further styles are applied to fictitious and not tappable buttons, thus not receiving any <code>isPressed</code> events.</p><p>There are more advanced solutions for these drawbacks, but at this point we're fighting the framework:<br>instead, it's better to consider each <code>.buttonStyle(..)</code> as a complete override of previous ones.</p><p>An easy fix for such limitations, at least for the moment, is to create and use a new style which combines the desired effects, for example:</p><pre><code><span class="keyword">struct</span> RoundedRectangleWithShadowedLabelButtonStyle: <span class="type">ButtonStyle</span> {
  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">HStack</span> {
      <span class="type">Spacer</span>()
      configuration.<span class="property">label</span>.<span class="call">foregroundColor</span>(.<span class="dotAccess">black</span>)
        .<span class="call">shadow</span>(
          color: configuration.<span class="property">isPressed</span> ? <span class="type">Color</span>.<span class="property">red</span> : <span class="type">Color</span>.<span class="property">black</span>,
          radius: <span class="number">4</span>, x: <span class="number">0</span>, y: <span class="number">5</span>
        )
      <span class="type">Spacer</span>()
    }
    .<span class="call">padding</span>()
    .<span class="call">background</span>(<span class="type">Color</span>.<span class="property">yellow</span>.<span class="call">cornerRadius</span>(<span class="number">8</span>))
    .<span class="call">scaleEffect</span>(configuration.<span class="property">isPressed</span> ? <span class="number">0.95</span> : <span class="number">1</span>)
  }
}
</code></pre><p>Which we can use as following:</p><pre><code><span class="type">Button</span>(<span class="string">"Rounded rectangle + shadow button style"</span>) {
  <span class="comment">// button tapped</span>
  ...  
}
.<span class="call">buttonStyle</span>(<span class="type">RoundedRectangleWithShadowedLabelButtonStyle</span>())
</code></pre><img src="https://www.fivestars.blog/assets/posts/button-styles/roundedShadow2.gif"/><blockquote><p>A kind reminder that this is a technical article, not a design article.</p></blockquote><h2>PrimitiveButtonStyle</h2><p>While <code>ButtonStyle</code> was all about customizing the appearance and keeping the standard interaction behavior, <code>PrimitiveButtonStyle</code> lets us customize both, meaning that it's up to us to define the button appearance <strong>and</strong> to decide when and how to trigger the button action.</p><p>The <code>PrimitiveButtonStyle</code> definition is nearly identical to <code>ButtonStyle</code>:</p><pre><code><span class="keyword">public protocol</span> PrimitiveButtonStyle {
    <span class="keyword">associatedtype</span> Body : <span class="type">View</span>

    <span class="keyword">func</span> makeBody(configuration: <span class="type">Self</span>.<span class="type">Configuration</span>) -&gt; <span class="type">Self</span>.<span class="type">Body</span>

    <span class="keyword">typealias</span> Configuration = <span class="type">PrimitiveButtonStyleConfiguration</span>
}
</code></pre><p>The only difference stands in the <code>makeBody(configuration:)</code> parameter, which is now a <a href="https://developer.apple.com/documentation/swiftui/primitivebuttonstyleconfiguration"><code>PrimitiveButtonStyleConfiguration</code></a> type:</p><pre><code><span class="keyword">public struct</span> PrimitiveButtonStyleConfiguration {
  <span class="keyword">public let</span> label: <span class="type">PrimitiveButtonStyleConfiguration</span>.<span class="type">Label</span>
  <span class="keyword">public func</span> trigger()
}
</code></pre><p>This configuration once again comes with the button <code>label</code> as a property, however <code>isPressed</code> is now replaced by a <code>trigger()</code> function:<br>invoking <code>trigger()</code> is how we call the button action, and it's now up to us to define the right time to do so.</p><p>For example, if we would like a button to trigger only when double tapped, we could define the following style:</p><pre><code><span class="keyword">struct</span> DoubleTapOnlyStyle: <span class="type">PrimitiveButtonStyle</span> {
  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    configuration.<span class="property">label</span>
      .<span class="call">onTapGesture</span>(count: <span class="number">2</span>, perform: configuration.<span class="property">trigger</span>)
  }
}
</code></pre><p>Which we can then use like any other style:</p><pre><code><span class="type">Button</span>(<span class="string">"Double tap me"</span>) {
  <span class="comment">// button double tapped</span>
  ...  
}
.<span class="call">buttonStyle</span>(<span class="type">DoubleTapOnlyStyle</span>())
</code></pre><img src="https://www.fivestars.blog/assets/posts/button-styles/doubletap.gif"/><h3>Applying and composing multiple (primitive) styles</h3><p>Unlike for <code>ButtonStyleConfiguration</code>, <code>Button</code> does have an initializer accepting a <code>PrimitiveButtonStyleConfiguration</code> instance, allowing us to compose/apply multiple (primitive) styles to the same button.</p><p>For example, consider the following styles:</p><pre><code><span class="comment">// The button action triggers on double taps.</span>
<span class="keyword">struct</span> DoubleTapStyle: <span class="type">PrimitiveButtonStyle</span> {
  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(configuration) <span class="comment">// &lt;- Button instead of configuration.label</span>
      .<span class="call">onTapGesture</span>(count: <span class="number">2</span>, perform: configuration.<span class="property">trigger</span>)
  }
}

<span class="comment">// The button action triggers on swipes.
// (even when terminated outside the button)</span>
<span class="keyword">struct</span> SwipeButtonStyle: <span class="type">PrimitiveButtonStyle</span> {
  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(configuration)
      .<span class="call">gesture</span>(
        <span class="type">DragGesture</span>()
          .<span class="call">onEnded</span> { <span class="keyword">_ in</span>
            configuration.<span class="call">trigger</span>()
          }
      )
  }
}
</code></pre><p>As each style returns a button, they can be combined and work together no problem:</p><pre><code><span class="type">Button</span>(
  <span class="string">"Double tap or swipe"</span>, 
  action: { 
    <span class="comment">// handle action here</span>
    ...
  }
)
.<span class="call">buttonStyle</span>(<span class="type">DoubleTapStyle</span>())
.<span class="call">buttonStyle</span>(<span class="type">SwipeButtonStyle</span>())
</code></pre><p>There's a small side-effect of this approach:<br><code>Button(configuration)</code> comes with the default button interaction and style, thankfully we can remove both of these by defining yet another "plain" style.</p><pre><code><span class="keyword">struct</span> PlainNoTapStyle: <span class="type">PrimitiveButtonStyle</span> {
  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(configuration)
      .<span class="call">buttonStyle</span>(<span class="type">PlainButtonStyle</span>()) <span class="comment">// removes any default appearance</span>
      .<span class="call">allowsHitTesting</span>(<span class="keyword">false</span>)         <span class="comment">// no more triggers on taps</span>
      .<span class="call">contentShape</span>(<span class="type">Rectangle</span>())       <span class="comment">// let other interactions work</span>
  }
}
</code></pre><p>If we now add this style to our button definition, we will truly make it work with just double taps and swipes:</p><pre><code><span class="type">Button</span>(
  <span class="string">"Double tap or swipe"</span>, 
  action: {
    <span class="comment">// handle action here</span>
    ...
  }
)
.<span class="call">buttonStyle</span>(<span class="type">DoubleTapStyle</span>())
.<span class="call">buttonStyle</span>(<span class="type">SwipeButtonStyle</span>())
.<span class="call">buttonStyle</span>(<span class="type">PlainNoTapStyle</span>())
</code></pre><img src="https://www.fivestars.blog/assets/posts/button-styles/gest.gif"/><p>However we'd probably want to keep the single tap default interaction enabled to most buttons.</p><h2>Using PrimitiveButtonStyle and ButtonStyle</h2><p>We've covered how each <code>ButtonStyle</code> can be considered a complete override of previous styles, while <code>PrimitiveButtonStyle</code> allows us to compose multiple styles (when defined correctly), what about combining the two?</p><p>We can apply and have active both a <code>ButtonStyle</code> and (multiple) <code>PrimitiveButtonStyle</code> at the same time, for example:</p><pre><code><span class="type">Button</span>(
  <span class="string">"Primitive + button style"</span>, 
  action: { 
    <span class="comment">// handle action here</span>
    ...
  }
)
<span class="comment">// 👇🏻 triggers the button even when dragging our finger out of the button</span>
.<span class="call">buttonStyle</span>(<span class="type">SwipeButtonStyle</span>()) 
.<span class="call">buttonStyle</span>(<span class="type">RoundedRectangleButtonStyle</span>())
</code></pre><img src="https://www.fivestars.blog/assets/posts/button-styles/combo.gif"/><p>In these situations it's important to have the <code>ButtonStyle</code> (<code>RoundedRectangleButtonStyle</code> above) declared last, as otherwise it would <em>erase</em> the <code>PrimitiveButtonStyle</code> as well.</p><p>Note that our <code>ButtonStyle</code> will only receive the <code>isPressed</code> event on the standard tap gesture, it won't know when the button action is triggered thanks to any other <code>PrimitiveButtonStyle</code>: it's our duty to define any visual clue on those styles when/if needed.</p><h2>Conclusions</h2><p>Buttons are the SwiftUI component with the simplest interaction: tap to trigger them.</p><p>In this article we've seen how we can turn any button into much more advanced elements with completely different appearances and gestures:<br>we won't need to go beyond a custom <a href="https://developer.apple.com/documentation/swiftui/buttonstyle"><code>ButtonStyle</code></a> most of the time, but it's always good to know that more powerful tools are there when needed.</p><p>Have you ever extended a button interaction? What other uses/needs do you see for such styles? <a href="https://twitter.com/zntfdr">Please let me know!</a></p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles.</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-patterns-bindings</guid><title>SwiftUI patterns: @Bindings</title><description></description><link>https://www.fivestars.blog/articles/swiftui-patterns-bindings</link><pubDate>Tue, 19 Jan 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Every time we create a new view, we're defining a new API: regardless of whether those views will be used just by us or by several people around the world, it's very important to keep all definitions consistent.</p><p>More importantly, our views signature should follow SwiftUI's own definitions and patterns:<br>doing so ensures that using our views feels natural, as anyone familiar with SwiftUI will automatically be familiar with our definitions.</p><p>In order to achieve this, we need to analyze and understand SwiftUI's APIs: let's start by exploring SwiftUI's use of <a href="https://developer.apple.com/documentation/swiftui/binding">@Binding</a> in views initializers!</p><blockquote><p>This article contains quite a bit of definitions and examples, if you'd rather have a TL:DR; check out the "Main takeaways" chapter at the bottom.</p></blockquote><h2>Value: Binding&lt;V&gt;</h2><p>Used in:</p><ul><li><a href="https://developer.apple.com/documentation/swiftui/slider"><code>Slider</code></a> (six initializers), <code>V</code> must conform to <a href="https://developer.apple.com/documentation/swift/binaryfloatingpoint"><code>BinaryFloatingPoint</code></a><ul><li><a href="https://developer.apple.com/documentation/swiftui/stepper"><code>Stepper</code></a> (six initializers), <code>V</code> must conform to <a href="https://developer.apple.com/documentation/swift/Strideable"><code>Strideable</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/textfield"><code>TextField</code></a> (two initializers)</li></ul></li></ul><p>Examples:</p><pre><code><span class="comment">// MARK: Slider

// Definition</span>
<span class="keyword">extension</span> <span class="type">Slider</span> {
  <span class="keyword">public init</span>&lt;V&gt;(
    value: <span class="type">Binding</span>&lt;<span class="type">V</span>&gt;, 
    <span class="keyword">in</span> bounds: <span class="type">ClosedRange</span>&lt;<span class="type">V</span>&gt; = <span class="number">0</span>...<span class="number">1</span>, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }
  ) <span class="keyword">where</span> <span class="type">V</span>: <span class="type">BinaryFloatingPoint</span>, <span class="type">V</span>.<span class="type">Stride</span>: <span class="type">BinaryFloatingPoint</span>
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> value: <span class="type">Float</span> = <span class="number">0</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Slider</span>(value: $value)
  }
}

<span class="comment">// MARK: Stepper

// Definition</span>
<span class="keyword">extension</span> <span class="type">Stepper</span> {
  <span class="keyword">public init</span>&lt;V: <span class="type">Strideable</span>&gt;(
    <span class="keyword">_</span> titleKey: <span class="type">LocalizedStringKey</span>, 
    value: <span class="type">Binding</span>&lt;<span class="type">V</span>&gt;, 
    step: <span class="type">V</span>.<span class="type">Stride</span> = <span class="number">1</span>, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }
  )
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> value: <span class="type">Float</span> = <span class="number">0</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Stepper</span>(<span class="string">"Stepper title"</span>, value: $value)
  }
}

<span class="comment">// MARK: TextField

// Definition</span>
<span class="keyword">extension</span> <span class="type">TextField</span> {
  <span class="keyword">public init</span>&lt;S: <span class="type">StringProtocol</span>, T&gt;(
    <span class="keyword">_</span> title: <span class="type">S</span>, 
    value: <span class="type">Binding</span>&lt;<span class="type">T</span>&gt;, 
    formatter: <span class="type">Formatter</span>, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }, 
    onCommit: <span class="keyword">@escaping</span> () -&gt; <span class="type">Void</span> = {}
  )
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> nameComponents = <span class="type">PersonNameComponents</span>()

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TextField</span>(
      <span class="string">"Person name:"</span>,
      value: $nameComponents,
      formatter: <span class="type">PersonNameComponentsFormatter</span>()
    )
  }
}
</code></pre><p>Notes:</p><ul><li>the <code>value</code> <code>Binding</code> is always associated with generic types</li><li>the <code>value</code> <code>Binding</code> may (or not) require a protocol conformance to its associated generic type</li><li>the <code>value</code> <code>Binding</code> parameter always comes either first or second, right after the view <code>title</code></li><li>Both <code>Slider</code> and <code>Stepper</code> use <code>V</code> as their generic type, probably as a reference to "<strong>V</strong>alue"</li><li><code>TextField</code> uses <code>T</code> as its generic type, probably as a reference to "arbitrary <strong>T</strong>ype" (this is inconsistent with the views above, FB8972305)</li></ul><h2>Text: Binding&lt;String&gt;</h2><p>Used in:</p><ul><li><a href="https://developer.apple.com/documentation/swiftui/securefield"><code>SecureField</code></a> (two initializers)</li><li><a href="https://developer.apple.com/documentation/swiftui/texteditor"><code>TextEditor</code></a> (one initializer)</li><li><a href="https://developer.apple.com/documentation/swiftui/textfield"><code>TextField</code></a> (one initializer)</li></ul><p>Examples:</p><pre><code><span class="comment">// MARK: SecureField

// Definition</span>
<span class="keyword">extension</span> <span class="type">SecureField</span> {
  <span class="keyword">public init</span>(
    <span class="keyword">_</span> titleKey: <span class="type">LocalizedStringKey</span>, 
    text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;, 
    onCommit: <span class="keyword">@escaping</span> () -&gt; <span class="type">Void</span> = {}
  )
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> password = <span class="string">""</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TextField</span>(<span class="string">"Password:"</span>, text: $password)
  }
}

<span class="comment">// MARK: TextEditor

// Definition</span>
<span class="keyword">extension</span> <span class="type">TextEditor</span> {
  <span class="keyword">public init</span>(text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;)
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> text = <span class="string">""</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TextEditor</span>(text: $text)
  }
}

<span class="comment">// MARK: TextField

// Definition</span>
<span class="keyword">extension</span> <span class="type">TextField</span> {
  <span class="keyword">public init</span>(
    <span class="keyword">_</span> titleKey: <span class="type">LocalizedStringKey</span>, 
    text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;, 
    onEditingChanged: <span class="keyword">@escaping</span> (<span class="type">Bool</span>) -&gt; <span class="type">Void</span> = { <span class="keyword">_ in</span> }, 
    onCommit: <span class="keyword">@escaping</span> () -&gt; <span class="type">Void</span> = {}
  )
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> name = <span class="string">""</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TextField</span>(<span class="string">"Name:"</span>, text: $name)
  }
}
</code></pre><p>Notes:</p><ul><li>the <code>text</code> <code>Binding</code> parameter is always associated with <code>String</code></li><li>the <code>text</code> <code>Binding</code> parameter always comes either first or second, right after the view <code>title</code></li></ul><h2>Selection: Binding</h2><p>The <code>selection: Binding&lt;...&gt;</code> pattern is the most common pattern found in SwiftUI, for this reason the section has been further split in multiple chapters.</p><h3>Non-optional binding, non-optional type</h3><p>Used in:</p><ul><li><a href="https://developer.apple.com/documentation/swiftui/colorpicker"><code>ColorPicker</code></a> (six initializers), <code>Binding</code> with either <code>Color</code> or <code>CGColor</code></li><li><a href="https://developer.apple.com/documentation/swiftui/datepicker"><code>DatePicker</code></a> (twelve initializers), <code>Binding</code> with <code>Date</code></li><li><a href="https://developer.apple.com/documentation/swiftui/picker"><code>Picker</code></a> (three initializers), <code>Binding</code> with a generic <a href="https://developer.apple.com/documentation/swift/hashable"><code>Hashable</code></a> <code>SelectionValue</code></li></ul><p>Examples:</p><pre><code><span class="comment">// MARK: ColorPicker

// Definition</span>
<span class="keyword">extension</span> <span class="type">ColorPicker</span> {
  <span class="keyword">public init</span>(
    <span class="keyword">_</span> titleKey: <span class="type">LocalizedStringKey</span>, 
    selection: <span class="type">Binding</span>&lt;<span class="type">Color</span>&gt;, 
    supportsOpacity: <span class="type">Bool</span> = <span class="keyword">true</span>
  )
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> color: <span class="type">Color</span> = .<span class="dotAccess">yellow</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ColorPicker</span>(<span class="string">"Choose color:"</span>, selection: $color)
  }
}

<span class="comment">// MARK: DatePicker

// Definition</span>
<span class="keyword">extension</span> <span class="type">DatePicker</span> {
  <span class="keyword">public init</span>(
    <span class="keyword">_</span> titleKey: <span class="type">LocalizedStringKey</span>, 
    selection: <span class="type">Binding</span>&lt;<span class="type">Date</span>&gt;, 
    displayedComponents: <span class="type">DatePicker</span>&lt;<span class="type">Label</span>&gt;.<span class="type">Components</span> = [.<span class="dotAccess">hourAndMinute</span>, .<span class="dotAccess">date</span>]
  )
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> date = <span class="type">Date</span>()

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">DatePicker</span>(<span class="string">"Your birthday:"</span>, selection: $date)
  }
}

<span class="comment">// MARK: Picker

// Definition</span>
<span class="keyword">extension</span> <span class="type">Picker</span> {
  <span class="keyword">public init</span>(
    selection: <span class="type">Binding</span>&lt;<span class="type">SelectionValue</span>: <span class="type">Hashable</span>&gt;, 
    label: <span class="type">Label</span>, 
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>
  )
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">enum</span> PizzaTopping: <span class="type">String</span>, <span class="type">CaseIterable</span>, <span class="type">Identifiable</span> {
    <span class="keyword">case</span> 🍍, 🍄, 🫒, 🐓
    <span class="keyword">var</span> id: <span class="type">String</span> { rawValue }
  }

  <span class="keyword">@State var</span> pizzaTopping: <span class="type">PizzaTopping</span> = .🍍

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Picker</span>(
      selection: $pizzaTopping,
      label: <span class="type">Text</span>(<span class="string">"Best pizza topping:"</span>)
    ) {
      <span class="type">ForEach</span>(<span class="type">PizzaTopping</span>.<span class="property">allCases</span>) { flavor <span class="keyword">in</span>
        <span class="type">Text</span>(flavor.<span class="property">rawValue</span>)
      }
    }
  }
}
</code></pre><p>Notes:</p><ul><li>all non-optional <code>selection</code> <code>Binding</code> with non-optional types are used exclusively by (all) SwiftUI pickers</li><li>the <code>selection</code> <code>Binding</code> parameter always comes either first or second, right after the picker <code>title</code></li></ul><h3>Non-optional binding, optional type</h3><p>Used in:</p><ul><li><a href="https://developer.apple.com/documentation/swiftui/navigationlink"><code>NavigationLink</code></a> (three initializers), <code>Binding</code> with a generic <a href="https://developer.apple.com/documentation/swift/hashable"><code>Hashable</code></a> <code>V?</code> type</li></ul><p>Examples:</p><pre><code><span class="comment">// Definition</span>
<span class="keyword">extension</span> <span class="type">NavigationLink</span> {
  <span class="keyword">public init</span>&lt;V: <span class="type">Hashable</span>&gt;(
    destination: <span class="type">Destination</span>, 
    tag: <span class="type">V</span>, 
    selection: <span class="type">Binding</span>&lt;<span class="type">V</span>?&gt;, 
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>
  )
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">enum</span> ScreenNavigation: <span class="type">Hashable</span> {
    <span class="keyword">case</span> a, b
  }

  <span class="keyword">@State var</span> showingNavigation: <span class="type">ScreenNavigation</span>? = <span class="keyword">nil

  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">NavigationLink</span>(
        destination: <span class="type">Text</span>(<span class="string">"Screen A"</span>),
        tag: .<span class="dotAccess">a</span>,
        selection: $showingNavigation,
        label: { <span class="type">Text</span>(<span class="string">"Go to Screen A"</span>) }
      )
    }
  }
}
</code></pre><p>Notes:</p><p>As this is used only in one view, and as we covered in <a href="https://www.fivestars.blog/articles/hashable-bindings/">Hashable SwiftUI bindings</a> and <a href="https://www.fivestars.blog/articles/programmatic-navigation/">The future of SwiftUI navigation (?)</a>, this can be considered an exception instead of a pattern.</p><h3>Optional binding</h3><p>Used in:</p><ul><li><a href="https://developer.apple.com/documentation/swiftui/list"><code>List</code></a> (twelve initializers), <code>Binding</code> with either <code>Set&lt;SelectionValue&gt;</code> or <code>SelectionValue?</code> type, where <code>SelectionValue</code> is a generic type conforming to <a href="https://developer.apple.com/documentation/swift/hashable"><code>Hashable</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/tabview"><code>TabView</code></a> (one initializer), <code>Binding</code> with <code>SelectionValue</code> as a generic <a href="https://developer.apple.com/documentation/swift/hashable"><code>Hashable</code></a> type</li></ul><p>Examples:</p><pre><code><span class="comment">// MARK: List

// Definition</span>
<span class="keyword">extension</span> <span class="type">List</span> {
  <span class="keyword">public init</span>(
    selection: <span class="type">Binding</span>&lt;<span class="type">Set</span>&lt;<span class="type">SelectionValue</span>&gt;&gt;?, 
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>
  )
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">enum</span> MyListElement: <span class="type">Hashable</span> {
    <span class="keyword">case</span> a, b, c
  }

  <span class="keyword">@State var</span> selectedElements: <span class="type">Set</span>&lt;<span class="type">MyListElement</span>&gt; = []

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">List</span>(selection: $selectedElements) {
      <span class="type">Text</span>(<span class="string">"Element a"</span>).<span class="call">tag</span>(<span class="type">MyListElement</span>.<span class="property">a</span>)
      <span class="type">Text</span>(<span class="string">"Element b"</span>).<span class="call">tag</span>(<span class="type">MyListElement</span>.<span class="property">b</span>)
      <span class="type">Text</span>(<span class="string">"Element c"</span>).<span class="call">tag</span>(<span class="type">MyListElement</span>.<span class="property">c</span>)
    }
    .<span class="call">environment</span>(\.<span class="property">editMode</span>, .<span class="dotAccess">constant</span>(<span class="type">EditMode</span>.<span class="property">active</span>))
    .<span class="call">onReceive</span>(selectedElements.<span class="property">publisher</span>, perform: { <span class="keyword">_ in</span>
      <span class="call">print</span>(<span class="string">"Selected elements:</span> \(selectedElements)<span class="string">"</span>)
    })
  }
}

<span class="comment">// MARK: TabView

// Definition</span>
<span class="keyword">extension</span> <span class="type">TabView</span> {
  <span class="keyword">public init</span>(
    selection: <span class="type">Binding</span>&lt;<span class="type">SelectionValue</span>&gt;?, 
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>
  )
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">enum</span> MyTab: <span class="type">Hashable</span> {
    <span class="keyword">case</span> home, news, settings
  }

  <span class="keyword">@State var</span> selectedTab: <span class="type">MyTab</span> = .<span class="dotAccess">home</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TabView</span>(selection: $selectedTab) {
      <span class="type">Text</span>(<span class="string">"Home View"</span>).<span class="call">tabItem</span> { <span class="type">Label</span>(<span class="string">"Home"</span>, systemImage: <span class="string">"house"</span>) }.<span class="call">tag</span>(<span class="type">MyTab</span>.<span class="property">home</span>)
      <span class="type">Text</span>(<span class="string">"News View"</span>).<span class="call">tabItem</span> { <span class="type">Label</span>(<span class="string">"News"</span>, systemImage: <span class="string">"newspaper"</span>) }.<span class="call">tag</span>(<span class="type">MyTab</span>.<span class="property">news</span>)
      <span class="type">Text</span>(<span class="string">"Settings View"</span>).<span class="call">tabItem</span> { <span class="type">Label</span>(<span class="string">"Settings"</span>, systemImage: <span class="string">"gear"</span>) }.<span class="call">tag</span>(<span class="type">MyTab</span>.<span class="property">settings</span>)
    }
  }
}
</code></pre><p>Notes:</p><ul><li>all these views work with or without passing a binding (refer to <a href="https://www.fivestars.blog/articles/optional-binding/">Adding optional @Bindings to SwiftUI views</a> for more details)</li><li>the <code>SelectionValue</code> naming is back, used to indicate something that can be selected/picked</li><li>similar to all pickers, <code>TabView</code> requires a value to be selected at any given time</li><li>in <code>List</code> case, <code>Binding&lt;Set&lt;SelectionValue&gt;&gt;</code> is used to show the possibility to have multiple selections at the same time, while <code>Binding&lt;SelectionValue?&gt;</code> is used to indicate the possibility to choose up to one element at any given time</li><li><code>List</code> is the only(?) view where the <code>selection: Binding&lt;..&gt;?</code> parameter can be found at the 2nd, 3rd and 4th position (depending on the initializer)</li><li>all these views work with <code>Hashable</code> tags (explicitly or implicitly), used in their <code>content</code> to distinguish which view belongs to which element/tag</li></ul><h2>is...: Binding&lt;Bool&gt;</h2><p>Used in:</p><ul><li><a href="https://developer.apple.com/documentation/swiftui/navigationlink"><code>NavigationLink</code></a> (four initializer), <code>isActive: Binding&lt;Bool&gt;</code></li><li><a href="https://developer.apple.com/documentation/swiftui/DisclosureGroup"><code>DisclosureGroup</code></a> (tree initializers), <code>isExpanded: Binding&lt;Bool&gt;</code></li><li><a href="https://developer.apple.com/documentation/swiftui/toggle"><code>Toggle</code></a> (tree initializers), <code>isOn: Binding&lt;Bool&gt;</code></li></ul><p>Examples:</p><pre><code><span class="comment">// MARK: NavigationLink

// Definition</span>
<span class="keyword">extension</span> <span class="type">NavigationLink</span> {
  <span class="keyword">public init</span>(
    destination: <span class="type">Destination</span>, 
    isActive: <span class="type">Binding</span>&lt;<span class="type">Bool</span>&gt;, 
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>
  )
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> showingDetails = <span class="keyword">false

  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">NavigationLink</span>(
        destination: <span class="type">Text</span>(<span class="string">"Detail Screen"</span>),
        isActive: $showingDetails,
        label: {
          <span class="type">Text</span>(<span class="string">"See more details"</span>)
        }
      )
    }
  }
}

<span class="comment">// MARK: DisclosureGroup

// Definition</span>
<span class="keyword">extension</span> <span class="type">DisclosureGroup</span> {
  <span class="keyword">public init</span>(
    <span class="keyword">_</span> titleKey: <span class="type">LocalizedStringKey</span>,
    isExpanded: <span class="type">Binding</span>&lt;<span class="type">Bool</span>&gt;,
    <span class="keyword">@ViewBuilder</span> content: <span class="keyword">@escaping</span> () -&gt; <span class="type">Content</span>
  )
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> showingDetails = <span class="keyword">false

  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">DisclosureGroup</span>(<span class="string">"See more details"</span>, isExpanded: $showingDetails) {
      <span class="type">Text</span>(<span class="string">"More details here"</span>)
    }
  }
}

<span class="comment">// MARK: Toggle

// Definition</span>
<span class="keyword">extension</span> <span class="type">Toggle</span> {
  <span class="keyword">public init</span>(isOn: <span class="type">Binding</span>&lt;<span class="type">Bool</span>&gt;, <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>)
}

<span class="comment">// Use</span>
<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> isOn = <span class="keyword">false

  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Toggle</span>(isOn: $isOn) {
      <span class="type">Text</span>(<span class="string">"Add extra topping:"</span>)
    }
  }
}
</code></pre><p>Notes:</p><ul><li>the <code>is...</code> <code>Binding</code> parameter is always associated with <code>Bool</code></li><li>the <code>is...</code> <code>Binding</code> parameter always represents straightforward yes/no states</li><li>there's not a single repetition, each view defines its own word (<code>isActive</code>/<code>isExpanded</code>/<code>isOn</code>)</li><li>the <code>is...</code> <code>Binding</code> parameter always comes either first or second, right after the view <code>title</code></li></ul><h2>Main takeaways</h2><p>In this article we've explored all SwiftUI views that accept a <code>@Binding</code> parameter, here are some of the most important takeaways:</p><ul><li>use <code>value: Binding&lt;V&gt;</code> for generic bindings, require the generic type <code>V</code> to conform to protocols if needed</li><li>use <code>text: Binding&lt;String&gt;</code> for bindings associated with text</li><li>use <code>selection: Binding&lt;...&gt;</code> when zero, one, or more elements can be selected/picked<ul><li>when binding to generic types, use <code>SelectionValue</code> as the type name</li><li>nearly all <code>selection: Binding&lt;..&gt;</code> require the associated type to conform to <code>Hashable</code></li><li>when a value must always be selected at any given time (like in all SwiftUI pickers), associate the binding with a non-optional type, <code>selection: Binding&lt;Color&gt;</code> for example</li><li>when zero or up to one element can be picked, associate the binding with an optional type, <code>selection: Binding&lt;Color?&gt;</code> for example</li><li>when zero or more elements can be picked, associate the binding with a set, <code>selection: Binding&lt;Set&lt;SelectionValue&gt;&gt;</code> for example</li><li>when the view can manage the selection state by itself but also expose such selection externally, offer an optional binding, <code>selection: Binding&lt;SelectionValue&gt;?</code> for example (refer to <a href="https://www.fivestars.blog/articles/optional-binding/">Adding optional @Bindings to SwiftUI views</a> for guidance on how to do this)</li></ul></li></ul><ul><li>use <code>is...: Binding&lt;Bool&gt;</code> for simple yes/no states (<code>isActive</code>, <code>isExpanded</code>, <code>isOn</code> etc)</li><li>no view accepts more than one <code>@Binding</code></li><li>if the view has/accepts a <code>title</code>, make sure its the first parameter of the initializer</li><li>most bindings are the first parameter of a view (or second if the view has a <code>title</code>)</li><li>if the view accepts optional closures (e.g. <code>onCommit</code>, <code>onEditingChanged</code>), provide a default implementation and put such parameters at the end of the initializer</li><li>if your case doesn't match any of the above, create your own pattern and be consistent with it</li></ul><h2>Conclusions</h2><p>SwiftUI is a young framework, there's no doubt that new patters will emerge as it grows, and old patterns will sunset as it evolves.</p><p>When expanding SwiftUI with our own definitions, we should be careful and try to follow and respect the patterns that the framework gives us as best as we can.</p><p>What other patterns have you spotted while using SwiftUI? Do you like/use any in particular? What patterns would you like me to cover next? <a href="https://twitter.com/zntfdr">Please let me know</a>!</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/trucated-text</guid><title>How to check if Text is truncated in SwiftUI?</title><description></description><link>https://www.fivestars.blog/articles/trucated-text</link><pubDate>Tue, 12 Jan 2021 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>When displaying text of various lengths, our design needs to be clear on whether there should be a maximum number of displayable lines, or if the full text should always be shown.</p><p><a href="https://developer.apple.com/documentation/swiftui/text"><code>Text</code></a> doesn't always behave predictably: sometimes the text gets truncated for no apparent reason, despite having plenty of space at our disposal (e.g. within <a href="https://developer.apple.com/documentation/swiftui/form"><code>Form</code></a>s and <a href="https://developer.apple.com/documentation/swiftui/list"><code>List</code></a>s).</p><p>In this article, let's have a look at how we can deal with these scenarios, and more.</p><h2>lineLimit</h2><p><code>Text</code> offers the instance method <a href="https://developer.apple.com/documentation/swiftui/text/linelimit(_:)"><code>lineLimit(_:)</code></a>:</p><pre><code><span class="comment">// This text instance won't exceed 3 rows.</span>
<span class="type">Text</span>(myString).<span class="call">lineLimit</span>(<span class="number">3</span>)
</code></pre><p>This method guarantees that our text instance will not exceed the given number of rows.</p><p>We can also pass <code>nil</code> as the <code>lineLimit</code> parameter, asking <code>Text</code> to take as many rows as needed:</p><pre><code><span class="type">Text</span>(myString).<span class="call">lineLimit</span>(<span class="keyword">nil</span>)
</code></pre><p>Unfortunately, passing <code>nil</code> means that <code>Text</code> will follow its default behavior and nothing more:<br>our <code>Text</code> will still get truncated if SwiftUI decides that this is the right thing to do.</p><blockquote><p>If the text is "five lines long" and we set <code>.lineLimit(3)</code>, this doesn't guarantee that <code>Text</code> will take three rows, it just guarantees that <code>Text</code> won't exceed three lines.</p></blockquote><h2>fixedSize(horizontal:vertical:)</h2><p><a href="https://developer.apple.com/documentation/swiftui/button/fixedsize(horizontal:vertical:)">fixedSize</a>, which we <a href="https://www.fivestars.blog/articles/flexible-swiftui/">used</a> <a href="https://www.fivestars.blog/articles/adaptive-swiftui-views/">multiple</a> <a href="https://www.fivestars.blog/articles/content-friendly-layouts/">times</a>, lets any view take as much space as needed, completely disregarding the proposed size.</p><p><code>fixedSize</code> accepts two booleans, <code>horizontal</code> and <code>vertical</code>, letting us decide whether the view should disregard both axes proposed size, just one axis, or neither.</p><blockquote><p><code>fixedSize</code> also comes with a convenience <a href="https://developer.apple.com/documentation/swiftui/button/fixedsize()"><code>fixedSize()</code></a> method, which is equivalent to <code>fixedSize(horizontal: true, vertical: true)</code>.</p></blockquote><p>To my knowledge, using <code>Text</code> with <code>.fixedSize</code> is the only way to <strong>guarantee</strong> <code>Text</code> to be always fully displayed (if you're aware of other ways, <a href="https://twitter.com/zntfdr">please let me know!</a>).</p><pre><code><span class="comment">// This Text will respect the proposed horizontal space 
// and take as much vertical space as needed.</span>
<span class="type">Text</span>(myLongString).<span class="call">fixedSize</span>(horizontal: <span class="keyword">false</span>, vertical: <span class="keyword">true</span>)
</code></pre><img src="https://www.fivestars.blog/assets/posts/truncable-text/fixed.png"/><p>There's a catch: while using <code>.fixedSize</code> guarantees the full text to be displayed entirely, if there actually is not enough space, this will break the UI:</p><pre><code><span class="type">Rectangle</span>()
  .<span class="call">stroke</span>()
  .<span class="call">frame</span>(width: <span class="number">200</span>, height: <span class="number">100</span>)
  .<span class="call">overlay</span>(<span class="type">Text</span>(myLongString).<span class="call">fixedSize</span>(horizontal: <span class="keyword">false</span>, vertical: <span class="keyword">true</span>))
</code></pre><img src="https://www.fivestars.blog/assets/posts/truncable-text/broken.png"/><h2>Putting it all together</h2><p>Now that we have covered the main two methods, let's see how we can answer the "How to check if Text is truncated?" question.</p><p>Similarly to UIKit, what we will need to know is whether the <a href="https://developer.apple.com/documentation/uikit/uiview/1622600-intrinsiccontentsize">intrinsic size</a> of our <code>Text</code> is the same as the actual size of the <code>Text</code> in the layout.</p><p>To get both the intrinsic and the actual size of our <code>Text</code> we will need to add both cases in our view hierarchy, however we only want to display one of these two cases/<code>Text</code>s, a trick to hide views while still computing their layout is to:</p><ul><li>add them as a <a href="https://developer.apple.com/documentation/swiftui/view/background(_:alignment:)"><code>background</code></a> of another view, background views don't participate in the size of their parent</li><li>apply the <a href="https://developer.apple.com/documentation/swiftui/form/hidden()"><code>hidden()</code></a> modifier on them, hidden views are not drawn by SwiftUI</li></ul><p>Here's our final layout:</p><pre><code><span class="type">Text</span>(myString)
  .<span class="call">lineLimit</span>(myLineLimit)
  .<span class="call">background</span>(
    <span class="type">Text</span>(myString)
      .<span class="call">fixedSize</span>(horizontal: <span class="keyword">false</span>, vertical: <span class="keyword">true</span>)
      .<span class="call">hidden</span>()
  )
</code></pre><p>We now need to get the sizes of both <code>Text</code>s, to do so we will use <a href="https://www.fivestars.blog/articles/swiftui-share-layout-information/"><code>readSize</code></a>, which we <a href="https://www.fivestars.blog/articles/swiftui-share-layout-information/">introduced here</a>:</p><pre><code><span class="type">Text</span>(myString)
  .<span class="call">lineLimit</span>(myLineLimit)
  .<span class="call">readSize</span> { size <span class="keyword">in</span>
    <span class="call">print</span>(<span class="string">"truncated size:</span> \(size)<span class="string">"</span>)
  }
  .<span class="call">background</span>(
    <span class="type">Text</span>(myString)
      .<span class="call">fixedSize</span>(horizontal: <span class="keyword">false</span>, vertical: <span class="keyword">true</span>)
      .<span class="call">hidden</span>()
      .<span class="call">readSize</span> { size <span class="keyword">in</span>
      	<span class="call">print</span>(<span class="string">"intrinsic size:</span> \(size)<span class="string">"</span>)
      }
  )
</code></pre><p>Lastly, we can save those two values in our view and use them at will:</p><pre><code><span class="keyword">struct</span> TruncableText: <span class="type">View</span> {
  <span class="keyword">let</span> text: <span class="type">Text</span>
  <span class="keyword">let</span> lineLimit: <span class="type">Int</span>?
  <span class="keyword">@State private var</span> intrinsicSize: <span class="type">CGSize</span> = .<span class="dotAccess">zero</span>
  <span class="keyword">@State private var</span> truncatedSize: <span class="type">CGSize</span> = .<span class="dotAccess">zero</span>
  <span class="keyword">let</span> isTruncatedUpdate: (<span class="keyword">_</span> isTruncated: <span class="type">Bool</span>) -&gt; <span class="type">Void</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    text
      .<span class="call">lineLimit</span>(lineLimit)
      .<span class="call">readSize</span> { size <span class="keyword">in</span>
        truncatedSize = size
        <span class="call">isTruncatedUpdate</span>(truncatedSize != intrinsicSize)
      }
      .<span class="call">background</span>(
        text
          .<span class="call">fixedSize</span>(horizontal: <span class="keyword">false</span>, vertical: <span class="keyword">true</span>)
          .<span class="call">hidden</span>()
          .<span class="call">readSize</span> { size <span class="keyword">in</span>
            intrinsicSize = size
            <span class="call">isTruncatedUpdate</span>(truncatedSize != intrinsicSize)
          }
      )
  }
}
</code></pre><p><code>TruncableText</code> invokes an <code>isTruncatedUpdate</code> block with the latest truncated state at every size change, this information can then be used to adapt the UI in various situations.</p><p>For example, here's a view that displays a "Show All" button when the <code>Text</code> content is truncated, and displays the full text when tapped:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> isTruncated: <span class="type">Bool</span> = <span class="keyword">false
  @State var</span> forceFullText: <span class="type">Bool</span> = <span class="keyword">false

  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="keyword">if</span> forceFullText {
        text
          .<span class="call">fixedSize</span>(horizontal: <span class="keyword">false</span>, vertical: <span class="keyword">true</span>)
      } <span class="keyword">else</span> {
        <span class="type">TruncableText</span>(
          text: text,
          lineLimit: <span class="number">3</span>
        ) {
          isTruncated = $0
        }
      }
      <span class="keyword">if</span> isTruncated &amp;&amp; !forceFullText {
        <span class="type">Button</span>(<span class="string">"show all"</span>) {
          forceFullText = <span class="keyword">true</span>
        }
      }
    }
    .<span class="call">padding</span>()
  }

  <span class="keyword">var</span> text: <span class="type">Text</span> {
    <span class="type">Text</span>(
      <span class="string">"Introducing a new kind of fitness experience. One that dynamically integrates your personal metrics from Apple Watch, along with music from your favorite artists, to inspire like no other workout in the world."</span>
    )
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/truncable-text/example.gif"/><p>The complete gist can be found <a href="https://github.com/zntfdr/FiveStarsCodeSamples/tree/main/Truncable-Text">here</a>.</p><h2>Conclusions</h2><p>Finding out whether a displayed text is truncated wasn't easy in UIKit and, for the moment, it's not straightforward in SwiftUI neither.</p><p>Fortunately, SwiftUI provides us all the tools needed to overcome this limitation: have you found yourself in a similar situation? how did you solve it? <a href="https://twitter.com/zntfdr">I'd love to know</a>!</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/stack-spacer-alternatives</guid><title>An alternative to SwiftUI's stacks + Spacer combo</title><description></description><link>https://www.fivestars.blog/articles/stack-spacer-alternatives</link><pubDate>Tue, 15 Dec 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Apple has recently published <a href="https://twitter.com/zntfdr/status/1336700623589318662">some great new articles</a> around building layouts with SwiftUI's stacks in various situations.</p><p>There's no doubt that these articles showcase best practices/approaches on how to build views, and everybody should definitely make sure to read <a href="https://developer.apple.com/documentation/swiftui/building-layouts-with-stack-views">all</a> <a href="https://developer.apple.com/documentation/swiftui/creating-performant-scrollable-stacks">of</a> <a href="https://developer.apple.com/documentation/swiftui/aligning-views-across-stacks">them</a>.</p><p>In this article I would like to propose/highlight another pattern that I use daily, which is an alternative to Apple's <a href="https://developer.apple.com/documentation/swiftui/building-layouts-with-stack-views"><code>Building Layouts with Stack Views</code></a> article.</p><h2>Overlay &amp; background</h2><p>When building a view with some components underneath/on top, instead of a <code>ZStack</code> we should use the <a href="https://developer.apple.com/documentation/swiftui/view/overlay(_:alignment:)"><code>overlay(_:alignment:)</code></a> or <a href="https://developer.apple.com/documentation/swiftui/view/background(_:alignment:)"><code>background(_:alignment:)</code></a> view modifiers on top of the main view, here it is in Apple's words:</p><blockquote><p>If your layout has one dominant view that defines the size of the layout, use the <a href="https://developer.apple.com/documentation/swiftui/view/overlay(_:alignment:)"><code>overlay(_:alignment:)</code></a> or <a href="https://developer.apple.com/documentation/swiftui/view/background(_:alignment:)"><code>background(_:alignment:)</code></a> view modifier on that view.</p></blockquote><p>A view defined within these modifiers will get a proposed size as big as the dominant view, and by default will be put in the middle of it, for example:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Color</span>.<span class="property">yellow</span>
      .<span class="call">frame</span>(width: <span class="number">200</span>, height: <span class="number">200</span>)
      .<span class="call">overlay</span>(<span class="type">Text</span>(<span class="string">"Five Stars"</span>))
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/stack-spacer-alternative/image1.png"/><p>Let's say that we would like <code>Text</code> to be put on the leading side of the dominant view instead, an easy way to do so is to embed the <code>Text</code> in a <code>HStack</code> and then use a <code>Spacer</code> to push the text to the preferred side:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Color</span>.<span class="property">yellow</span>
      .<span class="call">frame</span>(width: <span class="number">200</span>, height: <span class="number">200</span>)
      .<span class="call">overlay</span>(
        <span class="type">HStack</span> {
          <span class="type">Text</span>(<span class="string">"Five Stars"</span>)
          <span class="type">Spacer</span>()
        }
      )
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/stack-spacer-alternative/image2.png"/><p>While this works great, there's a more concise way to declare the same layout thanks to the <code>alignment</code> parameter available on both <a href="https://developer.apple.com/documentation/swiftui/view/overlay(_:alignment:)"><code>overlay(_:alignment:)</code></a> and <a href="https://developer.apple.com/documentation/swiftui/view/background(_:alignment:)"><code>background(_:alignment:)</code></a>:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Color</span>.<span class="property">yellow</span>
      .<span class="call">frame</span>(width: <span class="number">200</span>, height: <span class="number">200</span>)
      .<span class="call">overlay</span>(<span class="type">Text</span>(<span class="string">"Five Stars"</span>), alignment: .<span class="dotAccess">leading</span>)
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/stack-spacer-alternative/image2.png"/><p>With this definition we've managed to remove five lines of code (our entire <code>body</code> is now three lines long!) while obtaining the exact same layout, no compromises.</p><p>This approach is recommended and showcased in <a href="https://developer.apple.com/documentation/swiftui/building-layouts-with-stack-views">Apple's article as well</a>.</p><h2>Frame</h2><p>Imagine now to not be on a container like <a href="https://developer.apple.com/documentation/swiftui/view/overlay(_:alignment:)"><code>overlay(_:alignment:)</code></a> or <a href="https://developer.apple.com/documentation/swiftui/view/background(_:alignment:)"><code>background(_:alignment:)</code></a>, but where we would still like to put our content on the leading side of the available space, in this case Apple's recommended way is to use <code>HStack</code> + <code>Spacer</code>:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">HStack</span> {
      <span class="type">Text</span>(<span class="string">"Five Stars"</span>)
      <span class="type">Spacer</span>()
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/stack-spacer-alternative/image3.png"/><p>This works great, it's Apple's blessed way, and the layout is very clear.</p><p>However, when putting just a single element inside the stack, I prefer to use the <a href="https://developer.apple.com/documentation/swiftui/view/frame(minwidth:idealwidth:maxwidth:minheight:idealheight:maxheight:alignment:)"><code>.frame(...)</code></a> view modifier instead:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Text</span>(<span class="string">"Five Stars"</span>)
      .<span class="call">frame</span>(maxWidth: .<span class="dotAccess">infinity</span>, alignment: .<span class="dotAccess">leading</span>)
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/stack-spacer-alternative/image3.png"/><p>By setting either <code>maxWidth</code> or <code>maxHeight</code> to <code>.infinity</code>, we're telling SwiftUI that this view will take as much space as available on the associated axis, and then put the frame content (the <code>Text</code> in our example) according to the declared <code>alignment</code> argument.</p><p>In short we're using <code>frame</code> to create a new container and use its <code>alignment</code> to move the content, all while halving our view definition (our <code>body</code> went from 4 to 2 lines of code).</p><p>I see this as an equivalent approach to Apple's suggested way for <code>.overlay(...)</code> and <code>.background(...)</code>, just for views without containers.</p><p>Other advantages of this approach:</p><ul><li>no need to define multiple stacks in complex layouts</li><li>easier to animate alignment changes (e.g. from <code>.leading</code> to <code>.trailing</code>)</li><li>less views definitions (no stacks and <code>Spacer</code>s!)</li></ul><h2>When to use stack + Spacer then?</h2><p>I believe the stack + <code>Spacer</code> combo is best used when we're distributing multiple components within the same stack, for example:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">HStack</span> {
      <span class="type">Text</span>(<span class="string">"Five"</span>)
      <span class="type">Spacer</span>()
      <span class="type">Text</span>(<span class="string">"Stars"</span>)
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/stack-spacer-alternative/image4.png"/><p>In this case <code>HStack</code> + <code>Spacer</code> is the way to go: the <code>.frame</code> approach would have an hard time declaring this layout while also making sure that the two <code>Text</code> instances wouldn't overlap.</p><h2>Conclusions</h2><p>In this article we've covered an alternative approach on how to setup and distribute content in our SwiftUI layouts:<br>the outcome is exactly the same, which one you pick doesn't really matter as long as the choice stays consistent throughout your project.</p><p>Do you use any other alternatives? I'd love <a href="https://twitter.com/zntfdr">to know</a>!</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/lets-build-state</guid><title>Let's build @State</title><description></description><link>https://www.fivestars.blog/articles/lets-build-state</link><pubDate>Tue, 8 Dec 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<blockquote><p>Shout out to <a href="https://twitter.com/mikeash">Mike Ash</a> for the original, historic <a href="https://mikeash.com/pyblog/friday-qa-2013-01-25-lets-build-nsobject.html"><code>Let's build NSObject</code></a> article.</p></blockquote><p><a href="https://developer.apple.com/documentation/swiftui/state"><code>@State</code></a> is one of the many SwiftUI's pillars that, once understood, we take for granted and use pretty much everywhere without a second thought. But what is <code>@State</code>? What's happening behind the scenes?</p><p>In this article, let's try to answer those questions by re-building <code>@State</code>, and more.</p><blockquote><p>As usual, I have no access to the actual SwiftUI code/implementation:what we will find here is a best guess on mocking the original <code>@State</code> behavior, there's probably much more to it in the real implementation.</p></blockquote><h2>Property wrapper</h2><p>First of all, <code>@State</code> is a <a href="https://docs.swift.org/swift-book/LanguageGuide/Properties.html#ID617">property wrapper</a>, which in short is a fancy getter and setter with extra logic and storage. Let's start by defining our state as following:</p><pre><code><span class="keyword">@propertyWrapper
struct</span> FSState {

}
</code></pre><p>A property wrapper requires a <code>wrappedValue</code>, letting us read/write the associated value.<br>Since we want to mock <code>@State</code>, we will make our property wrapper generic over a type <code>V</code>, and store the original value in a internal <code>value</code> property:</p><pre><code><span class="keyword">@propertyWrapper
struct</span> FSState&lt;V&gt; {
  <span class="comment">// This is where our value is actually stored.</span>
  <span class="keyword">var</span> value: <span class="type">V</span>
  
  <span class="comment">// And here are our getter/setters.</span>
  <span class="keyword">var</span> wrappedValue: <span class="type">V</span> {
    <span class="keyword">get</span> {
      value
    }
    <span class="keyword">set</span> {
      value = newValue
    }
  }
}
</code></pre><p>Lastly, if we want to provide the same syntax as <code>@State</code> and all other property wrappers (e.g. <code>@State var x = "hello"</code>), we will need to declare a special initializer:</p><pre><code><span class="keyword">@propertyWrapper
struct</span> FSState&lt;V&gt; {
  <span class="keyword">var</span> value: <span class="type">V</span>
  
  <span class="keyword">var</span> wrappedValue: <span class="type">V</span> {
    ...
  }

  <span class="keyword">init</span>(wrappedValue value: <span class="type">V</span>) {
    <span class="keyword">self</span>.<span class="property">value</span> = value
  }
}
</code></pre><p>With this definition we can now go ahead and start using <code>@FSState</code> in a view, for example:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@FSState var</span> text = <span class="string">"Hello Five Stars"</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Text</span>(text)
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/lets-build-state/image1.png"/><h2>nonmutating</h2><p>So far our definition is not much different than having a property defined directly in the view itself.<br>If we remove <code>@FSState</code> from our <code>ContentView</code> declaration, everything still works great:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> text = <span class="string">"Hello Five Stars"</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Text</span>(text)
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/lets-build-state/image1.png"/><p>Let's now try to change the text with a button for example:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@FSState var</span> text = <span class="string">"Hello Five Stars"</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">Text</span>(text)

      <span class="type">Button</span>(<span class="string">"Change text"</span>) {
        text = [<span class="string">"hello"</span>, <span class="string">"five"</span>, <span class="string">"stars"</span>].<span class="call">randomElement</span>()!
      }
    }
  }
}
</code></pre><p>Unfortunately, this won't build:<br>we get a <code>Cannot assign to property: 'self' is immutable</code> error on the button action.</p><p>The issue is that assigning to <code>text</code> would mutate/change <code>ContentView</code>.</p><p>With structs we can declare <code>mutating</code> methods, but we cannot declare <code>mutating</code> computed properties (like <code>body</code>), nor we can call <code>mutating</code> methods in them.</p><p>To overcome this we must not change <code>ContentView</code>, which means we cannot change <code>FSState</code> neither, as our property wrapper is just another value type nested in our view.</p><p>First, let's declare our property wrapper setter as <code>nonmutating</code>, which tells Swift that setting this value won't change our <code>FSState</code> instance:</p><pre><code><span class="keyword">@propertyWrapper
struct</span> FSState&lt;V&gt; {
  <span class="keyword">var</span> value: <span class="type">V</span>
  
  <span class="keyword">var</span> wrappedValue: <span class="type">V</span> {
    <span class="keyword">get</span> { ... }
    <span class="keyword">nonmutating set</span> { <span class="comment">// our setter is now nonmutating</span>
      value = newValue
    }
  }

  ...
}
</code></pre><p>We've now moved the <code>Cannot assign to property: 'self' is immutable</code> build error from our <code>text</code> assignment to <code>FSState</code>'s <code>wrappedValue</code> setter.<br>This makes sense, as we're promising to not mutate the struct instance, but then we're setting <code>value = newValue</code>, which is mutating.</p><p>This is where Swift's reference types come in: if we replace <code>FSState</code>'s <code>value</code> property with a class type, and then update that class instance in our setter, we're effectively not changing <code>FSState</code> (as <code>FSState</code> would only contain the reference to that class, which always stays the same).</p><p>Let's define this "container" class type:</p><pre><code><span class="keyword">final class</span> Box&lt;V&gt; {
  <span class="keyword">var</span> value: <span class="type">V</span>

  <span class="keyword">init</span>(<span class="keyword">_</span> value: <span class="type">V</span>) {
    <span class="keyword">self</span>.<span class="property">value</span> = value
  }
}
</code></pre><p><code>Box</code> is a generic class that only has one function: hold and update our value.<br><br>Let's make <code>@FSState</code>'s declaration take advantage of this class:</p><pre><code><span class="keyword">@propertyWrapper
struct</span> FSState&lt;V&gt; {
  <span class="keyword">var</span> box: <span class="type">Box</span>&lt;<span class="type">V</span>&gt;

  <span class="keyword">var</span> wrappedValue: <span class="type">V</span> {
    <span class="keyword">get</span> {
      box.<span class="property">value</span>
    }
    <span class="keyword">nonmutating set</span> {
      box.<span class="property">value</span> = newValue
    }
  }

  <span class="keyword">init</span>(wrappedValue value: <span class="type">V</span>) {
    <span class="keyword">self</span>.<span class="property">box</span> = <span class="type">Box</span>(value)
  }
}
</code></pre><p>With this update we can build and run our app!</p><img src="https://www.fivestars.blog/assets/posts/lets-build-state/image2.gif"/><p>We tap the button but see no change, if we set breakpoints we will see that everything works: tapping the button correctly sets and updates our state, however the new challenge is letting SwiftUI know.</p><p>It's true that we're updating our data, but SwiftUI doesn't know that it should listen to such change and trigger a redraw of its body, let's tackle that next.</p><h2>DynamicProperty</h2><p>Similarly to how SwiftUI has a set of known <a href="https://www.fivestars.blog/articles/impossible-swiftui-views/">view primitives</a>, SwiftUI has also a set of known publishers that each view can listen to, based on the properties defined within that view.</p><p>The SwiftUI team has done an astonishing job at hiding SwiftUI's heavy use of Combine:<br>when we associate a view property with <code>@State</code>, <code>@ObservedObject</code>, etc SwiftUI will listen to all publishers connected to each property wrapper, which in turn tell SwiftUI when it's time to redraw.</p><p>In our case let's use <a href="https://developer.apple.com/documentation/swiftui/stateobject"><code>@StateObject</code></a> by conforming <code>Box</code> to <a href="https://developer.apple.com/documentation/Combine/ObservableObject"><code>ObservableObject</code></a>. Combine associates an <a href="https://developer.apple.com/documentation/combine/observableobject/objectwillchange-2oa5v"><code>objectWillChange</code></a> publisher to all <code>ObservableObject</code> instances, which we can then use to send events to SwiftUI by calling <a href="https://developer.apple.com/documentation/combine/observableobjectpublisher/send()"><code>send()</code></a>:</p><pre><code><span class="keyword">final class</span> Box&lt;V&gt;: <span class="type">ObservableObject</span> {
  <span class="keyword">var</span> value: <span class="type">V</span> {
    <span class="keyword">willSet</span> {
      <span class="comment">// This is where we send out our "hey, something has changed!" event</span>
      objectWillChange.<span class="call">send</span>()
    }
  }

  <span class="keyword">init</span>(<span class="keyword">_</span> value: <span class="type">V</span>) {
    <span class="keyword">self</span>.<span class="property">value</span> = value
  }
}
</code></pre><blockquote><p>There are easier ways to declare this, but in this article we're trying to see how things work by removing as much "<em>magic</em>" as possible.</p></blockquote><p>With <code>Box</code>'s definition updated, we can now go back to <code>@FSState</code> and associate <code>@StateObject</code> to the <code>box</code> property:</p><pre><code><span class="keyword">@propertyWrapper
struct</span> FSState&lt;V&gt; {
  <span class="keyword">@StateObject var</span> box: <span class="type">Box</span>&lt;<span class="type">V</span>&gt;

  <span class="keyword">var</span> wrappedValue: <span class="type">V</span> {
    ...
  }

  <span class="keyword">init</span>(wrappedValue value: <span class="type">V</span>) {
    <span class="keyword">self</span>.<span class="property">_box</span> = <span class="type">StateObject</span>(wrappedValue: <span class="type">Box</span>(value))
  }
}
</code></pre><p>Thanks to this update every time <code>box</code>'s value changes:</p><ul><li>an <a href="https://developer.apple.com/documentation/combine/observableobject/objectwillchange-2oa5v"><code>objectWillChange</code></a> event is fired</li><li>and an observer (SwiftUI?) of <code>box</code>'s publisher would know about it</li></ul><p>Let's run our app once again:</p><img src="https://www.fivestars.blog/assets/posts/lets-build-state/image2.gif"/><p>Unfortunately, we're not there yet. While it's true that the new publisher is sending out events when our value changes, we still need to tell SwiftUI about it:<br>from SwiftUI's point of view, <code>ContentView</code> has a <code>text</code> property of type <code>FSState&lt;String&gt;</code>, which is not something SwiftUI needs to pay attention to.</p><p>To change this, we need to make <code>FSState</code> conform to <a href="https://developer.apple.com/documentation/swiftui/dynamicproperty"><code>DynamicProperty</code></a>, described in the documentation as <code>An interface for a stored variable that updates an external property of a view.</code>.</p><p>Now, this is something SwiftUI is interested in! By making <code>FSState</code> conform to <code>DynamicProperty</code>, SwiftUI will listen to its events (if any) and trigger a redraw when needed.</p><p><code>DynamicProperty</code> requires only the implementation of an <code>update()</code> function, however SwiftUI already provides its default implementation, all we need to do is add the <code>DynamicProperty</code> conformance and we're good to go:</p><pre><code><span class="keyword">@propertyWrapper
struct</span> FSState&lt;V&gt;: <span class="type">DynamicProperty</span> {
  ...
}
</code></pre><p>With this last change let's try to run our app once again:</p><img src="https://www.fivestars.blog/assets/posts/lets-build-state/image3.gif"/><p>It works!<br>Despite adding this <code>DynamicProperty</code> conformance, we still didn't declare exactly which properties SwiftUI should listen to:<br><a href="https://www.fivestars.blog/articles/swift-protocols/">similarly to how view equatability works</a>, I suspect SwiftUI uses Swift's reflection to iterate over all stored properties and look for known property wrapper types to subscribe to.</p><blockquote><p>For an open source example on how to use reflection this way, refer to my <a href="https://www.fivestars.blog/articles/a-look-into-argument-parser/">deep dive into Apple's <code>ArgumentParser</code>'s implementation</a>, where the same approach is used to find the various command line arguments.</p></blockquote><h2>Binding</h2><p>An optional feature of property wrappers is to expose a projected value:<br>a projected value is an alternative look at the value stored within the property wrapper, exposed in a different manner.</p><p>Many SwiftUI views use bindings to refer to and potentially mutate values owned and stored somewhere else. An example of this is <a href="https://developer.apple.com/documentation/swiftui/textfield"><code>TextField</code></a> which uses a <code>Binding&lt;String&gt;</code>:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@FSState var</span> text = <span class="string">""</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">TextField</span>(<span class="string">"Write something"</span>, text: $text) <span class="comment">// TextField's text is a binding</span>
    }
  }
}
</code></pre><p>As seen above, we can get a binding from a <code>@State</code> by calling the associate property with a <code>$</code> in front of the property name, what this symbol really does is reaching for the projected value instead of the wrapped one.</p><p>Therefore <code>@State</code>'s projected value is a generic <a href="https://developer.apple.com/documentation/swiftui/binding"><code>@Binding</code></a> over its type <code>V</code>, let's add the same projected value in <code>@FSState</code>:</p><pre><code><span class="keyword">@propertyWrapper
struct</span> FSState&lt;V&gt;: <span class="type">DynamicProperty</span> {
  <span class="keyword">@ObservedObject private var</span> box: <span class="type">Box</span>&lt;<span class="type">V</span>&gt;

  <span class="keyword">var</span> wrappedValue: <span class="type">V</span> {
    ...
  }

  <span class="keyword">var</span> projectedValue: <span class="type">Binding</span>&lt;<span class="type">V</span>&gt; {
    <span class="type">Binding</span>(
      get: {
        wrappedValue
      },
      set: {
        wrappedValue = $0
      }
    )
  }

  ...
}
</code></pre><p>And voila', we can now use <code>@FSState</code> with bindings!</p><img src="https://www.fivestars.blog/assets/posts/lets-build-state/image4.gif"/><p>Here's the final <code>@FSState</code> definition:</p><pre><code><span class="keyword">@propertyWrapper
struct</span> FSState&lt;V&gt;: <span class="type">DynamicProperty</span> {
  <span class="keyword">@StateObject private var</span> box: <span class="type">Box</span>&lt;<span class="type">V</span>&gt;

  <span class="keyword">var</span> wrappedValue: <span class="type">V</span> {
    <span class="keyword">get</span> {
      box.<span class="property">value</span>
    }
    <span class="keyword">nonmutating set</span> {
      box.<span class="property">value</span> = newValue
    }
  }

  <span class="keyword">var</span> projectedValue: <span class="type">Binding</span>&lt;<span class="type">V</span>&gt; {
    <span class="type">Binding</span>(
      get: {
        wrappedValue
      },
      set: {
        wrappedValue = $0
      }
    )
  }

  <span class="keyword">init</span>(wrappedValue value: <span class="type">V</span>) {
    <span class="keyword">self</span>.<span class="property">_box</span> = <span class="type">StateObject</span>(wrappedValue: <span class="type">Box</span>(value))
  }
}

<span class="keyword">final class</span> Box&lt;T&gt;: <span class="type">ObservableObject</span> {
  <span class="keyword">var</span> value: <span class="type">T</span> {
    <span class="keyword">willSet</span> {
      objectWillChange.<span class="call">send</span>()
    }
  }

  <span class="keyword">init</span>(<span class="keyword">_</span> value: <span class="type">T</span>) {
    <span class="keyword">self</span>.<span class="property">value</span> = value
  }
}
</code></pre><h2>Conclusions</h2><p>Once again the more we explore SwiftUI, the more it shows how much complexity can be hidden in a simple, elegant API.</p><p>Most developers won't ever need to worry about how things really work behind the scenes, however I cannot help but appreciate all the effort that has been done in order to get to this beautiful <em>state</em>.</p><p>I'm sure <code>@FSState</code> is not as complete as the real <code>@State</code>: if there's something that I missed or if you have more insights on something I glossed over, <a href="https://twitter.com/zntfdr">I'd love to know</a>!</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/programmatic-navigation</guid><title>The future of SwiftUI navigation (?)</title><description></description><link>https://www.fivestars.blog/articles/programmatic-navigation</link><pubDate>Tue, 1 Dec 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<blockquote><p>For a great overview on SwiftUI navigation, please check out <a href="https://www.hackingwithswift.com/articles/216/complete-guide-to-navigationview-in-swiftui">this article</a> by <a href="https://twitter.com/twostraws">Paul Hudson</a>.</p></blockquote><p>As it stands today, SwiftUI presents some limitations when dealing with <a href="https://developer.apple.com/documentation/swiftui/navigationlink"><code>NavigationLink</code></a>s.</p><p>In <a href="https://www.fivestars.blog/articles/swift-protocols/"><code>Swift protocols in SwiftUI</code></a> we've covered how SwiftUI uses <code>Identifiable</code> to manage sheets, alerts, and other views presentation, while <code>Hashable</code> is used for navigation:<br>this is the first limiting factor as, out of the box, it makes it hard to programmatically trigger a navigation while also passing dynamic data to the destination view.</p><p>The second limiting factor is that <code>NavigationLink</code>s are buttons in disguise: their declaration requires a view of some kind, which will then be part of the view hierarchy.</p><p>In this article, let's try to overcome both these limitations.</p><h2>Now you're here, now you're gone</h2><p><code>NavigationLink</code>s behave as triggers to push and pop views: internally, they observe a <a href="https://www.fivestars.blog/articles/hashable-bindings/">boolean binding</a> (which can be <a href="https://www.fivestars.blog/articles/optional-binding/">exposed externally if needed</a>) to determine when to do so.</p><p>If we want to pop back from a pushed view programmatically, the most common way is by setting the <code>NavigationLink</code>'s binding to the proper state:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> showingNavigation: <span class="type">Bool</span> = <span class="keyword">false

  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">NavigationLink</span>(
        <span class="string">"Push view"</span>,
        destination: <span class="type">Button</span>(<span class="string">"Pop back"</span>, action: { showingNavigation = <span class="keyword">false</span> }),
        isActive: $showingNavigation
      )
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/programmatic-navigation/bool.gif"/><p>While this works great, we can also use another <em>hidden</em> trick to obtain the same effect:<br>entirely remove the <code>NavigationLink</code> from the view hierarchy.</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> showingNavigation: <span class="type">Bool</span> = <span class="keyword">true

  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">VStack</span> {
        <span class="keyword">if</span> showingNavigation {
          <span class="type">NavigationLink</span>(
            <span class="string">"Go to destination"</span>,
            destination: <span class="type">Button</span>(<span class="string">"Hide navigation"</span>, action: { showingNavigation.<span class="call">toggle</span>() })
          )
        }

        <span class="keyword">if</span> !showingNavigation {
          <span class="type">Button</span>(<span class="string">"Show navigation"</span>, action: { showingNavigation.<span class="call">toggle</span>() })
        }
      }
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/programmatic-navigation/navigationLink.gif"/><p>In this second case, we use <code>showingNavigation</code> not as a <code>NavigationLink</code> binding, but to decide whether the <code>NavigationLink</code> is part of the view hierarchy.<br>If we remove the <code>NavigationLink</code> while presenting, it will also pop its destination.</p><h2>EmptyView</h2><p><code>NavigationLink</code>s require a view as their <code>Label</code>, however any view will do, therefore we can "<em>hide</em>" a <code>NavigationLink</code> by passing an <code>EmptyView</code> instance:</p><pre><code><span class="type">NavigationLink</span>(
  destination: ...,
  isActive: ...,
  label: { <span class="type">EmptyView</span>() }
)
</code></pre><p>By doing so, we're effectively hiding the link from the view hierarchy while still preserving its push/pop trigger effects.</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> showingNavigation: <span class="type">Bool</span> = <span class="keyword">false

  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">VStack</span> {
        <span class="type">NavigationLink</span>(
          destination: <span class="type">Button</span>(<span class="string">"Pop back"</span>, action: { showingNavigation = <span class="keyword">false</span> }),
          isActive: $showingNavigation,
          label: { <span class="type">EmptyView</span>() }
        )

        <span class="type">Button</span>(<span class="string">"Push view"</span>, action: { showingNavigation = <span class="keyword">true</span> })
      }
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/programmatic-navigation/emptyView.gif"/><p>In this example, we're using a couple of buttons to trigger the navigation push and pop, but the same could have been done via any other logic (for example, after fetching something from the web).</p><p>While <code>NavigationLink</code> is still part of the view hierarchy definition, it's now hidden and disconnected from the rest of the UI.</p><h2>Identifiable x NavigationLink</h2><p>Let's recap what we've covered so far:</p><ul><li>if a presenting <code>NavigationLink</code> is removed from the view hierarchy, its destination will pop back</li><li>using <code>EmptyView</code> as <code>NavigationLink</code>'s <code>Label</code> will <em>hide</em> the view from the UI, while preserving its push/pop triggers</li></ul><p>We would like to have a <code>NavigationLink</code> with an <code>Identifiable</code> binding instead of a <code>Bool</code> or <code>Hashable</code> one.</p><p>This aligns the navigation push/pop with the rest of SwiftUI view presentations (covered in the <code>Identifiable</code> chapter <a href="https://www.fivestars.blog/articles/swift-protocols/">here</a>), enabling us to pass data between different views easily.</p><p>While SwiftUI does not offer this out of the box, as we've covered in <a href="https://www.fivestars.blog/articles/hashable-bindings/"><code>Hashable SwiftUI bindings</code></a>, nobody stops us from creating a new extension.</p><p>We start with the following:</p><pre><code><span class="keyword">extension</span> <span class="type">NavigationLink</span> {
  <span class="keyword">public init</span>&lt;V: <span class="type">Identifiable</span>&gt;(
    item: <span class="type">Binding</span>&lt;<span class="type">V</span>?&gt;,
    destination: ...
  ) {
    ...
  }
}
</code></pre><p>Let's imagine to have an <code>Identifiable</code> type defined as following:</p><pre><code><span class="keyword">enum</span> ContentViewNavigation: <span class="type">Identifiable</span> {
  <span class="keyword">case</span> one
  <span class="keyword">case</span> two(number: <span class="type">Int</span>)
  <span class="keyword">case</span> three(text: <span class="type">String</span>)

  <span class="comment">// MARK: Identifiable</span>

  <span class="keyword">var</span> id: <span class="type">Int</span> {
    <span class="keyword">switch self</span> {
    <span class="keyword">case</span> .<span class="dotAccess">one</span>:
      <span class="keyword">return</span> <span class="number">1</span>
    <span class="keyword">case</span> .<span class="dotAccess">two</span>:
      <span class="keyword">return</span> <span class="number">2</span>
    <span class="keyword">case</span> .<span class="dotAccess">three</span>:
      <span class="keyword">return</span> <span class="number">3</span>
    }
  }
}
</code></pre><p>This "navigation" type has three possible destinations, where some of them (case <code>.two</code> and <code>.tree</code>) will also pass dynamic data to the destination view.</p><p>Our <code>NavigationLink</code> extension would need to generate a different destination for each possible <code>Identifiable</code> instance, therefore let's ask for a <em>view builder</em> function as our destination parameter:</p><pre><code><span class="keyword">extension</span> <span class="type">NavigationLink</span> {
  <span class="keyword">public init</span>&lt;V: <span class="type">Identifiable</span>&gt;(
    item: <span class="type">Binding</span>&lt;<span class="type">V</span>?&gt;,
    destination: <span class="keyword">@escaping</span> (<span class="type">V</span>) -&gt; <span class="type">Destination</span>
  ) {
    ...
  }
}
</code></pre><p>Thanks to this API we no longer need to declare a separate <code>NavigationLink</code> for each destination. Therefore let's <em>hide</em> this <code>NavigationLink</code> from the UI by declaring its <code>Label</code> as an <code>EmptyView</code>:</p><pre><code><span class="keyword">extension</span> <span class="type">NavigationLink</span> <span class="keyword">where</span> <span class="type">Label</span> == <span class="type">EmptyView</span> {
  <span class="keyword">public init</span>&lt;V: <span class="type">Identifiable</span>&gt;(
    item: <span class="type">Binding</span>&lt;<span class="type">V</span>?&gt;,
    destination: <span class="keyword">@escaping</span> (<span class="type">V</span>) -&gt; <span class="type">Destination</span>
  ) {
    ...
  }
}
</code></pre><p>This <code>NavigationLink</code> will generate a view with the correct destination only when the given <code>binding</code> is not <code>nil</code>: when the binding is <code>nil</code>, the initialization will fail.</p><p>To make this happen our extension will implement a failable initializer (note the <code>?</code> in the <code>init</code>):</p><pre><code><span class="keyword">extension</span> <span class="type">NavigationLink</span> <span class="keyword">where</span> <span class="type">Label</span> == <span class="type">EmptyView</span> {
  <span class="keyword">public init</span>?&lt;<span class="type">V</span>: <span class="type">Identifiable</span>&gt;(
    item: <span class="type">Binding</span>&lt;<span class="type">V</span>?&gt;,
    destination: <span class="keyword">@escaping</span> (<span class="type">V</span>) -&gt; <span class="type">Destination</span>
  ) {
     ...
  }
}
</code></pre><p>Now our <code>NavigationLink</code> will only be generated when the binding has a value (and a view is pushed): as soon as the binding is set to <code>nil</code> the <code>NavigationLink</code> will be removed from the view hierarchy, triggering a navigation pop.</p><pre><code><span class="keyword">extension</span> <span class="type">NavigationLink</span> <span class="keyword">where</span> <span class="type">Label</span> == <span class="type">EmptyView</span> {
  <span class="keyword">public init</span>?&lt;<span class="type">V</span>: <span class="type">Identifiable</span>&gt;(
    item: <span class="type">Binding</span>&lt;<span class="type">V</span>?&gt;,
    destination: <span class="keyword">@escaping</span> (<span class="type">V</span>) -&gt; <span class="type">Destination</span>
  ) {
    <span class="keyword">if let</span> value = item.<span class="property">wrappedValue</span> {
      <span class="keyword">self</span>.<span class="keyword">init</span>(...)
    } <span class="keyword">else</span> {
      <span class="keyword">return nil</span>
    }
  }
}
</code></pre><p>Lastly, we need to pass a binding to the initializer within our extension, let's make one that the official <code>NavigationLink</code> API accepts:</p><pre><code><span class="keyword">extension</span> <span class="type">NavigationLink</span> <span class="keyword">where</span> <span class="type">Label</span> == <span class="type">EmptyView</span> {
  <span class="keyword">public init</span>?&lt;<span class="type">V</span>: <span class="type">Identifiable</span>&gt;(
    item: <span class="type">Binding</span>&lt;<span class="type">V</span>?&gt;,
    destination: <span class="keyword">@escaping</span> (<span class="type">V</span>) -&gt; <span class="type">Destination</span>
  ) {
    <span class="keyword">if let</span> value = item.<span class="property">wrappedValue</span> {
      <span class="keyword">let</span> isActive: <span class="type">Binding</span>&lt;<span class="type">Bool</span>&gt; = <span class="type">Binding</span>(
        get: { item.<span class="property">wrappedValue</span> != <span class="keyword">nil</span> },
        set: { value <span class="keyword">in</span>
          <span class="comment">// There's shouldn't be a way for SwiftUI to set `true` here.</span>
          <span class="keyword">if</span> !value {
            item.<span class="property">wrappedValue</span> = <span class="keyword">nil</span>
          }
        }
      )

      <span class="keyword">self</span>.<span class="keyword">init</span>(
        destination: <span class="call">destination</span>(value),
        isActive: isActive,
        label: { <span class="type">EmptyView</span>() }
      )
    } <span class="keyword">else</span> {
      <span class="keyword">return nil</span>
    }
  }
}
</code></pre><p>And with this, our extension is complete! Here's how we can use it:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> showingNavigation: <span class="type">ContentViewNavigation</span>?

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">VStack</span> {
        <span class="type">NavigationLink</span>(item: $showingNavigation, destination: presentNavigation)

        <span class="type">Button</span>(<span class="string">"Go to navigation one"</span>) {
          showingNavigation = .<span class="dotAccess">one</span>
        }
        <span class="type">Button</span>(<span class="string">"Go to navigation two"</span>) {
          showingNavigation = .<span class="call">two</span>(number: <span class="type">Int</span>.<span class="call">random</span>(in: <span class="number">1</span>...<span class="number">5</span>))
        }
        <span class="type">Button</span>(<span class="string">"Go to navigation three"</span>) {
          showingNavigation = .<span class="call">three</span>(text: [<span class="string">"five"</span>, <span class="string">"stars"</span>].<span class="call">randomElement</span>()!)
        }
      }
    }
  }

  <span class="keyword">@ViewBuilder
  func</span> presentNavigation(<span class="keyword">_</span> navigation: <span class="type">ContentViewNavigation</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">switch</span> navigation {
    <span class="keyword">case</span> .<span class="dotAccess">one</span>:
      <span class="type">Text</span>(verbatim: <span class="string">"one"</span>)
    <span class="keyword">case</span> .<span class="dotAccess">two</span>(<span class="keyword">let</span> number):
      <span class="type">Text</span>(<span class="string">"two</span> \(number)<span class="string">"</span>)
    <span class="keyword">case</span> .<span class="dotAccess">three</span>(<span class="keyword">let</span> text):
      <span class="type">Text</span>(<span class="string">"three</span> \(text)<span class="string">"</span>)
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/programmatic-navigation/identifiable.gif"/><h2>What NavigationLink?</h2><p>Thanks to our extension, it's now simpler to pass data to any destination view while maintaining clarity in our view definition.</p><p>Compared to other SwiftUI presentations, our solution is still missing the complete removal of its definition from the view hierarchy. While this is currently not possible, we can undoubtedly hide it nicely.</p><p>Looking at our previous example, the navigation link is still within our <code>VStack</code>:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  ...

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">VStack</span> {
        <span class="type">NavigationLink</span>(item: $showingNavigation, destination: presentNavigation)

        <span class="type">Button</span>(...) { ... }
        <span class="type">Button</span>(...) { ... }
        <span class="type">Button</span>(...) { ... }
      }
    }
  }
  ...
}
</code></pre><p>However, a <code>NavigationLink</code> can be placed anywhere. For example, we can move it to the background, and everything would still work fine:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  ...

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">VStack</span> {
        <span class="type">Button</span>(...) { ... }
        <span class="type">Button</span>(...) { ... }
        <span class="type">Button</span>(...) { ... }
      }
      .<span class="call">background</span>(
        <span class="type">NavigationLink</span>(item: $showingNavigation, destination: presentNavigation)
      )
    }
  }

  ...
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/programmatic-navigation/identifiable.gif"/><p>As this would probably be the ideal way to use our <code>NavigationLink</code> extension, we can define a helper <code>View</code> function that enables us to so:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">func</span> navigation&lt;V: <span class="type">Identifiable</span>, Destination: <span class="type">View</span>&gt;(
    item: <span class="type">Binding</span>&lt;<span class="type">V</span>?&gt;,
    destination: <span class="keyword">@escaping</span> (<span class="type">V</span>) -&gt; <span class="type">Destination</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">background</span>(<span class="type">NavigationLink</span>(item: item, destination: destination))
  }
}
</code></pre><p>Making our final API look very similar to other SwiftUI presentations (for sheets, alerts, etc.):</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  ...

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">VStack</span> {
        <span class="type">Button</span>(...) { ... }
        <span class="type">Button</span>(...) { ... }
        <span class="type">Button</span>(...) { ... }
      }
      .<span class="call">navigation</span>(item: $showingNavigation, destination: presentNavigation)
    }
  }

  ...
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/programmatic-navigation/identifiable.gif"/><p>The complete working project can be found <a href="https://github.com/zntfdr/FiveStarsCodeSamples/tree/main/Identifiable-Navigation">here</a>.</p><h2>Conclusions</h2><p>The extensions introduced here probably won't cover 100% of the use cases. Still, I'd argue that a similar official API would be very welcome and would solve most of the implementation issues we face today.</p><p>We've seen no change on SwiftUI's navigation APIs at this year's WWDC. However, I wouldn't be surprised if something new would pop up in a new Xcode 11.4-like release, or perhaps at the next WWDC. In the meanwhile, we can always try and solve these challenges ourselves!</p><p>Did you face or are you facing any SwiftUI challenges? How did you solve it? <a href="https://twitter.com/zntfdr">Please let me know!</a></p><p>Thank you for reading, and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swift-protocols</guid><title>Swift protocols in SwiftUI</title><description></description><link>https://www.fivestars.blog/articles/swift-protocols</link><pubDate>Wed, 25 Nov 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Like any other Swift framework, SwiftUI heavily relies on protocols as a core part of its definitions:<br>in previous articles, we've covered examples of SwiftUI's protocols, such as <a href="https://www.fivestars.blog/articles/impossible-swiftui-views/"><code>View</code></a> or <a href="https://www.fivestars.blog/articles/label/"><code>LabelStyle</code></a>.</p><p>Let's take a look at SwiftUI's usage of Swift standard library protocols: <code>Hashable</code>, <code>Identifiable</code>, and <code>Equatable</code>.</p><blockquote><p>This article is not an introduction to such protocols. If you need a quick refresher, <a href="https://twitter.com/mattt">Mattt</a>'s <a href="https://nshipster.com/">NSHipster</a> has your back: <a href="https://nshipster.com/equatable-and-comparable/"><code>Equatable</code> and <code>Comparable</code></a>, <a href="https://nshipster.com/hashable/"><code>Hashable</code> / <code>Hasher</code></a>, and <a href="https://nshipster.com/identifiable/"><code>Identifiable</code></a>.</p></blockquote><h2>Equatable</h2><p>SwiftUI is as lazy as it gets:<br>nothing is redrawn unless deemed necessary, <a href="https://www.fivestars.blog/articles/preferencekey-reduce/">nothing is computed unless there's demand for it</a>.</p><p><code>Equatable</code> is one of the unsung heroes of SwiftUI's performance. Computing layouts, drawing components, etc., is expensive: the less SwiftUI does so, the better performance.</p><p><code>Equatable</code> is what's used by SwiftUI to take such decisions: even for views that do not declare <code>Equatable</code> conformance, SwiftUI traverses the view definition via Swift reflection and check for equatability of each property and decides if a redrawn is needed base on that.</p><p>This is just a sneak peek of what's happening behind the scenes: for a closer look, please refer to <a href="https://twitter.com/SwiftUILab">Javier Nigro</a>'s <a href="https://swiftui-lab.com/equatableview/">The Mystery Behind View Equality</a>.</p><h2>Identifiable</h2><p>While equatability is used to detect a view state change (therefore triggering a redraw), <code>Identifiable</code> separates a view identity from its state.</p><p>This is used in SwiftUI, for example to track elements order in <a href="https://developer.apple.com/documentation/swiftui/list"><code>List</code></a> and <a href="https://developer.apple.com/documentation/swiftui/outlinegroup"><code>OutlineGroup</code></a>:</p><ul><li>imagine a list of elements where some reordering might happen. If the reordering doesn't involve any other change, only the list will need to update the cell order, but no cell will need to redraw.</li><li>on the other hand, if only a cell state changes but the ordering doesn't, just the specific cell will need to redraw, while the list itself doesn't have to do anything.</li></ul><p>Another example where <code>Identifiable</code> is used is in <a href="https://developer.apple.com/documentation/swiftui/picker"><code>Picker</code></a>:<br>in this case, <code>Identifiable</code> is used to determine which element among the possible candidates is the selected one (if any), regardless of other possible states within the element type definition.</p><p>Lastly, <code>Identifiable</code> is used by all system alerts/sheets:</p><ul><li><a href="https://developer.apple.com/documentation/swiftui/view/sheet(item:ondismiss:content:)"><code>sheet(item:onDismiss:content:)</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/view/fullscreencover(item:ondismiss:content:)"><code>fullScreenCover(item:onDismiss:content:)</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/view/popover(ispresented:attachmentanchor:arrowedge:content:)"><code>popover(item:attachmentAnchor:arrowEdge:content:)</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/view/alert(item:content:)"><code>alert(item:content:)</code></a></li><li><a href="https://developer.apple.com/documentation/swiftui/view/actionsheet(item:content:)"><code>actionSheet(item:content:)</code></a></li></ul><blockquote><p>These also offer a simpler <code>isPresented</code> boolean alternative, using the same thought process we covered in <a href="https://www.fivestars.blog/articles/hashable-bindings/">Hashable SwiftUI bindings</a>.</p></blockquote><p>In this case (I believe), the idea to use <code>Identifiable</code> is to give us a chance to clearly define the possible different alerts/sheets and pass more data if needed.</p><p>Imagine having two different sheets:</p><pre><code><span class="keyword">enum</span> ContentViewSheet: <span class="type">Identifiable</span> {
  <span class="keyword">case</span> one
  <span class="keyword">case</span> two

  <span class="keyword">var</span> id: <span class="type">Int</span> {
    <span class="keyword">switch self</span> {
    <span class="keyword">case</span> .<span class="dotAccess">one</span>:
      <span class="keyword">return</span> <span class="number">1</span>
    <span class="keyword">case</span> .<span class="dotAccess">two</span>:
      <span class="keyword">return</span> <span class="number">2</span>
    }
  }
}
</code></pre><p><code>ContentViewSheet</code> has a case for each sheet, we can this definition along with <code>sheet(item:onDismiss:content:)</code> as following:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> showingSheet: <span class="type">ContentViewSheet</span>?

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">Button</span>(<span class="string">"go to sheet 1"</span>) {
        showingSheet = .<span class="dotAccess">one</span>
      }

      <span class="type">Button</span>(<span class="string">"go to sheet 2"</span>) {
        showingSheet = .<span class="dotAccess">two</span>
      }
    }
    .<span class="call">sheet</span>(item: $showingSheet, content: presentSheet)
  }

  <span class="keyword">@ViewBuilder
  private func</span> presentSheet(for sheet: <span class="type">ContentViewSheet</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">switch</span> sheet {
    <span class="keyword">case</span> .<span class="dotAccess">one</span>:
      <span class="type">Text</span>(<span class="string">"One"</span>)
    <span class="keyword">case</span> .<span class="dotAccess">two</span>:
      <span class="type">Text</span>(<span class="string">"Two"</span>)
    }
  }
}
</code></pre><p>This view shows a couple of buttons that, when tapped, will trigger the display of the associated sheet:</p><img src="https://www.fivestars.blog/assets/posts/swift-protocols/sheet.gif"/><p>Let's say that we would like to pass a value to the second sheet. A way to do so (which might not be the most correct/recommended) is by extending the second <code>ContentViewSheet</code> case with an associated value:</p><pre><code><span class="keyword">enum</span> ContentViewSheet: <span class="type">Identifiable</span> {
  <span class="keyword">case</span> one
  <span class="keyword">case</span> two(someData: <span class="type">Int</span>) <span class="comment">// new associated value

  /// The identity ignores the `someData` value.</span>
  <span class="keyword">var</span> id: <span class="type">Int</span> {
    <span class="keyword">switch self</span> {
    <span class="keyword">case</span> .<span class="dotAccess">one</span>:
      <span class="keyword">return</span> <span class="number">1</span>
    <span class="keyword">case</span> .<span class="dotAccess">two</span>:
      <span class="keyword">return</span> <span class="number">2</span>
    }
  }
}
</code></pre><p>Thanks to <code>someData</code>, we can now pass an <code>Int</code> value (or any other value really) when creating a new sheet:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  ...

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      ...

      <span class="type">Button</span>(<span class="string">"go to sheet 2"</span>) {
        showingSheet = .<span class="call">two</span>(someData: <span class="type">Int</span>.<span class="call">random</span>(in: <span class="number">1</span>...<span class="number">5</span>)) <span class="comment">// Pass data here</span>
      }
    }
    .<span class="call">sheet</span>(item: $showingSheet, content: presentSheet)
  }

  <span class="keyword">@ViewBuilder
  private func</span> presentSheet(for sheet: <span class="type">ContentViewSheet</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">switch</span> sheet {
      ...
    <span class="keyword">case</span> .<span class="dotAccess">two</span>(<span class="keyword">let</span> value): <span class="comment">// read and use the data here</span>
      <span class="type">Text</span>(<span class="string">"Sheet two with value</span> \(value)<span class="string">"</span>)
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/swift-protocols/sheet2.gif"/><h2>Hashable</h2><p><code>Hashable</code> is used mainly in two views, <a href="https://developer.apple.com/documentation/swiftui/tabview"><code>TabView</code></a> and <a href="https://developer.apple.com/documentation/swiftui/navigationlink"><code>NavigationLink</code></a>, and for <code>@FocusState</code>.</p><p>These views work with tags, which are our way to connect view declarations to the values of the associated <code>Hashable</code> types SwiftUI needs to listen to.</p><p>In <code>TabView</code> case, we might declare each possible tab as follows:</p><pre><code><span class="keyword">enum</span> Tab: <span class="type">Hashable</span> {
  <span class="keyword">case</span> home
  <span class="keyword">case</span> view2
  <span class="keyword">case</span> view3
  <span class="keyword">case</span> view4
}
</code></pre><p>Beside the type declaration, we now need a way to associate each view to each tab, and this is done via the <a href="https://developer.apple.com/documentation/swiftui/view/tag(_:)"><code>tag(_:)</code></a> method:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> tabBarState: <span class="type">Tab</span> = .<span class="dotAccess">home</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TabView</span>(selection: $tabBarState) {
      <span class="type">Text</span>(<span class="string">"Home"</span>)
        .<span class="call">tabItem</span> { <span class="type">Text</span>(<span class="string">"Home"</span>) }
        .<span class="call">tag</span>(<span class="type">Tab</span>.<span class="property">home</span>)

      <span class="type">Text</span>(<span class="string">"View 2"</span>)
        .<span class="call">tabItem</span> { <span class="type">Text</span>(<span class="string">"View 2"</span>) }
        .<span class="call">tag</span>(<span class="type">Tab</span>.<span class="property">view2</span>)

      <span class="type">Text</span>(<span class="string">"View 3"</span>)
        .<span class="call">tabItem</span> { <span class="type">Text</span>(<span class="string">"View 3"</span>) }
        .<span class="call">tag</span>(<span class="type">Tab</span>.<span class="property">view3</span>)

      <span class="type">Text</span>(<span class="string">"View 4"</span>)
        .<span class="call">tabItem</span> { <span class="type">Text</span>(<span class="string">"View 4"</span>) }
        .<span class="call">tag</span>(<span class="type">Tab</span>.<span class="property">view4</span>)
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/swift-protocols/tab.gif"/><p>In a similar manner, as <a href="https://www.fivestars.blog/articles/hashable-bindings/">we've seen previously</a>, we need to declare a new <code>NavigationLink</code> for each possible destination:</p><pre><code><span class="keyword">enum</span> ContentViewNavigation: <span class="type">Hashable</span> {
  <span class="keyword">case</span> a <span class="comment">// destination a</span>
  <span class="keyword">case</span> b <span class="comment">// destination b</span>
  <span class="keyword">case</span> c <span class="comment">// destination a</span>
}

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> showingContent: <span class="type">ContentViewNavigation</span>?

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">VStack</span> {
        <span class="type">NavigationLink</span>(<span class="string">"Go to A"</span>, destination: <span class="type">Text</span>(<span class="string">"A"</span>), tag: .<span class="dotAccess">a</span>, selection: $showingContent)
        <span class="type">NavigationLink</span>(<span class="string">"Go to B"</span>, destination: <span class="type">Text</span>(<span class="string">"B"</span>), tag: .<span class="dotAccess">b</span>, selection: $showingContent)
        <span class="type">NavigationLink</span>(<span class="string">"Go to C"</span>, destination: <span class="type">Text</span>(<span class="string">"C"</span>), tag: .<span class="dotAccess">c</span>, selection: $showingContent)
      }
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/hashable-bindings/destination.gif"/><p>While this tag approach makes total sense for <code>TabView</code>s, as of today, this is also the most advanced way to control navigation within an app thanks to <code>NavigationLink</code>:<br>this becomes cumbersome when we want to pass data to the destination view, especially when doing so programmatically (deep linking, anyone?).</p><p>This is probably one of the most awkward points when moving from UIKit to SwiftUI:<br>I'm sure the SwiftUI team had reasons to choose this <em>route</em> just for navigation instead of the <code>Identifiable</code> approach used by all other view presentations (as seen above). Still, I sure hope we will see some evolutions on this in the future to streamline things (FB8910787).</p><h2>Conclusions</h2><p>Similarly to how to really understand UIKit we need to grasp and understand Objective-C; learning and understanding SwiftUI requires us to learn and understand Swift: the more we learn about one, the more we learn about the other.</p><p>SwiftUI might not be "complete" nor "perfect" as it stands today, but as mentioned in the <a href="https://www.fivestars.blog/articles/content-friendly-layouts/">last article</a>, the current state presents us a great opportunity to learn in depth both the framework itself and the Swift language.</p><p>Do you have any leads on the reason behind the use of <code>Hashable</code> for Navigation? <a href="https://twitter.com/zntfdr">Please let me know</a>!</p><p>Thank you for reading, and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/content-friendly-layouts</guid><title>How to build content-friendly layouts in SwiftUI</title><description>A tour on how to change layouts based on available space in SwiftUI</description><link>https://www.fivestars.blog/articles/content-friendly-layouts</link><pubDate>Tue, 17 Nov 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>In <a href="https://www.fivestars.blog/articles/adaptive-swiftui-views/">Adaptive SwiftUI views</a>, we've covered many approaches on how to make our layouts responsive to different external factors such as size classes, dynamic type, and more.</p><p>While these are awesome, they don't consider another important factor: the actual content size.<br>Let's address this!</p><h2>The UIKit way</h2><p>WWDC20 session <a href="http://www.wwdcnotes.com/notes/wwdc20/10219/">Build localization-friendly layouts using Xcode</a> covers precisely how to do so in UIKit:<br>the session introduces <code>ReadjustingStackView</code>, a horizontal stack that turns vertical when the available horizontal space is less than the content needs. Here's the session demo project:</p><table><tbody><tr><td><img src="https://www.fivestars.blog/assets/posts/content-friendly-layouts/horizontalStack.gif"/></td><td><img src="https://www.fivestars.blog/assets/posts/content-friendly-layouts/adaptiveStack.gif"/></td></tr></tbody></table><blockquote><p>Left: a standard horizontal <code>UIStackView</code>. Right: the new <code>ReadjustingStackView</code>.</p></blockquote><p>And this is <code>ReadjustingStackView</code>'s definition:</p><pre><code><span class="keyword">class</span> ReadjustingStackView: <span class="type">UIStackView</span> {

  <span class="comment">/// To know the size of our margins without hardcoding them, we have an
  /// outlet to a leading space constraint to read the constant value.</span>
  <span class="keyword">@IBOutlet var</span> leadingConstraint: <span class="type">NSLayoutConstraint</span>!

  <span class="keyword">required init</span>(coder: <span class="type">NSCoder</span>) {
    <span class="keyword">super</span>.<span class="keyword">init</span>(coder: coder)
    <span class="comment">// We want to recalculate our orientation whenever the dynamic type settings
    // on the device change.</span>
    <span class="type">NotificationCenter</span>.<span class="property">default</span>.<span class="call">addObserver</span>(
      <span class="keyword">self</span>,
      selector: <span class="keyword">#selector</span>(adjustOrientation),
      name: <span class="type">UIContentSizeCategory</span>.<span class="property">didChangeNotification</span>,
      object: <span class="keyword">nil</span>
    )
  }

  <span class="comment">/// This takes care of recalculating our orientation whenever our content or
  /// layout changes (such as due to device rotation, addition of more buttons
  /// to the stack view, etc).</span>
  <span class="keyword">override func</span> layoutSubviews() {
    <span class="call">adjustOrientation</span>()
  }

  <span class="keyword">@objc
  func</span> adjustOrientation() {
    <span class="comment">// Always attempt to fit everything horizontally first</span>
    axis = .<span class="dotAccess">horizontal</span>
    alignment = .<span class="dotAccess">firstBaseline</span>

    <span class="keyword">let</span> desiredStackViewWidth = <span class="call">systemLayoutSizeFitting</span>(
      <span class="type">UIView</span>.<span class="property">layoutFittingCompressedSize</span>
    ).<span class="property">width</span>

    <span class="keyword">if let</span> parent = superview {
      <span class="keyword">let</span> availableWidth = parent.<span class="property">bounds</span>.<span class="call">inset</span>(by: parent.<span class="property">safeAreaInsets</span>).<span class="property">width</span> - (leadingConstraint.<span class="property">constant</span> * <span class="number">2.0</span>)
      <span class="keyword">if</span> desiredStackViewWidth &gt; availableWidth {
        axis = .<span class="dotAccess">vertical</span>
        alignment = .<span class="dotAccess">fill</span>
      }
    }
  }
}
</code></pre><p>The magic happens in <code>adjustOrientation()</code>, where we do the following:</p><ul><li>compute the horizontal space our content would take if laid out horizontally</li><li>compute the actual available space</li><li>compare the two values above, and decide the final orientation</li></ul><p>Thanks to Apple, we're done in the UIKit world: <code>ReadjustingStackView</code> works as advertised, and its implementation is straightforward. Let's move to SwiftUI next.</p><h2>The SwiftUI way</h2><p>The aforementioned WWDC20 session doesn't provide a solution in SwiftUI; however, we can take the same steps as in <code>ReadjustingStackView</code>'s <code>adjustOrientation()</code> and apply them here.</p><p>For simplicity's sake, we're going to change the order of the steps slightly:</p><ol><li>compute the horizontal space available</li><li>compute the content total horizontal space</li><li>decide the layout orientation</li></ol><h3>1. Compute the horizontal space available</h3><p>If this sounds strangely familiar, it's because we covered how to do so in the first step of <a href="https://www.fivestars.blog/articles/flexible-swiftui/">Flexible layouts in SwiftUI</a>, which was based on <a href="https://www.fivestars.blog/articles/swiftui-share-layout-information/">Sharing layout information in SwiftUI</a>.</p><p>Without repeating the same content here, please refer to <a href="https://www.fivestars.blog/articles/swiftui-share-layout-information/">those</a> <a href="https://www.fivestars.blog/articles/flexible-swiftui/">two</a> articles.</p><p>The outcome is a new view, which we will conveniently call <code>ReadjustingStackView</code>, with knowledge on how much horizontal space is available (stored in its <code>availableWidth</code> property):</p><pre><code><span class="keyword">struct</span> ReadjustingStackView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> availableWidth: <span class="type">CGFloat</span> = <span class="number">0</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ZStack</span> {
      <span class="type">Color</span>.<span class="property">clear</span>
        .<span class="call">frame</span>(height: <span class="number">1</span>)
        .<span class="call">readSize</span> { size <span class="keyword">in</span>
          availableWidth = size.<span class="property">width</span>
        }

      <span class="comment">// Rest of our implementation</span>
    }
  }
}
</code></pre><p>We can now move to our second step.</p><h3>2. Compute the content total horizontal space</h3><p>This step is solved by collecting the size of all elements in our content:<br>this is equivalent to step 2 of <a href="https://www.fivestars.blog/articles/flexible-swiftui/">Flexible layouts in SwiftUI</a>, which bring us to the following outcome:</p><pre><code><span class="keyword">struct</span> ReadjustingStackView&lt;Data: <span class="type">RandomAccessCollection</span>, Content: <span class="type">View</span>&gt;: <span class="type">View</span> <span class="keyword">where</span> <span class="type">Data</span>.<span class="type">Element</span>: <span class="type">Hashable</span> {
  <span class="keyword">let</span> data: <span class="type">Data</span>
  <span class="keyword">let</span> content: (<span class="type">Data</span>.<span class="type">Element</span>) -&gt; <span class="type">Content</span>
  <span class="keyword">@State private var</span> elementsSize: [<span class="type">Data</span>.<span class="type">Element</span>: <span class="type">CGSize</span>] = [:]

  <span class="comment">// ...</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ZStack</span> {
      <span class="comment">// ...</span>

      <span class="type">ForEach</span>(data, id: \.<span class="keyword">self</span>) { element <span class="keyword">in</span>
        <span class="call">content</span>(element)
          .<span class="call">fixedSize</span>()
          .<span class="call">readSize</span> { size <span class="keyword">in</span>
            elementsSize[element] = size
          }
      }
    }
  }
}
</code></pre><p>With this, we've completed our second step! It's fantastic when we can apply the knowledge that we already have.</p><h3>3. Decide the final layout orientation</h3><p>With both our <code>availableWidth</code> and <code>elementsSize</code>, it's time to implement the logic that compare the two and decide which axis our layout will take, a way to do so is the following:</p><pre><code><span class="keyword">func</span> isHorizontal() -&gt; <span class="type">Bool</span> {
  <span class="keyword">let</span> desiredStackViewWidth = data.<span class="call">reduce</span>(into: <span class="number">0</span>) { totalWidth, element <span class="keyword">in
    let</span> elementSize = elementsSize[element, default: <span class="type">CGSize</span>(width: availableWidth, height: <span class="number">1</span>)]
    totalWidth += elementSize.<span class="property">width</span>
  }

  <span class="keyword">return</span> availableWidth &gt; desiredStackViewWidth
}
</code></pre><p><code>isHorizontal</code> tells us whether we should use a <code>HStack</code> or a <code>VStack</code> by computing the total horizontal width first, and then comparing it with our <code>availableWidth</code>.</p><p>With the orientation in our hands, all is left to do is the actual view declaration:</p><pre><code><span class="keyword">struct</span> ReadjustingStackView&lt;...&gt;: <span class="type">View</span> <span class="keyword">where</span> ... {
  <span class="comment">// ...</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ZStack</span> {
      <span class="comment">// ...</span>

      <span class="keyword">if</span> <span class="call">isHorizontal</span>() {
        <span class="type">HStack</span>(spacing: spacing) {
          elementsViews
        }
      } <span class="keyword">else</span> {
        <span class="type">VStack</span>(spacing: spacing) {
          elementsViews
        }
      }
    }
  }

  <span class="keyword">var</span> elementsViews: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ForEach</span>(data, id: \.<span class="keyword">self</span>) { element <span class="keyword">in</span>
      <span class="call">content</span>(element)
        .<span class="call">fixedSize</span>()
        .<span class="call">readSize</span> { size <span class="keyword">in</span>
          elementsSize[element] = size
        }
    }
  }

  <span class="comment">// ...</span>
}
</code></pre><p>We extract <code>elementsViews</code> from the body declaration just to avoid having to repeat the same code twice.</p><blockquote><p>Switching between the two layouts is effectively as if we were drawing those elements from scratch (therefore, all events such as <a href="https://developer.apple.com/documentation/swiftui/text/onappear(perform:)"><code>onAppear</code></a> will be triggered when switching).</p></blockquote><p>With this, we’re done! The <a href="https://github.com/zntfdr/FiveStarsCodeSamples/tree/main/Content-Friendly-Layouts">final project</a> also considers the spacing between elements in the stack (left out from the article for simplicity's sake):</p><img src="https://www.fivestars.blog/assets/posts/content-friendly-layouts/swiftUI.gif"/><h2>Conclusions</h2><p>While our SwiftUI solution might not be as intuitive and as concise as the UIKit one, we must also keep in mind that we're comparing a 10+ years old framework with a 1.5 years old one:<br>I'm sure more SwiftUI view extensions are coming, making things such as getting an intrinsic view size easier or getting a proposed size possible.</p><p>With that being said, I see the current limitations as opportunities: the more we struggle to find ways to implement something today, the more we learn about how SwiftUI works.</p><p>What challenges have you faced with SwiftUI? Did you implement something tricky? <a href="https://twitter.com/zntfdr">Please feel free to reach me out</a>, I'd love to know!</p><p>Thank you for reading, and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/disfavoredOverload</guid><title>What is @_disfavoredOverload in Swift?</title><description>A journey into Swift overloading thanks to this private attribute</description><link>https://www.fivestars.blog/articles/disfavoredOverload</link><pubDate>Tue, 10 Nov 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<blockquote><p>Many thanks to <a href="https://forums.swift.org/t/how-to-determine-if-a-passed-argument-is-a-string-literal/41651/3">Stefano De Carolis</a> for the tip!</p></blockquote><p>Swift allows <a href="https://en.wikipedia.org/wiki/Function_overloading">function overloading</a>, a.k.a. it lets us declare two or more functions with the same name but different arguments and/or return types:</p><pre><code><span class="keyword">func</span> sameName() {
  ...
}

<span class="keyword">func</span> sameName(parameter: <span class="type">Int</span>) {
  ...
}
</code></pre><p>In the example above it's always clear which function is called based on the parameter presence:</p><pre><code><span class="call">sameName</span>() <span class="comment">// no parameter =&gt; calls sameName()</span>
<span class="call">sameName</span>(parameter: <span class="number">1</span>) <span class="comment">// parameter =&gt; calls sameName(parameter:)</span>
</code></pre><p>This is pretty straight forward and expected behavior.</p><p>Swift, however, has many different ways to overload a function, which can bring to ambiguous situations where the behavior is not so easy to predict, for example:</p><h3>Void vs. default value</h3><pre><code><span class="keyword">func</span> sameName() {
  ...
}

<span class="keyword">func</span> sameName(value: <span class="type">Int</span> = <span class="number">5</span>) {
  ...
}
</code></pre><p>A function accepts no parameters and the other accepts one with a default value:</p><pre><code><span class="call">sameName</span>(value: <span class="number">1</span>) <span class="comment">// parameter passed =&gt; calls sameName(value:)</span>
<span class="call">sameName</span>() <span class="comment">// no parameter passed, both declarations match =&gt; ??</span>
</code></pre><h3>Protocol vs. conforming type of that protocol</h3><pre><code><span class="keyword">func</span> sameName(<span class="keyword">_</span> number: <span class="type">Int</span>) {
  ...
}

<span class="keyword">func</span> sameName&lt;N: <span class="type">Numeric</span>&gt;(<span class="keyword">_</span> number: <span class="type">N</span>) {
  ...
}
</code></pre><p>Both functions accept a parameter, one requires an <code>Int</code> instance while the other requires an instance conforming to <a href="https://developer.apple.com/documentation/swift/numeric"><code>Numeric</code></a> (which <code>Int</code> conforms to):</p><pre><code><span class="call">sameName</span>(<span class="number">5.0</span>) <span class="comment">// not an Int =&gt; calls Numeric function</span>
<span class="call">sameName</span>(<span class="number">5</span>) <span class="comment">// Int, both declarations match =&gt; ??</span>
</code></pre><h3>Variadic vs. non</h3><pre><code><span class="keyword">func</span> sameName(<span class="keyword">_</span> number: <span class="type">Int</span>) {
  ...
}

<span class="keyword">func</span> sameName(<span class="keyword">_</span> numbers: <span class="type">Int</span>...) {
  ...
}
</code></pre><p>Here we accept an <code>Int</code> in one function, and zero or more <code>Int</code>s in the other (a.k.a, we have a variadic parameter):</p><pre><code><span class="call">sameName</span>(<span class="number">5</span>, <span class="number">5</span>, <span class="number">5</span>) <span class="comment">// Multiple values are passed =&gt; variadic function</span>
<span class="call">sameName</span>(<span class="number">5</span>) <span class="comment">// One value is passed, both declarations match =&gt; ??</span>
</code></pre><h2>The hidden principle</h2><p>It's quite simple to find ourselves in an ambiguous situation, while not officially documented, former Swift team member <a href="https://twitter.com/UINT_MIN">Jordan Rose</a> has <a href="https://forums.swift.org/t/compiler-choosing-between-functions-with-no-parameters-vs-functions-with-default-parameters/23501/2">shed some light</a> on the <em>secret formula</em>:</p><p><a href="https://forums.swift.org/t/compiler-choosing-between-functions-with-no-parameters-vs-functions-with-default-parameters/23501/2">"The general principle is that the most specific overload should win"</a>.</p><p>Going back to our examples:</p><h3>Void vs. default value</h3><pre><code><span class="keyword">func</span> sameName()
<span class="keyword">func</span> sameName(value: <span class="type">Int</span> = <span class="number">5</span>)
</code></pre><p><code>sameName()</code> triggers the first function:<br>a function that always takes 0 parameters is <em>more specific</em> than a function that sometimes takes 0 parameters.</p><h3>Protocol vs. conforming type of that protocol</h3><pre><code><span class="keyword">func</span> sameName(<span class="keyword">_</span> number: <span class="type">Int</span>)
<span class="keyword">func</span> sameName&lt;N: <span class="type">Numeric</span>&gt;(<span class="keyword">_</span> number: <span class="type">N</span>)
</code></pre><p><code>sameName(5)</code> triggers the first function:<br>a function accepting <code>Int</code> is <em>more specific</em> than a function accepting any type conforming to <code>Numeric</code>.</p><h3>Variadic vs. non</h3><pre><code><span class="keyword">func</span> sameName(<span class="keyword">_</span> number: <span class="type">Int</span>)
<span class="keyword">func</span> sameName(<span class="keyword">_</span> numbers: <span class="type">Int</span>...)
</code></pre><p><code>sameName(5)</code> triggers the first function:<br>a function that always takes 1 parameter is more specific than a function that takes 0+ parameters.</p><h2>@_disfavoredOverload</h2><blockquote><p>The underscore in front of the attribute means that this Swift feature hasn't gone through Swift Evolution just yet. Proceed at your discretion.</p></blockquote><p>Thanks to this principle, we have an excellent way to predict Swift's behavior in case of ambiguity.</p><p>While this is great, sometimes, as API/library designers, the default behavior is not what we would like to have:<br>in such situations, we can use the <code>@_disfavoredOverload</code> attribute, which will lower the rank of the associated function, making less <em>specific</em> functions win over more specific ones.</p><p>Going back to an earlier example:</p><pre><code><span class="keyword">func</span> sameName() { ... }
<span class="keyword">func</span> sameName(value: <span class="type">Int</span> = <span class="number">5</span>) { ... }
</code></pre><p>we know that calling <code>sameName()</code> matches the first function, however, if we introduce <code>@_disfavoredOverload</code>, this changes:</p><pre><code><span class="keyword">@_disfavoredOverload 
func</span> sameName() { ... }

<span class="keyword">func</span> sameName(value: <span class="type">Int</span> = <span class="number">5</span>) { ... }
</code></pre><p>With this updated declaration calling <code>sameName()</code> will match the second function instead.</p><p>It's very crucial to note that this attribute won't show up in our library headers:<br>if we use this attribute on our public declarations, it's recommended to document the behavior, which otherwise may cause surprises to adopters of our API.</p><h2>SwiftUI</h2><p><a href="https://forums.swift.org/t/how-to-determine-if-a-passed-argument-is-a-string-literal/41651/6">SwiftUI relies</a> on <code>@_disfavoredOverload</code> in many of its declarations.</p><p>An example is <a href="https://developer.apple.com/documentation/swiftui/text"><code>Text</code></a>, which we covered in <a href="https://www.fivestars.blog/articles/displaying-text-swiftui/">Displaying text in SwiftUI</a>. <code>Text</code> comes with multiple initializers. Let's focus on two in particular:</p><pre><code><span class="keyword">public init</span>&lt;S: <span class="type">StringProtocol</span>&gt;(<span class="keyword">_</span> content: <span class="type">S</span>)
<span class="keyword">public init</span>(<span class="keyword">_</span> key: <span class="type">LocalizedStringKey</span>, tableName: <span class="type">String</span>? = <span class="keyword">nil</span>, bundle: <span class="type">Bundle</span>? = <span class="keyword">nil</span>, comment: <span class="type">StaticString</span>? = <span class="keyword">nil</span>)
</code></pre><p>From what we've seen so far, we know that calling <code>Text("a string")</code> would match the first declaration. However, if we read <code>init&lt;S: StringProtocol&gt;(_ content: S)</code>'s documentation, we will see the following message:</p><pre><code><span class="comment">/// SwiftUI doesn't call the `init(_:)` method when you initialize a text
/// view with a string literal as the input. Instead, a string literal
/// triggers the ``Text/init(_:tableName:bundle:comment:)`` method — which
/// treats the input as a ``LocalizedStringKey`` instance — and attempts to
/// perform localization.</span>
</code></pre><p>This works because <a href="https://developer.apple.com/documentation/swiftui/localizedstringkey"><code>LocalizedStringKey</code></a> conforms to <a href="https://developer.apple.com/documentation/swift/expressiblebystringliteral"><code>ExpressibleByStringLiteral</code></a>:<br>as this key is the only required parameter in <code>init(_:tableName:bundle:comment:)</code>, a definition such as <code>Text("a string")</code> matches both initializers, and associating <code>@_disfavoredOverload</code> to the <code>StringProtocol</code>'s <code>init</code> makes the other initializer the preferred one.</p><p>Again, like in <code>Text</code>'s case, it's imperative to document such behavior as otherwise adopters of our API would be met with unexpected results.</p><p>The reason behind this <code>Text</code> behavior is to make SwiftUI's API easy to use:</p><ul><li>if we pass a string literal (e.g. <code>Text("my_title")</code>), the framework will turn the string into a <code>LocalizedStringKey</code> and attempt to localize it</li><li>if we don't pass a string literal (e.g. <code>Text(stringVariable)</code>), the framework will display the value as is, without localization</li></ul><h2>Conclusions</h2><p>Function overloading is a powerful tool that enables us to define multiple functions of the same name with different implementations:<br>in this article, we've covered how Swift addresses ambiguity and how we can (<em>cautiously</em>) use the <code>@_disfavoredOverload</code> attribute in case of multiple matches.</p><p>Do you use this approach yourself? Have you seen any other cool use of <code>@_disfavoredOverload</code>? Please <a href="https://twitter.com/zntfdr">let me know</a>!</p><p>Thank you for reading, and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/displaying-text-swiftui</guid><title>Displaying text in SwiftUI</title><description></description><link>https://www.fivestars.blog/articles/displaying-text-swiftui</link><pubDate>Tue, 3 Nov 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>In <a href="https://www.fivestars.blog/articles/design-system-composing-views/">Composing SwiftUI views</a> we've covered an example on how to start building a design system, and how it's very beneficial to have one as early as possible in any project.</p><p>In this article, let's continue building our design system with a new component, a button:</p><img src="https://www.fivestars.blog/assets/posts/displaying-text-swiftui/button.png"/><h2>The start</h2><p>In this case we're lucky, as our app only has one design. We don't even need to create a new <a href="https://developer.apple.com/documentation/swiftui/buttonstyle"><code>ButtonStyle</code></a>! Just in case, we build one anyway:</p><pre><code><span class="keyword">struct</span> FSButton: <span class="type">View</span> {
  <span class="keyword">let</span> titleKey: <span class="type">LocalizedStringKey</span>
  <span class="keyword">let</span> action: () -&gt; <span class="type">Void</span>

  <span class="keyword">init</span>(<span class="keyword">_</span> titleKey: <span class="type">LocalizedStringKey</span>, action: <span class="keyword">@escaping</span> () -&gt; <span class="type">Void</span>) {
    <span class="keyword">self</span>.<span class="property">titleKey</span> = titleKey
    <span class="keyword">self</span>.<span class="property">action</span> = action
  }

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(action: action, label: { <span class="type">Text</span>(titleKey).<span class="call">bold</span>() })
      .<span class="call">buttonStyle</span>(<span class="type">FSButtonStyle</span>())
  }
}

<span class="keyword">private struct</span> FSButtonStyle: <span class="type">ButtonStyle</span> {
  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">HStack</span> {
      <span class="type">Spacer</span>()
      configuration.<span class="property">label</span>
        .<span class="call">foregroundColor</span>(.<span class="dotAccess">white</span>)
      <span class="type">Spacer</span>()
    }
    .<span class="call">padding</span>()
    .<span class="call">background</span>(
      <span class="type">RoundedRectangle</span>(cornerRadius: <span class="number">8</span>, style: .<span class="dotAccess">continuous</span>).<span class="call">fill</span>(<span class="type">Color</span>.<span class="property">green</span>)
    )
    .<span class="call">opacity</span>(configuration.<span class="property">isPressed</span> ? <span class="number">0.5</span> : <span class="number">1</span>)
  }
}
</code></pre><p>Which we can use as follows:</p><pre><code><span class="type">FSButton</span>(<span class="string">"Button title"</span>) {
  <span class="comment">// button tap action</span>
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/displaying-text-swiftui/button.gif"/><p>Great! We’re happy with <code>FSButton</code>: we add a couple of previews and move to the next design system component.</p><h2>One week later</h2><p>One week pass by and we're told by management that some of the buttons text will be provided by our backend instead of being handled by the app.</p><p>In order to use our current API, we would need to explicitly convert the backend string into a <code>LocalizedStringKey</code>:</p><pre><code><span class="keyword">var</span> backendString: <span class="type">String</span>

...

<span class="type">FSButton</span>(<span class="type">LocalizedStringKey</span>(backendString), action: buttonDidTap)
</code></pre><p>However this is "<em>not ideal</em>" in many levels:</p><ul><li>we're misusing <code>LocalizedStringKey</code></li><li>we're triggering a runtime lookup for a string that we already know won't be in our <code>Localizable.string</code> table</li><li>it's just...<em>not ideal</em></li></ul><p>A nicer way to handle this would be to use the <code>init&lt;S: StringProtocol&gt;(_ content: S)</code> <code>Text</code> initializer, of which purpose is to display strings as they are, without trying to localize them first.</p><p>We need a way to accommodate both this new initializer and also our previous one. A solution is to have <code>FSButton</code> replace its <code>titleKey</code> <code>LocalizedStringKey</code> property with a <code>title</code> <code>Text</code>:</p><pre><code><span class="keyword">struct</span> FSButton: <span class="type">View</span> {
  <span class="keyword">let</span> title: <span class="type">Text</span>
  <span class="keyword">let</span> action: () -&gt; <span class="type">Void</span>

  <span class="keyword">init</span>(titleKey: <span class="type">LocalizedStringKey</span>, action: <span class="keyword">@escaping</span> () -&gt; <span class="type">Void</span>) {
    <span class="keyword">self</span>.<span class="property">title</span> = <span class="type">Text</span>(titleKey)
    <span class="keyword">self</span>.<span class="property">action</span> = action
  }

  <span class="keyword">init</span>&lt;S: <span class="type">StringProtocol</span>&gt;(<span class="keyword">_</span> content: <span class="type">S</span>, action: <span class="keyword">@escaping</span> () -&gt; <span class="type">Void</span>) {
    <span class="keyword">self</span>.<span class="property">title</span> = <span class="type">Text</span>(content)
    <span class="keyword">self</span>.<span class="property">action</span> = action
  }

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(action: action, label: { title.<span class="call">bold</span>() })
      .<span class="call">buttonStyle</span>(<span class="type">FSButtonStyle</span>())
  }
}
</code></pre><p>With this update <code>FSButton</code> initializers will create <em>on the fly</em> the <code>Text</code> instance with the proper initializer:</p><pre><code><span class="type">FSButton</span>(titleKey: <span class="string">"my_localized_title"</span>, action: { ... })

<span class="keyword">var</span> backendString: <span class="type">String</span> = ...
<span class="type">FSButton</span>(backendString, action: { ... })
</code></pre><img src="https://www.fivestars.blog/assets/posts/displaying-text-swiftui/vstack.png"/><blockquote><p>You now know how to write "Button title" in Thai!</p></blockquote><h2>One more week later</h2><p>The marketing team has heard about the button's text backend-driven approach, and now they want us to make it possible for them to also drive designs such as this one:</p><img src="https://www.fivestars.blog/assets/posts/displaying-text-swiftui/marketing.png"/><p>This is not possible with our two initializers. However, <code>Text</code> is one of most flexible and dynamic SwiftUI views, it has a whole suite of ad-hoc modifiers that return other <code>Text</code> views, and it's even possible to add <code>Text</code> views to other <code>Text</code> views, with the outcome still being another <code>Text</code>:</p><pre><code><span class="keyword">let</span> text: <span class="type">Text</span> =
  <span class="type">Text</span>(<span class="string">"Default "</span>) +
  <span class="type">Text</span>(<span class="string">"italic "</span>).<span class="call">italic</span>() +
  <span class="type">Text</span>(<span class="string">"Big "</span>).<span class="call">font</span>(.<span class="dotAccess">title</span>) +
  <span class="type">Text</span>(<span class="string">"Red "</span>).<span class="call">foregroundColor</span>(.<span class="dotAccess">red</span>) +
  <span class="type">Text</span>(<span class="string">"underline"</span>).<span class="call">underline</span>()
</code></pre><p>Therefore, in order to support this new request, all we need to do from <code>FSButton</code>'s point of view is to expose a new initializer that accepts a <code>Text</code>:</p><pre><code><span class="keyword">extension</span> <span class="type">FSButton</span> {
  <span class="keyword">init</span>(<span class="keyword">_</span> title: <span class="type">Text</span>, action: <span class="keyword">@escaping</span> () -&gt; <span class="type">Void</span>) {
    <span class="keyword">self</span>.<span class="property">title</span> = title
    <span class="keyword">self</span>.<span class="property">action</span> = action
  }
}
</code></pre><p>Which makes it possible to create views such as:</p><img src="https://www.fivestars.blog/assets/posts/displaying-text-swiftui/complex.png"/><p>Note that, thanks to this last initializer, we can completely override the default style of our button <code>Text</code>, with different font, weight, text color etc. which opens to a variety of new button styles for free.</p><h2>...where does it end?</h2><p>So far we've managed to keep our button content a <code>Text</code> view, however there might be a day in the future where that changes:<br>at that point we will probably need to extend our view to accept a view builder instead, which we've <a href="https://www.fivestars.blog/articles/design-system-composing-views/">already covered here</a>.</p><p>In conclusion, where should we start and what should we expose is really up to us, for example:</p><ul><li>if we're building a library/design system and we want to force whoever adopts it to use our style, we're probably ok by exposing just the two <code>LocalizedStringKey</code> and <code>StringProtocol</code> initializers</li><li>if we want to offer more flexibility, we can let developers pass a <code>Text</code> instance, which opens a whole world of customization, while still requiring the main content to be a <code>Text</code></li><li>lastly, if we really want to open to infinite possibilities, we can expose an initializer that accepts a view builder, at which point the customization limits are our imagination's</li></ul><p>The final project can be found <a href="https://github.com/zntfdr/FiveStarsCodeSamples/tree/main/Displaying-Text-SwiftUI">here</a>.</p><h2>Conclusions</h2><p>Building SwiftUI components displaying text can be trickier than expected, however most use cases can be covered with little work on our side.</p><p>The same approach and thought process we've see here has been applied in many SwiftUI views definitions as well, where different views will expose different initializers.</p><p>If you're looking for inspiration, here are some SwiftUI views that use this approach:<br><code>Button</code>, <code>ColorPicker</code>, <code>CommandMenu</code>, <code>DatePicker</code>, <code>DisclosureGroup</code>, <code>Label</code>, <code>Link</code>, <code>Menu</code>, <code>NavigationLink</code>, <code>Picker</code>, <code>ProgressView</code>, <code>SecureField</code>, <code>Stepper</code>, <code>TextField</code>, <code>Toggle</code>, <code>WindowGroup</code>.</p><p>..and even a few modifiers such as <code>navigationTitle</code> and <code>help</code>.</p><p>Do you use this approach when building SwiftUI views? Have you used/seen any other patterns? Please <a href="https://twitter.com/zntfdr">let me know</a>!</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/hashable-bindings</guid><title>Hashable SwiftUI bindings</title><description></description><link>https://www.fivestars.blog/articles/hashable-bindings</link><pubDate>Tue, 27 Oct 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>We've <a href="https://www.fivestars.blog/articles/optional-binding/">previously covered</a> how to <a href="https://www.fivestars.blog/articles/optional-binding/">add optional bindings to SwiftUI views</a> using <a href="https://developer.apple.com/documentation/swiftui/DisclosureGroup"><code>DisclosureGroup</code></a> as an example.</p><p>Another SwiftUI view with similar API is <a href="https://developer.apple.com/documentation/swiftui/navigationlink"><code>NavigationLink</code></a>:</p><pre><code><span class="comment">// No binding</span>
<span class="type">NavigationLink</span>(
  <span class="string">"Go to view"</span>, 
  destination: <span class="type">DestinationView</span>()
)

<span class="comment">// With binding</span>
<span class="type">NavigationLink</span>(
  <span class="string">"Go to view"</span>, 
  destination: <span class="type">DestinationView</span>(), 
  isActive: $isShowingDestinationView
)
</code></pre><p>This works the same way as with <code>DisclosureGroup</code>:</p><ul><li>if we don't need to manage the <code>DestinationView</code> presentation ourselves, we can use the initializer without binding.</li><li>if we want to manage the <code>DestinationView</code> presentation ourselves, we can use the second initializer, where we pass a <code>$isShowingDestinationView</code> <code>Binding&lt;Bool&gt;</code>.</li></ul><p><code>NavigationLink</code> also provides a completely different, generic initializer:</p><pre><code><span class="keyword">enum</span> ContentViewNavigation: <span class="type">Hashable</span> {
  <span class="keyword">case</span> destinationA
  <span class="keyword">case</span> destinationB
  <span class="keyword">case</span> destinationC
}

...

<span class="type">NavigationLink</span>(
  <span class="string">"Go to view"</span>, 
  destination: <span class="type">DestinationA</span>(), 
  tag: .<span class="dotAccess">destinationA</span>, 
  selection: $showingNavigation
)
</code></pre><p>This initializer requires a <code>tag</code>, which is a value of a generic <code>Hashable</code> type, and a <code>selection</code>, which binds over the same <code>Hashable</code> type as <code>tag</code>.</p><p>How can a view offer such different initializers?<br>In this article, we will try to extend <code>DisclosureGroup</code> with a similar API.</p><p>But first, let’s have a look at the concepts behind <code>NavigationLink</code>.</p><h2>NavigationLink</h2><p><code>NavigationLink</code> is one of the two SwiftUI views used for navigation, the other being <a href="https://developer.apple.com/documentation/swiftui/navigationview"><code>NavigationView</code></a>:<br>among the two, <code>NavigationLink</code>'s role is to trigger and manage a navigation from one screen to another (a.k.a. the destination push/pop).</p><p>Each <code>NavigationLink</code> controls the presentation of a single destination view.</p><p>With this being said, the reasoning behind the first two initializers should be clear:</p><pre><code><span class="type">NavigationLink</span>(
  <span class="string">"Go to view"</span>, 
  destination: <span class="type">DestinationView</span>()
)
</code></pre><p>This first initializer lets SwiftUI own and manage the presentation of the destination.</p><pre><code><span class="type">NavigationLink</span>(
  <span class="string">"Go to view"</span>, 
  destination: <span class="type">DestinationView</span>(), 
  isActive: $isShowingDestinationView
)
</code></pre><p>This second initializer lets us push/pop the destination programmatically as well.</p><p>Any view can only push up to one single view at any given time:<br>it doesn't make sense to push multiple views in the same stack at the same time.</p><p>This is where the last initializer comes in, where:</p><ul><li>instead of having each <code>NavigationLink</code> rely to its own independent state, all <code>NavigationLink</code>s share the same state (the <code>selection</code> binding)</li><li>each <code>NavigationLink</code> triggers on a different <code>selection</code> value (the <code>tag</code>)</li></ul><p>Here's an example of a view with three possible destinations:</p><pre><code><span class="keyword">enum</span> ContentViewNavigation: <span class="type">Hashable</span> {
  <span class="keyword">case</span> a <span class="comment">// destination a</span>
  <span class="keyword">case</span> b <span class="comment">// destination b</span>
  <span class="keyword">case</span> c <span class="comment">// destination a</span>
}

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> showingContent: <span class="type">ContentViewNavigation</span>?

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">NavigationView</span> {
      <span class="type">VStack</span> {
        <span class="type">NavigationLink</span>(<span class="string">"Go to A"</span>, destination: <span class="type">Text</span>(<span class="string">"A"</span>), tag: .<span class="dotAccess">a</span>, selection: $showingContent)
        <span class="type">NavigationLink</span>(<span class="string">"Go to B"</span>, destination: <span class="type">Text</span>(<span class="string">"B"</span>), tag: .<span class="dotAccess">b</span>, selection: $showingContent)
        <span class="type">NavigationLink</span>(<span class="string">"Go to C"</span>, destination: <span class="type">Text</span>(<span class="string">"C"</span>), tag: .<span class="dotAccess">c</span>, selection: $showingContent)
      }
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/hashable-bindings/destination.gif"/><p>Thanks to this third initialize, we no longer can (mistakenly) push multiple views simultaneously.</p><h2>DisclosureGroup</h2><p>Imagine a screen with multiple <code>DisclosureGroup</code>s:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">List</span> {
      <span class="type">DisclosureGroup</span>(<span class="string">"Show content A"</span>) {
        <span class="type">Text</span>(<span class="string">"Content A"</span>)
      }

      <span class="type">DisclosureGroup</span>(<span class="string">"Show content B"</span>) {
        <span class="type">Text</span>(<span class="string">"Content B"</span>)
      }

      <span class="type">DisclosureGroup</span>(<span class="string">"Show content C"</span>) {
        <span class="type">Text</span>(<span class="string">"Content C"</span>)
      }
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/hashable-bindings/freeContent.gif"/><p>We now receive a new requirement, where only up to one <code>DisclosureGroup</code> can display its content at any given time (therefore mimicking <code>NavigationLink</code>'s limitation).</p><p>Using just the official APIs, we'd need:</p><ul><li>a separate <code>Bool</code> state for each view group...</li><li>...that then needs to be observed and acted upon when said state becomes <code>true</code> (hiding the content of other previously open <code>DisclosureGroup</code>s)</li></ul><p>One way to achieve this would be:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> isContentAShown: <span class="type">Bool</span> = <span class="keyword">false
  @State var</span> isContentBShown: <span class="type">Bool</span> = <span class="keyword">false
  @State var</span> isContentCShown: <span class="type">Bool</span> = <span class="keyword">false

  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">List</span> {
      <span class="type">DisclosureGroup</span>(<span class="string">"Show content A"</span>, isExpanded: $isContentAShown) {
        <span class="type">Text</span>(<span class="string">"Content A"</span>)
      }

      <span class="type">DisclosureGroup</span>(<span class="string">"Show content B"</span>, isExpanded: $isContentBShown) {
        <span class="type">Text</span>(<span class="string">"Content B"</span>)
      }

      <span class="type">DisclosureGroup</span>(<span class="string">"Show content C"</span>, isExpanded: $isContentCShown) {
        <span class="type">Text</span>(<span class="string">"Content C"</span>)
      }
    }
    .<span class="call">onChange</span>(of: isContentAShown) { newValue <span class="keyword">in
      if</span> newValue {
        isContentBShown = <span class="keyword">false</span>
        isContentCShown = <span class="keyword">false</span>
      }
    }
    .<span class="call">onChange</span>(of: isContentBShown) { newValue <span class="keyword">in
      if</span> newValue {
        isContentAShown = <span class="keyword">false</span>
        isContentCShown = <span class="keyword">false</span>
      }
    }
    .<span class="call">onChange</span>(of: isContentCShown) { newValue <span class="keyword">in
      if</span> newValue {
        isContentAShown = <span class="keyword">false</span>
        isContentBShown = <span class="keyword">false</span>
      }
    }
  }
}
</code></pre><p>While this works, it's error-prone and costly to maintain:<br>the more groups the view has, the more <code>onChange</code> view modifiers need to be added, the more <code>State&lt;Bool&gt;</code> properties need to be declared, etc.</p><p>Besides, each group <code>Bool</code> state is still independent of the rest: nobody can stop a rogue method to set all the <code>DisclosureGroup</code> states to <code>true</code> at once, resulting in undefined behavior (because of the <code>onChange</code> observers).</p><h3>Hashable Binding</h3><p>Similarly to the last <code>NavigationLink</code> example, it would be ideal if we could fix our current <code>DisclosureGroup</code> solution shortcomings by:</p><ul><li>sharing a single state among all our <code>DisclosureGroup</code>s</li><li>making it impossible to have multiple groups showing the content at the same time</li></ul><p>First, let's define a new <code>Hashable</code> enum, with each case representing a separate section of our view:</p><pre><code><span class="keyword">enum</span> ContentViewGroup: <span class="type">Hashable</span> {
  <span class="keyword">case</span> a
  <span class="keyword">case</span> b
  <span class="keyword">case</span> c
}
</code></pre><p>We want to use this enum as our shared state, where each <code>DisclosureGroup</code> listens to a separate case: when tapping on the <code>DisclosureGroup</code> "A" for example, the state will be set to <code>.a</code>, allowing the first group to show its content, while other groups keep the content hidden.</p><p>Using the same <code>NavigationLink</code> approach, this is where we want to end up with:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> showingContent: <span class="type">ContentViewGroup</span>?

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">List</span> {
      <span class="type">DisclosureGroup</span>(
        <span class="string">"Tap to show content A"</span>,
        tag: .<span class="dotAccess">a</span>,
        selection: $showingContent) {
        <span class="type">Text</span>(<span class="string">"Content A"</span>)
      }

      <span class="type">DisclosureGroup</span>(
        <span class="string">"Tap to show content B"</span>,
        tag: .<span class="dotAccess">b</span>,
        selection: $showingContent) {
        <span class="type">Text</span>(<span class="string">"Content B"</span>)
      }

      <span class="type">DisclosureGroup</span>(
        <span class="string">"Tap to show content C"</span>,
        tag: .<span class="dotAccess">c</span>,
        selection: $showingContent) {
        <span class="type">Text</span>(<span class="string">"Content C"</span>)
      }
    }
  }
}
</code></pre><p>Unfortunately, <code>DisclosureGroup</code> doesn't offer such API.</p><p>But if it was offered, how would this initializer be exposed?<br>Looking at <code>NavigationLink</code>s headers, we would have something like:</p><pre><code><span class="keyword">extension</span> <span class="type">DisclosureGroup</span> <span class="keyword">where</span> <span class="type">Label</span> == <span class="type">Text</span> {
  <span class="keyword">public init</span>&lt;V: <span class="type">Hashable</span>, S: <span class="type">StringProtocol</span>&gt;(
    <span class="keyword">_</span> label: <span class="type">S</span>,
    tag: <span class="type">V</span>,
    selection: <span class="type">Binding</span>&lt;<span class="type">V</span>?&gt;,
    content: <span class="keyword">@escaping</span> () -&gt; <span class="type">Content</span>) {
    ...
  }
}
</code></pre><p>Before trying to fill in this initializer, let's take a step back and look at both <code>NavigationLink</code> and <code>DisclosureGroup</code>:<br>at any given time, a <code>NavigationLink</code> is either pushing the destination or not, <strong>regardless of what initializer we use</strong>. Similarly, a <code>DisclosureGroup</code> is either showing its content or not.</p><p>These views <strong>always have a boolean state</strong> (pushing/not-pushing, showing/not-showing), even when we pass the <code>Hashable</code> <code>tag</code> + <code>selection</code> binding combo.</p><p>The <code>Hashable</code> initializer is a convenience:<br>behind the scenes, these views still behave as if a boolean binding has been passed.</p><p>Let's take a look at the first <code>DisclosureGroup</code> definition in our example:</p><pre><code><span class="type">DisclosureGroup</span>(
  <span class="string">"Tap to show content A"</span>,
  tag: .<span class="dotAccess">a</span>,
  selection: $showingContent,
  content: { <span class="type">Text</span>(<span class="string">"Content A"</span>) }
)
</code></pre><p>If we had to turn the <code>showingContent</code> <code>Hashable</code> binding into a <code>Bool</code> one, this is more or less how we'd do it:</p><ul><li>the value would be <code>true</code> if <code>showingContent.wrappedValue == .a</code>, <code>false</code> otherwise</li><li>when setting the boolean binding value to <code>true</code>, we'd reflect this change by setting <code>showingContent.wrappedValue = .a</code></li><li>when setting the boolean binding value to <code>false</code>, we'd reflect this change by setting <code>showingContent.wrappedValue = nil</code></li></ul><p>The <code>Hashable</code> initializer has all it is needed to turn the <code>Hashable</code> binding into a <code>Bool</code> one:<br>despite not getting a <code>Bool</code> binding, nobody is stopping us from creating a new one. This is what <code>NavigationLink</code> does, and what we can do in <code>DisclosureGroup</code> as well:</p><pre><code><span class="keyword">extension</span> <span class="type">DisclosureGroup</span> <span class="keyword">where</span> <span class="type">Label</span> == <span class="type">Text</span> {
  <span class="keyword">public init</span>&lt;V: <span class="type">Hashable</span>, S: <span class="type">StringProtocol</span>&gt;(
    <span class="keyword">_</span> label: <span class="type">S</span>,
    tag: <span class="type">V</span>,
    selection: <span class="type">Binding</span>&lt;<span class="type">V</span>?&gt;,
    content: <span class="keyword">@escaping</span> () -&gt; <span class="type">Content</span>) {

    <span class="keyword">let</span> boolBinding: <span class="type">Binding</span>&lt;<span class="type">Bool</span>&gt; = <span class="type">Binding</span>(
      get: { selection.<span class="property">wrappedValue</span> == tag },
      set: { newValue <span class="keyword">in
        if</span> newValue {
          selection.<span class="property">wrappedValue</span> = tag
        } <span class="keyword">else</span> {
          selection.<span class="property">wrappedValue</span> = <span class="keyword">nil</span>
        }
      }
    )

    <span class="comment">// Here we call the "normal" initializer with a Binding&lt;Bool&gt;.</span>
    <span class="keyword">self</span>.<span class="keyword">init</span>(
      label,
      isExpanded: boolBinding,
      content: content
    )
  }
}
</code></pre><p>And with this extension, we've now accomplished our target:</p><img src="https://www.fivestars.blog/assets/posts/hashable-bindings/oneShowing.gif"/><p>Thanks to this generic approach, our code is much more maintainable, reusable, and less error-prone:<br>the final gist can be found <a href="https://github.com/zntfdr/FiveStarsCodeSamples/tree/main/Hashable-Bindings">here</a>.</p><h2>Conclusions</h2><p>Despite offering multiple initializers, each SwiftUI view will run the same core logic at the end of the day.</p><p>The <code>Hashable</code> <em>convenience</em> initializer is yet another example of how SwiftUI excels at progressive discovery:<br>taking any SwiftUI view, we can start by adopting their simple initializers first, where most of the details are hidden, and then, once we are acquaint with them, we can move and use, or even create, more advanced ones, where we have more control (and more responsibility!) of each view.</p><p>Have you seen any other SwiftUI examples of such APIs? Please <a href="https://twitter.com/zntfdr">let me know</a>!</p><p>Thank you for reading, and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/adaptive-swiftui-views</guid><title>How to make SwiftUI views adaptive</title><description></description><link>https://www.fivestars.blog/articles/adaptive-swiftui-views</link><pubDate>Tue, 20 Oct 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>One of the most powerful aspects of SwiftUI is how it adapts based on the context:<br>this is SwiftUI's promise, running on all Apple devices, from the 38mm Apple Watch to the 27-inches iMac (without considering external monitors!).</p><p>While this is great and can save us hundreds of hours, sometimes we want to make our UI declarations even more adaptive: let's see how.</p><h2>Our example</h2><p>Our app has a view that we would like to adapt based on the available space.</p><p>We define two layouts, one where the content is stacked vertically and one where the content is stacked horizontally:</p><img src="https://www.fivestars.blog/assets/posts/adaptive-views/layoutsImage.png"/><p>Even before taking care of how to pick which layout, let's define a generic reusable view, <code>AdaptiveView</code>:</p><pre><code><span class="keyword">struct</span> AdaptiveView&lt;Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">var</span> content: <span class="type">Content</span>

  <span class="keyword">public init</span>(<span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>) {
    <span class="keyword">self</span>.<span class="property">content</span> = <span class="call">content</span>()
  }

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">if</span> <span class="comment">/* condition here */</span> {
      <span class="type">HStack</span> {
        content
      }
    } <span class="keyword">else</span> {
      <span class="type">VStack</span> {
        content
      }
    }
  }
}
</code></pre><blockquote><p>We will fill in the condition later on.</p></blockquote><p>Our example view will be able to use this new definition, where all it needs to declare is the content, <code>AdaptiveView</code> manages everything else:</p><pre><code><span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
  <span class="type">AdaptiveView</span> {
    <span class="type">RoundedRectangle</span>(...)
      .<span class="call">fill</span>(...)
      .<span class="call">frame</span>(maxHeight: <span class="number">400</span>)

    <span class="type">VStack</span> {
      <span class="type">Text</span>(<span class="string">"Title"</span>)
        .<span class="call">bold</span>()
        .<span class="call">font</span>(.<span class="dotAccess">title</span>)

      <span class="type">Text</span>(...)
        .<span class="call">fixedSize</span>(horizontal: <span class="keyword">false</span>, vertical: <span class="keyword">true</span>)
    }
  }
}
</code></pre><p>Awesome, let's now see how we can fill the <code>AdaptiveView</code> condition next.</p><blockquote><p>For simplicity's sake, we will focus on conditions based on the horizontal space available: the same concepts can also be applied for vertical space.</p></blockquote><h2>Size classes</h2><img src="https://www.fivestars.blog/assets/posts/adaptive-views/layoutsImage.png"/><blockquote><p>All the <em>plus</em> sizes iPhones have a regular horizontal size class when in landscape.</p></blockquote><p>Every SwiftUI view can observe the screen size classes via two environment values: <a href="https://developer.apple.com/documentation/swiftui/environmentvalues/horizontalsizeclass"><code>horizontalSizeClass</code></a> and <a href="https://developer.apple.com/documentation/swiftui/environmentvalues/verticalsizeclass"><code>verticalSizeClass</code></a>.</p><p>Both return a <a href="https://developer.apple.com/documentation/swiftui/userinterfacesizeclass"><code>UserInterfaceSizeClass</code></a> instance, which is SwiftUI's counterpart to UIKit's <a href="https://developer.apple.com/documentation/uikit/uiuserinterfacesizeclass"><code>UIUserInterfaceSizeClass</code></a>:</p><pre><code><span class="keyword">public enum</span> UserInterfaceSizeClass {
  <span class="keyword">case</span> compact
  <span class="keyword">case</span> regular
}
</code></pre><p>In this example we can make <code>AdaptiveView</code> switch layout based the environment's <code>horizontalSizeClass</code>:</p><pre><code><span class="keyword">struct</span> AdaptiveView&lt;Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">horizontalSizeClass</span>) <span class="keyword">var</span> horizontalSizeClass
  <span class="keyword">var</span> content: <span class="type">Content</span>

  <span class="keyword">init</span>(...) { ... }

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">if</span> horizontalSizeClass == .<span class="dotAccess">regular</span> {
      <span class="comment">// We have a "regular" horizontal screen estate: 
      // we lay the content horizontally.</span>
      <span class="type">HStack</span> {
        content
      }
    } <span class="keyword">else</span> {
      <span class="type">VStack</span> {
        content
      }
    }
  }
}
</code></pre><h2>Dynamic type</h2><img src="https://www.fivestars.blog/assets/posts/adaptive-views/dynamicTypeImage.png"/><p>Another approach <code>AdaptiveView</code> could use is based on the environment <a href="https://developer.apple.com/documentation/swiftui/contentsizecategory"><code>ContentSizeCategory</code></a>:<br>while <a href="https://developer.apple.com/documentation/swiftui/userinterfacesizeclass"><code>UserInterfaceSizeClass</code></a> tells us about the compact/regular size of the device screen in the current orientation, <a href="https://developer.apple.com/documentation/swiftui/contentsizecategory"><code>ContentSizeCategory</code></a> tells us the user's preferred content size (a.k.a. dynamic type).</p><pre><code><span class="keyword">public enum</span> ContentSizeCategory: <span class="type">Hashable</span>, <span class="type">CaseIterable</span> {
  <span class="keyword">case</span> extraSmall
  <span class="keyword">case</span> small
  <span class="keyword">case</span> medium
  <span class="keyword">case</span> large
  <span class="keyword">case</span> extraLarge
  <span class="keyword">case</span> extraExtraLarge
  <span class="keyword">case</span> extraExtraExtraLarge
  <span class="keyword">case</span> accessibilityMedium
  <span class="keyword">case</span> accessibilityLarge
  <span class="keyword">case</span> accessibilityExtraLarge
  <span class="keyword">case</span> accessibilityExtraExtraLarge
  <span class="keyword">case</span> accessibilityExtraExtraExtraLarge
}
</code></pre><p>We can use any of these cases as the condition threshold in <code>AdaptiveView</code>, for example we could switch layout for any size bigger than <code>.large</code>:</p><pre><code><span class="keyword">struct</span> AdaptiveView&lt;Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">sizeCategory</span>) <span class="keyword">var</span> sizeCategory: <span class="type">ContentSizeCategory</span>
  <span class="keyword">var</span> content: <span class="type">Content</span>

  <span class="keyword">init</span>(...) { ... }

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">if</span> sizeCategory &gt; .<span class="dotAccess">large</span> {
      <span class="type">VStack</span> {
        content
      }
    } <span class="keyword">else</span> {
      <span class="type">HStack</span> {
        content
      }
    }
  }
}
</code></pre><p>SwiftUI also offers an <a href="https://developer.apple.com/documentation/swiftui/contentsizecategory/isaccessibilitycategory"><code>isAccessibilityCategory</code></a> property on <code>ContentSizeCategory</code> which we can use as well:</p><pre><code><span class="keyword">struct</span> AdaptiveView&lt;Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">sizeCategory</span>) <span class="keyword">var</span> sizeCategory: <span class="type">ContentSizeCategory</span>
  <span class="keyword">var</span> content: <span class="type">Content</span>

  <span class="keyword">init</span>(...) { ... }

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">if</span> sizeCategory.<span class="property">isAccessibilityCategory</span> {
      <span class="comment">// When the user prefers an accessibility category, lay the content vertically.</span>
      <span class="type">VStack</span> {
        content
      }
    } <span class="keyword">else</span> {
      <span class="type">HStack</span> {
        content
      }
    }
  }
}
</code></pre><p><a href="https://developer.apple.com/documentation/swiftui/contentsizecategory/isaccessibilitycategory"><code>isAccessibilityCategory</code></a> returns <code>true</code> when the <code>ContentSizeCategory</code> instance is one that starts with "<code>accessibility</code>", this seems a good default threshold to use:<br>of course, we should test our implementation and see/decide if that works for us. If it doesn't, we can always fall back to another threshold.</p><h2>Custom threshold</h2><p>The approaches presented so far will work for most views. However, they also have a significant shortcoming: they rely on a global value.</p><p>This is great when a single <code>AdaptiveView</code> is the main content of the screen, but what if we have multiple views that should adapt?</p><p>If we fall into this case, we probably can't rely on these global environment properties: instead, we should make decisions for each view separately.</p><p>This way, two or more of these views can layout themselves differently, each based on their own space and <em>threshold</em>.</p><p>To do this, we need to take two steps:</p><ol><li>Obtain the available horizontal space for each <code>AdaptiveView</code></li><li>Create a condition based on that space</li></ol><h3>1. Obtain the available horizontal space</h3><p>Luckily for us, we've already faced this challenge in <a href="https://www.fivestars.blog/articles/flexible-swiftui/"><code>Flexible layouts in SwiftUI</code></a> (recommended read!), where we achieved the following result:</p><pre><code><span class="keyword">struct</span> FlexibleView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> availableWidth: <span class="type">CGFloat</span> = <span class="number">0</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ZStack</span> {
      <span class="type">Color</span>.<span class="property">clear</span>
        .<span class="call">frame</span>(height: <span class="number">1</span>)
        .<span class="call">readSize</span> { size <span class="keyword">in</span>
          availableWidth = size.<span class="property">width</span>
        }

      <span class="comment">// Rest of our implementation</span>
    }
  }
}
</code></pre><p>We can take this and implement it in our generic <code>AdaptiveView</code>:</p><pre><code><span class="keyword">struct</span> AdaptiveView&lt;Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">@State private var</span> availableWidth: <span class="type">CGFloat</span> = <span class="number">0</span>
  <span class="keyword">var</span> content: <span class="type">Content</span>

  <span class="keyword">public init</span>(...) { ... }

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ZStack</span> {
      <span class="type">Color</span>.<span class="property">clear</span>
        .<span class="call">frame</span>(height: <span class="number">1</span>)
        .<span class="call">readSize</span> { size <span class="keyword">in</span>
          availableWidth = size.<span class="property">width</span>
        }

      <span class="keyword">if</span> <span class="comment">/* condition */</span> {
        <span class="type">HStack</span> {
          content
        }
      } <span class="keyword">else</span> {
        <span class="type">VStack</span> {
          content
        }
      }
    }
  }
}
</code></pre><p>...completing our first point.</p><h3>2. Create a condition based on the available space</h3><p>Once we have the available space, all it's left to decide is how to use it:<br>as we're building a generic view, it's best to leave the threshold decision to the implementer, who knows where the view is used and what the actual content is.</p><p>For these reasons, we can add a new property, <code>threshold</code>, which will then be used in our <code>AdaptiveView</code> condition:</p><pre><code><span class="keyword">struct</span> AdaptiveView&lt;Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">@State private var</span> availableWidth: <span class="type">CGFloat</span> = <span class="number">0</span>
  <span class="keyword">var</span> threshold: <span class="type">CGFloat</span>
  <span class="keyword">var</span> content: <span class="type">Content</span>

  <span class="keyword">public init</span>(
    threshold: <span class="type">CGFloat</span>, 
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>
  ) {
    <span class="keyword">self</span>.<span class="property">threshold</span> = threshold
    <span class="keyword">self</span>.<span class="property">content</span> = <span class="call">content</span>()
  }

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ZStack</span> {
      <span class="type">Color</span>.<span class="property">clear</span>
        .<span class="call">frame</span>(height: <span class="number">1</span>)
        .<span class="call">readSize</span> { size <span class="keyword">in</span>
          availableWidth = size.<span class="property">width</span>
        }

      <span class="keyword">if</span> availableWidth &gt; threshold {
        <span class="type">HStack</span> {
          content
        }
      } <span class="keyword">else</span> {
        <span class="type">VStack</span> {
          content
        }
      }
    }
  }
}
</code></pre><p>And with this, our custom <code>AdaptiveView</code> is complete.</p><h3>Experimentation</h3><p>Since we now own the threshold, it's also easy to test different thresholds/layouts/devices, for example:</p><img src="https://www.fivestars.blog/assets/posts/adaptive-views/exp.gif"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> currentWidth: <span class="type">CGFloat</span> = <span class="number">0</span>
  <span class="keyword">@State var</span> padding: <span class="type">CGFloat</span> = <span class="number">8</span>
  <span class="keyword">@State var</span> threshold: <span class="type">CGFloat</span> = <span class="number">100</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">AdaptiveView</span>(threshold: threshold) {
        <span class="type">RoundedRectangle</span>(cornerRadius: <span class="number">40.0</span>, style: .<span class="dotAccess">continuous</span>)
          .<span class="call">fill</span>(
            <span class="type">Color</span>(red: <span class="number">224</span> / <span class="number">255.0</span>, green: <span class="number">21</span> / <span class="number">255.0</span>, blue: <span class="number">90</span> / <span class="number">255.0</span>, opacity: <span class="number">1</span>)
          )
        <span class="type">RoundedRectangle</span>(cornerRadius: <span class="number">40.0</span>, style: .<span class="dotAccess">continuous</span>)
          .<span class="call">fill</span>(
            <span class="type">Color</span>.<span class="property">pink</span>
          )
      }
      .<span class="call">readSize</span> { size <span class="keyword">in</span>
        currentWidth = size.<span class="property">width</span>
      }
      .<span class="call">overlay</span>(
        <span class="type">Rectangle</span>()
          .<span class="call">stroke</span>(lineWidth: <span class="number">2</span>)
          .<span class="call">frame</span>(width: threshold)
      )
      .<span class="call">padding</span>(.<span class="dotAccess">horizontal</span>, padding)

      <span class="type">Text</span>(<span class="string">"Current width:</span> \(<span class="type">Int</span>(currentWidth))<span class="string">"</span>)
      <span class="type">HStack</span> {
        <span class="type">Text</span>(<span class="string">"Threshold:</span> \(<span class="type">Int</span>(threshold))<span class="string">"</span>)
        <span class="type">Slider</span>(value: $threshold, in: <span class="number">0</span>...<span class="number">500</span>, step: <span class="number">1</span>) { <span class="type">Text</span>(<span class="string">""</span>) }
      }
      <span class="type">HStack</span> {
        <span class="type">Text</span>(<span class="string">"Padding:"</span>)
        <span class="type">Slider</span>(value: $padding, in: <span class="number">0</span>...<span class="number">500</span>, step: <span class="number">1</span>) { <span class="type">Text</span>(<span class="string">""</span>) }
      }
    }
    .<span class="call">padding</span>()
  }
}
</code></pre><p>The final project can be found <a href="https://github.com/zntfdr/FiveStarsCodeSamples/tree/main/Adaptive-Views">here</a>.</p><h2>Not only layouts</h2><p>The examples we've seen so far adapt the layout direction based on our condition. However, this is not the only use case. For instance, we can use a similar approach to show/hide part of the UI:</p><img src="https://www.fivestars.blog/assets/posts/adaptive-views/socialImage.png"/><p>This example is also part of the <a href="https://github.com/zntfdr/FiveStarsCodeSamples/tree/main/Adaptive-Views">final project</a>.</p><h2>Conclusions</h2><p>SwiftUI tries its best to fit as best as possible in every given scenario: it's wonderful to let the framework do all the heavy lifting, however going that extra mile, with a little work from our side, can help us provide an even better user experience.</p><p>In this article, we've seen various approaches to adapting our views based on different conditions: do you use any of them? Have you seen any other alternatives? <a href="https://twitter.com/zntfdr">Please let me know!</a></p><p>Thank you for reading, and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/design-system-composing-views</guid><title>How to compose SwiftUI views with @ViewBuilder</title><description>An exploration of an important pattern used throughout SwiftUI</description><link>https://www.fivestars.blog/articles/design-system-composing-views</link><pubDate>Wed, 14 Oct 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>We're tasked with building a brand new iOS 13+ app, or, maybe, with migrating an app from UIKit to SwiftUI: where should we start?</p><p>Thanks to SwiftUI's composable nature, we might want to start by defining and using a <a href="https://www.invisionapp.com/inside-design/guide-to-design-systems/">design system</a>:<br>once we have one, building views becomes a matter of picking the correct elements from the system and place them on screen.</p><p>Let's see how we can build a component of our design system: a text field.</p><h2>The start</h2><p>After studying the patterns from the design team, we conclude that there are two different appearances for our text fields in the app:<br>a <em>default</em> one, used when everything is ok, and an "error" one, used to tell the user that something is wrong.</p><p>Besides the appearance, all text fields have the same components: a title, a placeholder, and a border.</p><table><tbody><tr><td><img src="https://www.fivestars.blog/assets/posts/design-system/image0.png"/></td><td><img src="https://www.fivestars.blog/assets/posts/design-system/image1.png"/></td></tr></tbody></table><blockquote><p>The two text field appearances: default and error.</p></blockquote><p>With this knowledge we go ahead and build our own <code>FSTextField</code>:</p><pre><code><span class="keyword">struct</span> FSTextField: <span class="type">View</span> {
  <span class="keyword">var</span> title: <span class="type">LocalizedStringKey</span>
  <span class="keyword">var</span> placeholder: <span class="type">LocalizedStringKey</span> = <span class="string">""</span>
  <span class="keyword">@Binding var</span> text: <span class="type">String</span>
  <span class="keyword">var</span> appearance: <span class="type">Appearance</span> = .<span class="dotAccess">default</span>

  <span class="keyword">enum</span> Appearance {
    <span class="keyword">case</span> `default`
    case error
  }

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">HStack</span> {
        <span class="type">Text</span>(title)
          .<span class="call">bold</span>()
        <span class="type">Spacer</span>()
      }

      <span class="type">TextField</span>(
        placeholder,
        text: $text
      )
      .<span class="call">padding</span>(.<span class="dotAccess">horizontal</span>, <span class="number">8</span>)
      .<span class="call">padding</span>(.<span class="dotAccess">vertical</span>, <span class="number">4</span>)
      .<span class="call">background</span>(
        <span class="type">RoundedRectangle</span>(cornerRadius: <span class="number">8</span>, style: .<span class="dotAccess">continuous</span>)
          .<span class="call">strokeBorder</span>(borderColor)
      )
    }
  }

  <span class="keyword">var</span> borderColor: <span class="type">Color</span> {
    <span class="keyword">switch</span> appearance {
    <span class="keyword">case</span> .<span class="dotAccess">default</span>:
      <span class="keyword">return</span> .<span class="dotAccess">green</span>
    <span class="keyword">case</span> .<span class="dotAccess">error</span>:
      <span class="keyword">return</span> .<span class="dotAccess">red</span>
    }
  }
}
</code></pre><p><code>FSTextField</code> is defined as a <code>VStack</code> with a title (a <code>Text</code>) on top and a SwiftUI <code>TextField</code> at the bottom: this declaration is clear and covers 100% of the known cases.</p><p>We're happy with <code>FSTextField</code>: we add a couple of previews and move to the next design system component.</p><h2>One week later</h2><p>One week pass and we discover (or we're given) two new variations of our text field: the first displays a glyph on the top trailing corner, vertically aligned with the title, and the other shows a message in the same spot:</p><table><tbody><tr><td><img src="https://www.fivestars.blog/assets/posts/design-system/image2.png"/></td><td><img src="https://www.fivestars.blog/assets/posts/design-system/image3.png"/></td></tr></tbody></table><p>Fair enough, we define two new views, <code>FSGlyphTextField</code> and <code>FSMessageTextField</code>, which cover these new cases:</p><pre><code><span class="keyword">struct</span> FSGlyphTextField: <span class="type">View</span> {
  <span class="keyword">var</span> title: <span class="type">LocalizedStringKey</span>
  <span class="keyword">var</span> symbolName: <span class="type">String</span>
  <span class="keyword">var</span> systemColor: <span class="type">Color</span> = <span class="type">Color</span>(.<span class="dotAccess">label</span>)
  <span class="keyword">var</span> placeholder: <span class="type">LocalizedStringKey</span> = <span class="string">""</span>
  <span class="keyword">@Binding var</span> text: <span class="type">String</span>
  <span class="keyword">var</span> appearance: <span class="type">FSTextField</span>.<span class="type">Appearance</span> = .<span class="dotAccess">default</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">HStack</span> {
        <span class="type">Text</span>(title)
          .<span class="call">bold</span>()
        <span class="type">Spacer</span>()
        <span class="type">Image</span>(systemName: symbolName)
          .<span class="call">foregroundColor</span>(systemColor)
      }

      <span class="type">TextField</span>(
        ...
      )
    }
  }

  <span class="keyword">var</span> borderColor: <span class="type">Color</span> {
    ...
  }
}

<span class="keyword">struct</span> FSMessageTextField: <span class="type">View</span> {
  <span class="keyword">var</span> title: <span class="type">LocalizedStringKey</span>
  <span class="keyword">var</span> message: <span class="type">LocalizedStringKey</span>
  <span class="keyword">@Binding var</span> text: <span class="type">String</span>
  <span class="keyword">var</span> appearance: <span class="type">FSTextField</span>.<span class="type">Appearance</span> = .<span class="dotAccess">default</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">HStack</span> {
        <span class="type">Text</span>(title)
          .<span class="call">bold</span>()
        <span class="type">Spacer</span>()
        <span class="type">Text</span>(message)
          .<span class="call">font</span>(.<span class="dotAccess">caption</span>)
      }

      <span class="type">TextField</span>(
        ...
      )
    }
  }

  <span class="keyword">var</span> borderColor: <span class="type">Color</span> {
    ...
  }
}
</code></pre><p>Our design system has now three text field definitions instead of one:<br>we could do better, but we can manage.</p><h2>One more week later</h2><p>Another week pass by, and the design team adds two more variations to our text field: the first has no title, while the other has the usual title and a button on the trailing corner:</p><table><tbody><tr><td><img src="https://www.fivestars.blog/assets/posts/design-system/image4.png"/></td><td><img src="https://www.fivestars.blog/assets/posts/design-system/image5.png"/></td></tr></tbody></table><p>We could define two more text field views (e.g., <code>FSPlainTextField</code> and <code>FSButtonTextField</code>); however, creating new views for each variation defeats the purpose of our design system:<br>what happens when the design changes and we must update the title font, or perhaps the border color?</p><p>The more (text field) definitions we have, the harder it becomes to manage each component, and the easier it is to <em>forget</em> to update one (or more) variation(s).</p><h2>Generic text fields: core components</h2><p>With the current approach, we already take advantage of SwiftUI's composability. We use all these variations when building screens; however, we can go a step further and use composability within our text fields definitions.</p><p>First, looking at the current variations, we see that there's one constant: the text field itself. Let's extract it from the definitions above:</p><pre><code><span class="keyword">struct</span> _FSTextField: <span class="type">View</span> {
  <span class="keyword">var</span> placeholder: <span class="type">LocalizedStringKey</span> = <span class="string">""</span>
  <span class="keyword">@Binding var</span> text: <span class="type">String</span>
  <span class="keyword">var</span> borderColor: <span class="type">Color</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">TextField</span>(
      placeholder,
      text: $text
    )
    .<span class="call">padding</span>(.<span class="dotAccess">horizontal</span>, <span class="number">8</span>)
    .<span class="call">padding</span>(.<span class="dotAccess">vertical</span>, <span class="number">4</span>)
    .<span class="call">background</span>(
      <span class="type">RoundedRectangle</span>(cornerRadius: <span class="number">8</span>, style: .<span class="dotAccess">continuous</span>)
        .<span class="call">strokeBorder</span>(borderColor)
    )
  }
}
</code></pre><table><tbody><tr><td><img src="https://www.fivestars.blog/assets/posts/design-system/image7.png"/></td><td><img src="https://www.fivestars.blog/assets/posts/design-system/image6.png"/></td></tr></tbody></table><blockquote><p>Our two <code>_FSTextField</code> variations.</p></blockquote><p><code>_FSTextField</code> is a wrapper around SwiftUI's <code>TextField</code> with our app design applied to it:<br>we define this view with an underscore "<code>_</code>" prefix (<code>_FSTextField</code>) to make it clear that this view shouldn't be used directly but, instead, it's an implementation detail of other views.</p><p>If we replace our previous <code>TextField</code> definitions with <code>_FSTextField</code>, this already helps:<br>in the future, when we want to update the text field corner radius (for example), we will only need to change it within <code>_FSTextField</code>, and all other views will automatically inherit the change.</p><blockquote><p>It should be possible to 3rd party developers to define <code>TextField</code> styles, FB9078993.</p></blockquote><h2>Generic text fields: composable views</h2><p>Looking at our text fields variations, we can group them into two categories:</p><ul><li>views that have something on top of <code>_FSTextField</code> (e.g., the title and a glyph)</li><li>views that have just <code>_FSTextField</code> and nothing else</li></ul><p>Let's define a new generic view that covers both variations, <code>FSTextField</code>:</p><pre><code><span class="keyword">struct</span> FSTextField&lt;TopContent: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">var</span> placeholder: <span class="type">LocalizedStringKey</span> = <span class="string">""</span>
  <span class="keyword">@Binding var</span> text: <span class="type">String</span>
  <span class="keyword">var</span> appearance: <span class="type">Appearance</span> = .<span class="dotAccess">default</span>
  <span class="keyword">var</span> topContent: <span class="type">TopContent</span>

  <span class="keyword">init</span>(
    placeholder: <span class="type">LocalizedStringKey</span> = <span class="string">""</span>,
    text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;,
    appearance: <span class="type">Appearance</span> = .<span class="dotAccess">default</span>,
    <span class="keyword">@ViewBuilder</span> topContent: () -&gt; <span class="type">TopContent</span>
  ) {
    <span class="keyword">self</span>.<span class="property">placeholder</span> = placeholder
    <span class="keyword">self</span>.<span class="property">_text</span> = text
    <span class="keyword">self</span>.<span class="property">appearance</span> = appearance
    <span class="keyword">self</span>.<span class="property">topContent</span> = <span class="call">topContent</span>()
  }

  <span class="keyword">enum</span> Appearance {
    <span class="keyword">case</span> `default`
    case error
  }
  
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      topContent
      <span class="type">_FSTextField</span>(
        placeholder: placeholder,
        text: $text,
        borderColor: borderColor
      )
    }
  }

  <span class="keyword">var</span> borderColor: <span class="type">Color</span> {
    <span class="keyword">switch</span> appearance {
    <span class="keyword">case</span> .<span class="dotAccess">default</span>:
      <span class="keyword">return</span> .<span class="dotAccess">green</span>
    <span class="keyword">case</span> .<span class="dotAccess">error</span>:
      <span class="keyword">return</span> .<span class="dotAccess">red</span>
    }
  }
}
</code></pre><p><code>FSTextField</code> is a <code>VStack</code> with a generic <code>TopContent</code> view on top, and our <code>_FSTextField</code> at the bottom.</p><p>Thanks to this new definition, we can place any view above our <code>_FSTextField</code>. What about a <code>Label</code>, for example?</p><pre><code><span class="type">FSTextField</span>(placeholder: <span class="string">"Placeholder"</span>, text: $text) {
  <span class="type">Label</span>(<span class="string">"Label Title"</span>, systemImage: <span class="string">"star.fill"</span>)
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/design-system/imageLabel.png"/><p>Lastly, we need to take care of the view variation with no content above <code>_FSTextField</code>. How do we address that?</p><p>As <code>VStack</code>s ignore <code>EmptyView</code>s, if we want to display just the <code>_FSTextField</code> and nothing else, we can pass an <a href="https://developer.apple.com/documentation/swiftui/emptyview"><code>EmptyView</code></a> instance as our <code>TopContent</code>:</p><pre><code><span class="type">FSTextField</span>(
  placeholder: <span class="string">"Placeholder"</span>,
  text: $myText
) {
  <span class="type">EmptyView</span>()
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/design-system/image4.png"/><p>This works because:</p><ul><li>A <code>VStack</code> with an <code>EmptyView</code> and <code>_FSTextField</code> is (visually) equivalent to a <code>VStack</code> with just a <code>_FSTextField</code></li><li>Any stack with just an element is (visually) equivalent to just the element itself</li></ul><p>Therefore writing:</p><pre><code><span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
  <span class="type">VStack</span> {
    <span class="type">EmptyView</span>()
    <span class="type">_FSTextField</span>(...)
  }
}
</code></pre><p>is equivalent to:</p><pre><code><span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
  <span class="type">_FSTextField</span>(...)
}
</code></pre><p>We're building this design system and we know these tricks/details, however, we cannot expect other developers to have this knowledge as well. To make their life easier, we can create a <code>FSTextField</code> extension that hides this <code>VStack</code> + <code>EmptyView</code> combo:</p><pre><code><span class="keyword">extension</span> <span class="type">FSTextField</span> <span class="keyword">where</span> <span class="type">TopContent</span> == <span class="type">EmptyView</span> {
  <span class="keyword">init</span>(
    placeholder: <span class="type">LocalizedStringKey</span> = <span class="string">""</span>,
    text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;,
    appearance: <span class="type">Appearance</span> = .<span class="dotAccess">default</span>
  ) {
    <span class="keyword">self</span>.<span class="property">placeholder</span> = placeholder
    <span class="keyword">self</span>.<span class="property">_text</span> = text
    <span class="keyword">self</span>.<span class="property">appearance</span> = appearance
    <span class="keyword">self</span>.<span class="property">topContent</span> = <span class="type">EmptyView</span>()
  }
}
</code></pre><p>Thanks to this extension, developers that want to display just the text field can now use this new initializer, without the need for them to know how <code>FSTextField</code> is implemented:</p><pre><code><span class="type">FSTextField</span>(placeholder: <span class="string">"Placeholder"</span>, text: $myText)
</code></pre><img src="https://www.fivestars.blog/assets/posts/design-system/image4.png"/><h2>Generic Text Fields: Convenience initializers</h2><p>All other text field variations have some <code>TopContent</code> to display.</p><p>We could stop here and ask the developer to define the content themselves each time, for example:</p><pre><code><span class="type">FSTextField</span>(
  placeholder: <span class="string">"Placeholder"</span>,
  text: $myText
) {
  <span class="type">HStack</span> {
    <span class="type">Text</span>(title)
      .<span class="call">bold</span>()
    <span class="type">Spacer</span>()
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/design-system/image0.png"/><p>...however, since all these variations have a <code>TopContent</code> with a <code>title-space-something</code> pattern, we can do a little better with a new <code>FSTextField</code> extension:</p><pre><code><span class="keyword">extension</span> <span class="type">FSTextField</span> {
  <span class="keyword">init</span>&lt;TopTrailingContent: <span class="type">View</span>&gt;(
    title: <span class="type">LocalizedStringKey</span>,
    placeholder: <span class="type">LocalizedStringKey</span> = <span class="string">""</span>,
    text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;,
    appearance: <span class="type">Appearance</span> = .<span class="dotAccess">default</span>,
    <span class="keyword">@ViewBuilder</span> topTrailingContent: () -&gt; <span class="type">TopTrailingContent</span>
  ) <span class="keyword">where</span> <span class="type">TopContent</span> == <span class="type">HStack</span>&lt;<span class="type">TupleView</span>&lt;(<span class="type">Text</span>, <span class="type">Spacer</span>, <span class="type">TopTrailingContent</span>)&gt;&gt; {
    <span class="keyword">self</span>.<span class="property">placeholder</span> = placeholder
    <span class="keyword">self</span>.<span class="property">_text</span> = text
    <span class="keyword">self</span>.<span class="property">appearance</span> = appearance
    <span class="keyword">self</span>.<span class="property">topContent</span> = {
      <span class="type">HStack</span> {
        <span class="type">Text</span>(title)
          .<span class="call">bold</span>()
        <span class="type">Spacer</span>()
        <span class="call">topTrailingContent</span>()
      }
    }()
  }
}
</code></pre><p>This new initializer lets developers pass the title text directly as one of the <code>init</code> parameters, and then have the opportunity to define what else is placed on the top trailing corner via the new <code>topTrailingContent</code> parameter.</p><p>For example, our old <code>FSMessageTextField</code> can now be obtained with the following code:</p><pre><code><span class="type">FSTextField</span>(
  title: <span class="string">"Title"</span>, 
  placeholder: <span class="string">"Placeholder"</span>,
  text: $text
) {
  <span class="type">Text</span>(<span class="string">"Message"</span>)
    .<span class="call">font</span>(.<span class="dotAccess">caption</span>)
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/design-system/image3.png"/><p>As before, in case our developers would like to show just a <code>_FSTextField</code> and a title, they shouldn't need to know that they can pass an <code>EmptyView</code> instance as the <code>topTrailingContent</code> parameter, therefore it's better to create a new extension to take care of this scenario:</p><pre><code><span class="keyword">extension</span> <span class="type">FSTextField</span> {
  <span class="keyword">init</span>(
    title: <span class="type">LocalizedStringKey</span>,
    placeholder: <span class="type">LocalizedStringKey</span> = <span class="string">""</span>,
    text: <span class="type">Binding</span>&lt;<span class="type">String</span>&gt;,
    appearance: <span class="type">Appearance</span> = .<span class="dotAccess">default</span>
  ) <span class="keyword">where</span> <span class="type">TopContent</span> == <span class="type">HStack</span>&lt;<span class="type">TupleView</span>&lt;(<span class="type">Text</span>, <span class="type">Spacer</span>, <span class="type">EmptyView</span>)&gt;&gt; {
    <span class="keyword">self</span>.<span class="keyword">init</span>(
      title: title,
      placeholder: placeholder,
      text: text,
      appearance: appearance,
      topTrailingContent: <span class="type">EmptyView</span>.<span class="keyword">init</span>
    )
  }
}
</code></pre><p>Again, this works because <code>EmptyView</code>s are ignored when placed within stacks.</p><p>Thanks to this definition a <em>simple</em> text field + title combo (with no top trailing views) can be obtained via:</p><pre><code><span class="type">FSTextField</span>(title: <span class="string">"Title"</span>, placeholder: <span class="string">"Placeholder, text: $myText)</span>
</code></pre><img src="https://www.fivestars.blog/assets/posts/design-system/image0.png"/><p>Every other variation that we previously have defined with a new <code>View</code> can now be obtained directly via <code>FSTextField</code>:<br>when, in the future, the design team decides to update the title font, the title color, or the spacing between top content and <code>_FSTextField</code> for example, we will be able to update such detail in <strong>one</strong> place, and every other view will inherit the change.</p><p>Not only that, but thanks to our generic approach, future variations of our text field component won't require new definitions or changes in our design system.</p><blockquote><p>📚 A sample project with the design system defined in this article can be found <a href="https://github.com/zntfdr/FiveStarsCodeSamples/tree/main/Composing-SwiftUI-Views">here</a>.</p></blockquote><h2>Conclusions</h2><p>Like code, design is never finished:<br>thanks to Swift and SwiftUI we can build a solid, flexible, and intuitive design system that will help us build, compose, and update entire screens at a pace that wasn't possible before.</p><p>SwiftUI itself uses this very same approach on many of its definitions:</p><ul><li><code>Button</code>s' <code>init(_ titleKey: LocalizedStringKey, action: @escaping () -&gt; Void)</code> is a convenience initializer over <code>init(action: @escaping () -&gt; Void, label: () -&gt; Label)</code></li><li>lazy stacks are just a <a href="https://www.fivestars.blog/articles/lazy-stack-grid/">specific configuration of lazy grids</a></li><li>we've also seen how SwiftUI <a href="https://www.fivestars.blog/articles/optional-binding/">hides optional bindings</a> when they're not needed</li><li><a href="https://developer.apple.com/documentation/swiftui/list"><code>List</code></a> has one initializer for each possible use case (e.g., for <a href="https://www.fivestars.blog/articles/swiftui-hierarchy-list/">hierarchy lists</a>)</li></ul><p>...and many, many more.</p><p>Do your apps have a design system? What other approaches/patterns have you seen/used while building one? <a href="https://twitter.com/zntfdr">Please let me know 😃</a></p><p>Thank you for reading, and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/warn_unqualified_access</guid><title>@warn_unqualified_access</title><description></description><link>https://www.fivestars.blog/articles/warn_unqualified_access</link><pubDate>Tue, 6 Oct 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<blockquote><p>Credits to <a href="https://twitter.com/kingatarthur/status/1306376134427246592">Matt Young</a> for the tip!</p></blockquote><p>In recent Swift releases, we've seen a surge of new attributes such as <code>@unknown</code>, <code>@propertyWrapper</code>, and <code>@main</code>, to name a few:<br>while all of these are new and exciting on their own, in this article, let's focus on a lesser-known, older, but equally helpful attribute, <code>@warn_unqualified_access</code>.</p><h2>Introduction</h2><p><a href="https://github.com/apple/swift/commit/5c71b75b250ef5d564a9bc7bc451fb72227de2fd">Quietly introduced in Swift 2</a>, <code>@warn_unqualified_access</code> helps us remove ambiguity when calling top-level functions and/or static/instance methods.</p><p>Imagine building a new app that has a top-level function, <code>doSomething</code>:</p><pre><code><span class="keyword">@main
struct</span> FiveStarsApp: <span class="type">App</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">Scene</span> {
    <span class="type">WindowGroup</span> {
      <span class="type">ContentView</span>()
    }
  }
}

<span class="keyword">func</span> doSomething() {
  <span class="call">print</span>(<span class="string">"top-level function"</span>)
  <span class="comment">// ...</span>
}
</code></pre><blockquote><p>This example uses SwiftUI, but <code>@warn_unqualified_access</code> can be used everywhere, as it's a Swift feature.</p></blockquote><p>Now, one of our views also declares and uses a <code>doSomething</code> method with the same signature:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(action: doSomething) {
      <span class="type">Text</span>(<span class="string">"Tap me"</span>)
    }
  }

  <span class="keyword">func</span> doSomething() {
    <span class="call">print</span>(<span class="string">"instance method"</span>)
    <span class="comment">// ...</span>
  }
}
</code></pre><p>When the button is tapped, <code>doSomething</code> is called: but which one? Previous experiences in the same scenario might help us here, and we probably know that the instance method has priority over the top-level function.</p><p>This was a quick and simple example, but imagine working on a team and having a view with several more declarations. Maybe a team member wasn't aware of the instance method and meant to call the top-level function instead: how can we prevent such scenarios?</p><p>This is where <code>@warn_unqualified_access</code> comes in: adding this attribute to any method declaration will trigger a warning at the call site when its qualifier is not specified (a.k.a. when the call doesn't specify either the intended instance, class, or module).</p><p>Going back to our example, let's add our <em>new</em> attribute to the function declaration:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span>(action: doSomething) {
      <span class="type">Text</span>(<span class="string">"Tap me"</span>)
    }
  }

  <span class="keyword">@warn_unqualified_access
  func</span> doSomething() {
    <span class="call">print</span>(<span class="string">"instance method"</span>)
    <span class="comment">// ...</span>
  }
}
</code></pre><p>Building the project will now trigger a warning on the button's action. It reads: <code>Use of 'doSomething' treated as a reference to instance method in struct 'ContentView'</code>, and then proposes two actions:</p><ol><li>Use 'self.' to silence this warning</li><li>Use 'FiveStars.' to reference the global function</li></ol><p>Even if a team member weren't aware of the instance method at first, this warning would make sure they become aware of it and provide the two possible solutions to fix the warning.</p><h3>A static method example</h3><p>For completeness's sake, the following is an example where, instead of an instance method, a class method is declared with the same attribute (note how everything is now <code>static</code>):</p><pre><code><span class="keyword">class</span> FSClass {
  <span class="keyword">static func</span> main() {
    <span class="call">doSomething</span>() <span class="comment">// this will trigger a warning</span>
  }

  <span class="keyword">@warn_unqualified_access
  static func</span> doSomething() {
    <span class="call">print</span>(<span class="string">"class method"</span>)
    <span class="comment">/// ...</span>
  }
}
</code></pre><h2>Origin</h2><p>The reason for this attribute goes back to macOS's <a href="https://developer.apple.com/documentation/appkit/nsview#//apple_ref/occ/instm/NSView/print:"><code>NSView</code></a>, where the Objective-C <code>NSView</code>'s <a href="https://developer.apple.com/documentation/appkit/nsview/1483705-print?language=objc"><code>print:</code></a> instance method was translated to <code>print(_:)</code> in Swift:<br>since <code>Swift.print</code> is a top-level function, when used within a <code>NSView</code> subclass, it's lower priority than <code>NSView</code>'s one, meaning that calling <code>print(..)</code> would not print on the debugger, but instead, it triggered the <a href="https://stackoverflow.com/questions/31087601/calling-print-inside-nsview-opens-print-dialog">print dialog</a>, yikes!</p><blockquote><p>This was such an annoying issue that in Xcode 9 the <code>NSView</code>'s method Swift translation <a href="https://stackoverflow.com/a/50898343/5840990">has been renamed</a> to <a href="https://developer.apple.com/documentation/appkit/nsview/1483705-printview"><code>printView(_:)</code></a>, ending the ambiguity once for all.</p></blockquote><h2>Real example: min/max</h2><p>Swift provides <code>min</code> and <code>max</code>, two generic top-level functions that take in two <code>Comparable</code> elements.</p><p><a href="https://developer.apple.com/documentation/swift/anysequence/1689234-min">Pretty</a> <a href="https://developer.apple.com/documentation/swift/range/2996631-max">much</a> <a href="https://developer.apple.com/documentation/swift/flattensequence/1690010-min">every</a> <a href="https://developer.apple.com/documentation/swift/anyiterator/2885896-max">Swift's</a> <a href="https://developer.apple.com/documentation/swift/sequence"><code>Sequence</code></a> variation also provides <code>min</code> <code>max</code> methods: to avoid any confusion, all of them have been marked with <code>@warn_unqualified_access</code>.</p><p>Adding <code>@warn_unqualified_access</code> here helps to avoid disambiguation when we invoke a <code>min</code>/<code>max</code> method within any of our own sequences.</p><h2>SwiftUI</h2><p>In <a href="https://www.fivestars.blog/articles/impossible-swiftui-views/"><code>Impossible SwiftUI views</code></a> we've seen how easy it is to create views that compile fine but reliably crash our app:<br>while preventing such scenarios might be tricky without changing SwiftUI's declarations, we can start from our <code>View</code> extensions.</p><p>Imagine to have a design system, where all <code>Text</code> instances will use a given <code>Font</code> and foreground <code>Color</code>, since this is very common, we've decided to create a <code>View</code> extension:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">func</span> textStyle(
    <span class="keyword">_</span> font: <span class="type">Font</span>? = <span class="keyword">nil</span>,
    foregroundColor: <span class="type">Color</span> = .<span class="dotAccess">black</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">self</span>
      .<span class="call">font</span>(font)
      .<span class="call">foregroundColor</span>(foregroundColor)
  }
}
</code></pre><p>However, this extension won't stop us to declare views like the following:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Text</span>(<span class="string">"Hello, world!"</span>)
    <span class="call">textStyle</span>() <span class="comment">// &lt;-- missing the "."</span>
  }
}

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">textStyle</span>() <span class="comment">// &lt;-- missing the actual `Text` component</span>
  }
}
</code></pre><p>Which will end up in a guaranteed crash.</p><p>If we now add <code>@warn_unqualified_access</code> to our <code>textStyle</code> method declaration, this changes, as we now get a warning at compile time:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Text</span>(<span class="string">"Hello, world!"</span>)
    <span class="call">textStyle</span>() <span class="comment">// Warning: Use of 'textStyle' treated as a reference to instance method in protocol 'View'</span>
  }
}

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">textStyle</span>() <span class="comment">// Warning: Use of 'textStyle' treated as a reference to instance method in protocol 'View'</span>
  }
}
</code></pre><p>We can still write <code>self.textStyle()</code>, inhibiting the warning and still ending up in a crash, but most likely, this attribute will help us avoid writing bad declarations by mistake.</p><h2>Conclusions</h2><p>In this article, we've explored <code>@warn_unqualified_access</code>, a little known Swift attribute that can help us write better and clearer Swift code.</p><p>Do you use <code>@warn_unqualified_access</code>? Have you seen any other cool examples? Please <a href="https://twitter.com/zntfdr">let me know</a>!</p><p>Thank you for reading, and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/lazy-stack-grid</guid><title>Lazy stacks secrets</title><description>Lazy stacks secrets, hidden in plain sigh.</description><link>https://www.fivestars.blog/articles/lazy-stack-grid</link><pubDate>Tue, 29 Sep 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>A couple of weeks ago, I updated <a href="https://github.com/zntfdr/AStack"><code>AStack</code></a>, a micro-library that switches any SwiftUI stack axis when the environment <a href="https://developer.apple.com/documentation/swiftui/environmentvalues/sizecategory">content size category</a> is among the accessibility ones.</p><p>In doing so, I've noticed something that I missed before: lazy stacks initializers offer a <code>pinnedViews</code> parameter, which is suspiciously identical to what lazy grids offer.</p><p>This discovery made me think of <a href="https://pspdfkit.com/blog/2017/the-case-for-deprecating-uitableview/">two</a> <a href="https://pspdfkit.com/blog/2020/the-case-for-lists-in-uicollectionview/">articles</a> from the great <a href="https://pspdfkit.com/blog/">PSPDFKit blog</a>, highlighting how Apple is moving away from table views and going all-in with collections:<br>image having this knowledge and being responsible for building a brand new UI framework. You certainly wouldn't repeat history, would you?</p><p>In this article, let's compare lazy stacks with their counterpart, lazy grids.</p><blockquote><p>We will focus on the vertical variant of these views: the same concepts are equivalent to the horizontal ones.</p></blockquote><h2>Definitions</h2><p>The following definitions come from the official documentation:</p><ul><li><a href="https://developer.apple.com/documentation/swiftui/lazyvstack"><code>LazyVStack</code></a>: A view that arranges its children in a line that grows vertically, creating items only as needed</li><li><a href="https://developer.apple.com/documentation/swiftui/lazyvgrid"><code>LazyVGrid</code></a>: A container view that arranges its child views in a grid that grows vertically, creating items only as needed</li></ul><p>We've just read the same sentence twice:</p><ul><li>the second half of the definitions is an exact match, word for word</li><li>the first half varies slightly: <code>LazyVStack</code> arranges its children <em>in a line that grows vertically</em>, while <code>LazyVGrid</code> arranges its children <em>in a grid that grows vertically</em></li></ul><p>The difference is clear:<br>one is a grid, and the other is a line (or, dare I say, a <em>column</em>). Everything else is identical.</p><h2>Initializers</h2><p>Here are the initializers:</p><pre><code><span class="keyword">public struct</span> LazyVStack&lt;Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">public init</span>(
    alignment: <span class="type">HorizontalAlignment</span> = .<span class="dotAccess">center</span>, 
    spacing: <span class="type">CGFloat</span>? = <span class="keyword">nil</span>, 
    pinnedViews: <span class="type">PinnedScrollableViews</span> = .<span class="keyword">init</span>(), 
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>
  )
}

<span class="keyword">public struct</span> LazyVGrid&lt;Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">public init</span>(
    columns: [<span class="type">GridItem</span>], 
    alignment: <span class="type">HorizontalAlignment</span> = .<span class="dotAccess">center</span>, 
    spacing: <span class="type">CGFloat</span>? = <span class="keyword">nil</span>, 
    pinnedViews: <span class="type">PinnedScrollableViews</span> = .<span class="keyword">init</span>(), 
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>
  )
}
</code></pre><p>Once again, a near-exact match:<br>the only difference is an extra parameter on <code>LazyVGrid</code>, <code>columns</code>, where we pass the grid column configuration, telling SwiftUI how many columns we would like and their size.</p><blockquote><p>For an in-depth look at grids and how they work, please refer to <a href="https://swiftui-lab.com/impossible-grids/">this great article</a> by <a href="https://twitter.com/SwiftUILab">Javier Nigro</a>.</p></blockquote><h2>Making LazyVGrid mock LazyVStack</h2><p>We've seen how, on the surface, these views are nearly identical. Observing how <code>LazyVStack</code> behaves in practice, it's similar to a grid with one column, a.k.a. a flexible size <code>GridItem</code>: what if this is really it?</p><p>We now define <code>LazyVStackMock</code>, a view that matches this exact definition:</p><pre><code><span class="keyword">public struct</span> LazyVStackMock&lt;Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">let</span> alignment: <span class="type">HorizontalAlignment</span>
  <span class="keyword">let</span> spacing: <span class="type">CGFloat</span>?
  <span class="keyword">let</span> pinnedViews: <span class="type">PinnedScrollableViews</span>
  <span class="keyword">let</span> content: <span class="type">Content</span>

  <span class="keyword">public init</span>(
    alignment: <span class="type">HorizontalAlignment</span> = .<span class="dotAccess">center</span>,
    spacing: <span class="type">CGFloat</span>? = <span class="keyword">nil</span>,
    pinnedViews: <span class="type">PinnedScrollableViews</span> = .<span class="keyword">init</span>(),
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>
  ) {
    <span class="keyword">self</span>.<span class="property">alignment</span> = alignment
    <span class="keyword">self</span>.<span class="property">spacing</span> = spacing
    <span class="keyword">self</span>.<span class="property">pinnedViews</span> = pinnedViews
    <span class="keyword">self</span>.<span class="property">content</span> = <span class="call">content</span>()
  }

  <span class="keyword">public var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">LazyVGrid</span>(
      columns: [<span class="type">GridItem</span>(.<span class="call">flexible</span>())],
      alignment: alignment,
      spacing: spacing,
      pinnedViews: pinnedViews,
      content: { content }
    )
  }
}
</code></pre><blockquote><p><code>LazyVStackMock</code>'s body is a <code>LazyVGrid</code> with one column flexible size <code>GridItem</code></p></blockquote><h3>Comparing LazyVStack with LazyVStackMock</h3><p>Did we just reinvent <code>LazyVStack</code>? This is our testing ground:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">HStack</span> {
      <span class="type">LazyVStack</span> {
          <span class="type">Section</span>(header: <span class="call">header</span>(title: <span class="string">"Original"</span>)) { content }
      }
      <span class="type">LazyVStackMock</span> {
          <span class="type">Section</span>(header: <span class="call">header</span>(title: <span class="string">"Mock"</span>)) { content }
      }
    }
    .<span class="call">font</span>(.<span class="dotAccess">title</span>)
  }

  <span class="keyword">var</span> content: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ForEach</span>(<span class="number">1</span>...<span class="number">10</span>, id: \.<span class="keyword">self</span>) { count <span class="keyword">in</span>
      <span class="type">Label</span>(<span class="string">"Placeholder</span> \(count)<span class="string">"</span>, colorfulSystemImage: <span class="string">"leaf.fill"</span>)
    }
  }

  <span class="keyword">func</span> header(title: <span class="type">String</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Text</span>(verbatim: title)
      .<span class="call">bold</span>()
      .<span class="call">padding</span>(.<span class="dotAccess">horizontal</span>)
      .<span class="call">padding</span>(.<span class="dotAccess">vertical</span>, <span class="number">4</span>)
      .<span class="call">foregroundColor</span>(.<span class="dotAccess">white</span>)
      .<span class="call">background</span>(
        <span class="type">Capsule</span>().<span class="call">foregroundColor</span>(.<span class="dotAccess">green</span>)
      )
  }
}
</code></pre><blockquote><p>We're using the <code>Label</code> extension introduced in <a href="https://www.fivestars.blog/articles/label/">last week's article</a></p></blockquote><img src="https://www.fivestars.blog/assets/posts/lazy-lie/image1.png"/><p>If we run the test as is, we will have an identical result.</p><p>Let's see what happens if we specify other parameters:</p><ul><li>Alignment (we add <code>alignment: .leading</code>)</li></ul><img src="https://www.fivestars.blog/assets/posts/lazy-lie/image2.png"/><ul><li>Spacing (we add <code>spacing: 20</code> )</li></ul><img src="https://www.fivestars.blog/assets/posts/lazy-lie/image3.png"/><ul><li>Pinned views (we add <code>pinnedViews: [.sectionHeaders]</code>)</li></ul><img src="https://www.fivestars.blog/assets/posts/lazy-lie/image1.png"/><p>We still have a match even if we add everything together, put each stack in a <code>ScrollView</code>, and add several more items:</p><img src="https://www.fivestars.blog/assets/posts/lazy-lie/final.gif"/><blockquote><p>In the video, the content size was put to <code>ExtraSmall</code> to make the view fit nicely on the screen.</p></blockquote><p>The complete project can be found <a href="https://github.com/zntfdr/FiveStarsCodeSamples/tree/main/Stack-vs-Grid">here</a>.</p><h2>Conclusions</h2><p>The <a href="https://en.wikipedia.org/wiki/Duck_test">duck test</a> says, "<em>If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.</em>":<br>while we don't have access to SwiftUI's source code, our test shows that our <code>LazyVStackMock</code>, which is just a one-column <code>LazyVGrid</code> in disguise, perfectly matches <code>LazyVStack</code> in all scenarios.</p><p>We cannot guarantee that this is the case, but I still find it super interesting that SwiftUI's lazy stacks are just a specific implementation of their grid counterparts.</p><p>A framework goal is to empower adopters to build great things with as little friction as possible:<br>the SwiftUI team could have told us to create a lazy grid with one flexible GridItem every time we need a one column/row layout. Instead, having these lazy stacks backed-in makes it easier (and faster!) to create such common designs, all without requiring to know how grids work or even about their existence.</p><h2>What about lists?</h2><p>If, from the preamble, you've been wondering about <code>List</code>s: yes! You'd be completely right: this component sounds a lot like the SwiftUI's version of UIKit's table view.</p><p><code>List</code> also shares the same path with <code>UITableView</code>:</p><ul><li><code>UITableView</code> was introduced in iPhone OS 2.0 (in 2008), while <code>UICollectionView</code> was introduced in iOS 6.0 (six years later).</li><li><code>List</code> was introduced along SwiftUI in 2019, while lazy stacks/grids came a year later.</li></ul><p>Does it mean that <code>List</code> is going to be deprecated as well? Well, we haven't seen the deprecation of <code>UITableView</code> yet, so it's hard to say when/if this will ever happen.</p><p>However, the answer is <em>not for the time being</em>:<br>while <code>ScrollView</code> + lazy stacks are much more flexible and customizable than <code>List</code>s, <code>List</code>s are the go-to place if we're after the iOS/system look. Along with various <a href="https://developer.apple.com/documentation/swiftui/liststyle">list styles</a>, and other features such as tap highlight and swipe actions (all of which <code>ScrollViews</code> don't natively support yet, FB9142134).</p><p>Therefore SwiftUI's lazy views and <code>ScrollView</code> need to gain even more functionality and flexibility if we want this to happen. In the meanwhile, I can't wait to see what SwiftUI will bring us next!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/label</guid><title>SwiftUI's Label</title><description>Explore this new SwiftUI view beyond the basics.</description><link>https://www.fivestars.blog/articles/label</link><pubDate>Tue, 22 Sep 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p><a href="https://developer.apple.com/documentation/swiftui/label"><code>Label</code></a> is a new SwiftUI component. The SwiftUI team did a great job with <a href="https://developer.apple.com/documentation/swiftui/label"><code>Label</code>'s documentation</a>: if you're not familiar with this view yet, please <a href="https://developer.apple.com/documentation/swiftui/label">have a read at it first</a>.</p><p><code>Label</code> combines a text and an image into a single view. It also adapts based on the context (e.g., if it's put on a toolbar) and dynamic type.</p><img src="https://www.fivestars.blog/assets/posts/label/image0.png"/><p>In this article, let's explore this view beyond the basics.</p><h2>Initializers</h2><p><code>Label</code> comes with six initializers:</p><ul><li>the first four offer all possible combinations of text as <a href="https://developer.apple.com/documentation/swift/stringprotocol"><code>StringProtocol</code></a> or <a href="https://developer.apple.com/documentation/swiftui/localizedstringkey"><code>LocalizedStringKey</code></a>, and an image from an assets catalog or SF Symbols</li></ul><pre><code><span class="keyword">extension</span> <span class="type">Label</span> <span class="keyword">where</span> <span class="type">Title</span> == <span class="type">Text</span>, <span class="type">Icon</span> == <span class="type">Image</span> {
  <span class="keyword">public init</span>(<span class="keyword">_</span> titleKey: <span class="type">LocalizedStringKey</span>, image name: <span class="type">String</span>)
  <span class="keyword">public init</span>(<span class="keyword">_</span> titleKey: <span class="type">LocalizedStringKey</span>, systemImage name: <span class="type">String</span>)
  <span class="keyword">public init</span>&lt;S: <span class="type">StringProtocol</span>&gt;(<span class="keyword">_</span> title: <span class="type">S</span>, image name: <span class="type">String</span>)
  <span class="keyword">public init</span>&lt;S: <span class="type">StringProtocol</span>&gt;(<span class="keyword">_</span> title: <span class="type">S</span>, systemImage name: <span class="type">String</span>)
}
</code></pre><ul><li>the most flexible initializer takes two generics views, <em>no strings attached</em></li></ul><pre><code><span class="keyword">extension</span> <span class="type">Label</span> {
  <span class="keyword">public init</span>(<span class="keyword">@ViewBuilder</span> title: () -&gt; <span class="type">Title</span>, <span class="keyword">@ViewBuilder</span> icon: () -&gt; <span class="type">Icon</span>)
}
</code></pre><ul><li>the last initializer takes in a <a href="https://developer.apple.com/documentation/swiftui/labelstyleconfiguration"><code>LabelStyleConfiguration</code></a></li></ul><pre><code><span class="keyword">extension</span> <span class="type">Label</span> <span class="keyword">where</span> <span class="type">Title</span> == <span class="type">LabelStyleConfiguration</span>.<span class="type">Title</span>, <span class="type">Icon</span> == <span class="type">LabelStyleConfiguration</span>.<span class="type">Icon</span> {
  <span class="keyword">public init</span>(<span class="keyword">_</span> configuration: <span class="type">LabelStyleConfiguration</span>)
}
</code></pre><p>Every initializer has its place and use: we will cover all of them in this article.</p><h2>Label styles</h2><p>Unless we're in a particular context (e.g., a navigation bar), both <code>Label</code>'s title and image are displayed by default.</p><p>If we want to show only one of the two components (either the image or just the title), or change our <code>Label</code> appearance in other ways, we can do so via the <a href="https://developer.apple.com/documentation/swiftui/view/labelstyle(_:)"><code>labelStyle(_:)</code></a> view modifier:<br>this modifier accepts a <a href="https://developer.apple.com/documentation/swiftui/labelstyle"><code>LabelStyle</code></a> instance.</p><p><code>LabelStyle</code> tells SwiftUI how we would like the <code>Label</code> to be drawn on screen, by default we have three options:</p><ul><li><code>.iconOnly</code></li><li><code>.titleOnly</code></li><li><code>.automatic</code></li></ul><p>The names are self-explanatory. These backed-in styles are mutually exclusive: if we apply multiple styles to the same <code>Label</code>, only the closest one to the <code>Label</code> will take effect.</p><pre><code><span class="comment">// Only `.iconOnly` will be applied to the label:</span>
<span class="type">Label</span>(<span class="string">"Title"</span>, systemImage: <span class="string">"moon.circle.fill"</span>) 
  .<span class="call">labelStyle</span>(.<span class="dotAccess">iconOnly</span>)
  .<span class="call">labelStyle</span>(.<span class="dotAccess">titleOnly</span>)
  .<span class="call">labelStyle</span>(<span class="type">DefaultLabelStyle</span>())
</code></pre><img src="https://www.fivestars.blog/assets/posts/label/image1.png"/><p>As <code>LabelStyle</code> is a protocol, we can define our own styles:</p><pre><code><span class="keyword">public protocol</span> LabelStyle {
  <span class="keyword">associatedtype</span> Body: <span class="type">View</span>

  <span class="keyword">func</span> makeBody(configuration: <span class="type">Self</span>.<span class="type">Configuration</span>) -&gt; <span class="type">Self</span>.<span class="type">Body</span>

  <span class="keyword">typealias</span> Configuration = <span class="type">LabelStyleConfiguration</span>
}
</code></pre><p>Similarly to <a href="https://developer.apple.com/documentation/swiftui/viewmodifier"><code>ViewModifier</code></a>, <code>LabelStyle</code> requires a <code>makeBody(configuration:)</code> method, allowing us to define our style.</p><p><code>makeBody(configuration:)</code> takes in a <a href="https://developer.apple.com/documentation/swiftui/labelstyleconfiguration"><code>LabelStyleConfiguration</code></a> instance, which is the same parameter accepted by the last <code>Label</code> initializer we've listed above.</p><p>This configuration carries the complete set of instructions that defines the <code>Label</code> up to this point.</p><p>We can't define a brand new configuration ourselves, this is reserved to SwiftUI. However, we do have access to the current components of the <code>Label</code>, the image (named <code>icon</code>) and the title:</p><pre><code><span class="keyword">public struct</span> LabelStyleConfiguration {
  <span class="comment">/// A type-erased title view of a label.</span>
  <span class="keyword">public var</span> title: <span class="type">LabelStyleConfiguration</span>.<span class="type">Title</span> { <span class="keyword">get</span> }

  <span class="comment">/// A type-erased icon view of a label.</span>
  <span class="keyword">public var</span> icon: <span class="type">LabelStyleConfiguration</span>.<span class="type">Icon</span> { <span class="keyword">get</span> }
}
</code></pre><p>Thanks to this configuration, our <code>LabelStyle</code> is applied <strong>on top</strong> of the current style.</p><p>For example, here we build a <code>LabelStyle</code> that adds a shadow to the entire <code>Label</code>:</p><pre><code><span class="keyword">struct</span> ShadowLabelStyle: <span class="type">LabelStyle</span> {
  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Label</span>(configuration)
      .<span class="call">shadow</span>(color: <span class="type">Color</span>.<span class="property">gray</span>.<span class="call">opacity</span>(<span class="number">0.9</span>), radius: <span class="number">4</span>, x: <span class="number">0</span>, y: <span class="number">5</span>)
  }
}

<span class="keyword">extension</span> <span class="type">LabelStyle</span> <span class="keyword">where</span> <span class="type">Self</span> == <span class="type">ShadowLabelStyle</span> {
  <span class="keyword">public static var</span> shadow: <span class="type">ShadowLabelStyle</span> {
    <span class="type">ShadowLabelStyle</span>()
  }
}
</code></pre><blockquote><p><code>makeBody(configuration:)</code> is the only place where we can use this <code>Label</code> initializer.</p></blockquote><p>As <code>ShadowLabelStyle</code> is styling on top of the current <code>LabelStyle</code>, it will be applied to whatever the <code>Label</code> currently is.</p><p>Therefore, if we use it along with <code>.iconOnly</code> for example, the final result will be a <code>Label</code> with just the icon and our shadow:</p><pre><code><span class="type">Label</span>(<span class="string">"Title"</span>, systemImage: <span class="string">"moon.circle.fill"</span>)
  .<span class="call">labelStyle</span>(.<span class="dotAccess">shadow</span>)
  .<span class="call">labelStyle</span>(.<span class="dotAccess">iconOnly</span>)
</code></pre><img src="https://www.fivestars.blog/assets/posts/label/image2.png"/><h3>Label style erasers</h3><p>The <code>.labelStyles</code> declaration order is important: previously, we've seen how the three backed-in styles are mutually exclusive. This really means that in their definition, they don't use the configuration passed in <code>makeBody(configuration:)</code>, but will create a new one instead.</p><p>In other words, <code>.iconOnly</code>, <code>.titleOnly</code>, and <code>.automatic</code> act as style erasers: any previous style is not carried over.</p><p>Going back to our <code>ShadowLabelStyle</code> example:</p><pre><code><span class="type">Label</span>(<span class="string">"Title"</span>, systemImage: <span class="string">"moon.circle.fill"</span>)
  .<span class="call">labelStyle</span>(.<span class="dotAccess">shadow</span>)
  .<span class="call">labelStyle</span>(.<span class="dotAccess">iconOnly</span>)
</code></pre><img src="https://www.fivestars.blog/assets/posts/label/image2.png"/><p>will output the <code>Label</code> icon with a shadow.</p><pre><code><span class="type">Label</span>(<span class="string">"Title"</span>, systemImage: <span class="string">"moon.circle.fill"</span>)
  .<span class="call">labelStyle</span>(.<span class="dotAccess">iconOnly</span>) <span class="comment">// &lt;- the label style order has been swapped</span>
  .<span class="call">labelStyle</span>(.<span class="dotAccess">shadow</span>)
</code></pre><img src="https://www.fivestars.blog/assets/posts/label/image1.png"/><p>will output the <code>Label</code> icon with <strong>no</strong> shadow.</p><p>Since we're using a style eraser, SwiftUI won't even bother to apply our style first, this can be verified by adding a breaking point in <code>ShadowLabelStyle</code>'s <code>makeBody(configuration:)</code> implementation: SwiftUI won't call our method at all.</p><p>This is in line with what <a href="https://www.fivestars.blog/articles/preferencekey-reduce/">we've seen with SwiftUI's preference keys</a>: SwiftUI always strives to do the least amount of work possible.</p><h3>Can we define our own style eraser?</h3><p>As mentioned above, only SwiftUI can create new configurations, however there's a simple trick that will make any custom style also a style eraser: apply one of the native style erasers in our <code>makeBody(configuration:)</code> implementation.</p><pre><code><span class="keyword">struct</span> ShadowEraseLabelStyle: <span class="type">LabelStyle</span> {
  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Label</span>(configuration)
      .<span class="call">shadow</span>(color: <span class="type">Color</span>.<span class="property">gray</span>.<span class="call">opacity</span>(<span class="number">0.9</span>), radius: <span class="number">4</span>, x: <span class="number">0</span>, y: <span class="number">5</span>)
      .<span class="call">labelStyle</span>(.<span class="dotAccess">automatic</span>) <span class="comment">// &lt;- ✨</span>
  }
}

<span class="keyword">extension</span> <span class="type">LabelStyle</span> <span class="keyword">where</span> <span class="type">Self</span> == <span class="type">ShadowEraseLabelStyle</span> {
  <span class="keyword">public static var</span> shadowErase: <span class="type">ShadowEraseLabelStyle</span> { 
    <span class="type">ShadowEraseLabelStyle</span>()
  }
}
</code></pre><p>In this example we force our <code>Label</code> to display both the text and the icon, along with our shadow, any other style applied previously is ignored:</p><pre><code><span class="type">Label</span>(<span class="string">"Title"</span>, systemImage: <span class="string">"moon.circle.fill"</span>)
  .<span class="call">labelStyle</span>(.<span class="dotAccess">shadowErase</span>)
  .<span class="call">labelStyle</span>(.<span class="dotAccess">titleOnly</span>)
  .<span class="call">labelStyle</span>(.<span class="dotAccess">iconOnly</span>)
</code></pre><img src="https://www.fivestars.blog/assets/posts/label/image4.png"/><p>Again, since our style now acts as a style eraser, it won't be applied on top of the current style, but will start with a clean <code>Label</code> instead.</p><h3><code>LabelStyleConfiguration</code>'s icon and title style</h3><p>We might have tried to erase the style also by passing the two views of our <code>LabelStyleConfiguration</code> to a new <code>Label</code>:</p><pre><code><span class="keyword">struct</span> ShadowLabelTryStyle: <span class="type">LabelStyle</span> {
  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Label</span>(
      title: { configuration.<span class="property">icon</span> },
      icon: { configuration.<span class="property">title</span> }
    )
    .<span class="call">shadow</span>(color: <span class="type">Color</span>.<span class="property">gray</span>.<span class="call">opacity</span>(<span class="number">0.9</span>), radius: <span class="number">4</span>, x: <span class="number">0</span>, y: <span class="number">5</span>)
  }
}

<span class="keyword">extension</span> <span class="type">LabelStyle</span> <span class="keyword">where</span> <span class="type">Self</span> == <span class="type">ShadowLabelTryStyle</span> {
  <span class="keyword">public static var</span> shadowTry: <span class="type">ShadowLabelTryStyle</span> { 
    <span class="type">ShadowLabelTryStyle</span>()
  }
}
</code></pre><p>with our view body:</p><pre><code><span class="type">Label</span>(<span class="string">"Title"</span>, systemImage: <span class="string">"moon.circle.fill"</span>)
  .<span class="call">labelStyle</span>(.<span class="dotAccess">shadowTry</span>)
  .<span class="call">labelStyle</span>(.<span class="dotAccess">iconOnly</span>)
</code></pre><img src="https://www.fivestars.blog/assets/posts/label/image2.png"/><p>Interestingly, this would not have worked:<br>it turns out that <code>configuration.icon</code> and <code>configuration.title</code> carry over the whole configuration style.</p><p>In the example above the <code>title</code> view would have been hidden, despite us creating a new <code>Label</code>, without passing the previous configuration.</p><p>To further prove this, let's define a new style that all it does is swapping the <code>Label</code>'s' <code>title</code> with its <code>icon</code>:</p><pre><code><span class="keyword">struct</span> SwapLabelStyle: <span class="type">LabelStyle</span> {
  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Label</span>(
      title: { configuration.<span class="property">icon</span> },
      icon: { configuration.<span class="property">title</span> }
    )
  }
}

<span class="keyword">extension</span> <span class="type">LabelStyle</span> <span class="keyword">where</span> <span class="type">Self</span> == <span class="type">SwapLabelStyle</span> {
  <span class="keyword">public static var</span> swap: <span class="type">SwapLabelStyle</span> { 
    <span class="type">SwapLabelStyle</span>()
  }
}
</code></pre><blockquote><p>The new label has the original title as its icon, and the original icon as its title.</p></blockquote><p>Now imagine this view body:</p><pre><code><span class="type">Label</span>(<span class="string">"Title"</span>, systemImage: <span class="string">"moon.circle.fill"</span>)
  .<span class="call">labelStyle</span>(.<span class="dotAccess">swap</span>)
  .<span class="call">labelStyle</span>(.<span class="dotAccess">iconOnly</span>)
</code></pre><p>What we expect the final outcome to be?</p><p>We first apply <code>.iconOnly</code>, therefore the title <code>"Title"</code> is hidden, while the image <code>"moon.circle.fill"</code> is shown.<br>It doesn't matter that we swap them in <code>SwapLabelStyle</code>: the user will see the original icon, despite being the title in the <code>SwapLabelStyle</code> <code>Label</code>.</p><img src="https://www.fivestars.blog/assets/posts/label/image1.png"/><h3>Truly custom styles</h3><p>For completeness, I must point out that <code>LabelStyle</code>'s <code>makeBody(configuration:)</code> only requires <code>some View</code> to be returned, it doesn't require a <code>Label</code> (or a <code>Label</code> with a few modifiers).</p><p>We can really return anything: what about turning our label into a <code>HStack</code>?</p><pre><code><span class="keyword">struct</span> HStackLabelStyle: <span class="type">LabelStyle</span> {
  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">HStack</span> {
      configuration.<span class="property">icon</span>
      <span class="type">Spacer</span>()
      configuration.<span class="property">title</span>
    }
  }
}

<span class="keyword">extension</span> <span class="type">LabelStyle</span> <span class="keyword">where</span> <span class="type">Self</span> == <span class="type">HStackLabelStyle</span> {
  <span class="keyword">public static var</span> hStack: <span class="type">HStackLabelStyle</span> { 
    <span class="type">HStackLabelStyle</span>()
  }
}
</code></pre><p>And here we use it as any other <code>Label</code>:</p><pre><code><span class="type">Label</span>(<span class="string">"Title"</span>, systemImage: <span class="string">"moon.circle.fill"</span>)
  .<span class="call">labelStyle</span>(.<span class="dotAccess">hStack</span>)
</code></pre><img src="https://www.fivestars.blog/assets/posts/label/image5.png"/><p>While this works, this is the perfect opportunity to point out that <code>.labelStyle</code> modifiers work only if they're applied to a <code>Label</code>:<br>since <code>HStackLabelStyle</code> doesn't return a <code>Label</code>, any other label style applied, including erasing ones, will be ignored.</p><pre><code><span class="type">Label</span>(<span class="string">"Title"</span>, systemImage: <span class="string">"moon.circle.fill"</span>)
  .<span class="call">labelStyle</span>(.<span class="dotAccess">hStack</span>)
  .<span class="call">labelStyle</span>(.<span class="dotAccess">shadow</span>)
  .<span class="call">labelStyle</span>(.<span class="dotAccess">iconOnly</span>)
</code></pre><img src="https://www.fivestars.blog/assets/posts/label/image5.png"/><p>However, applying them before <code>HStackLabelStyle</code> would work:</p><pre><code><span class="type">Label</span>(<span class="string">"Title"</span>, systemImage: <span class="string">"moon.circle.fill"</span>)
  .<span class="call">labelStyle</span>(.<span class="dotAccess">shadow</span>)
  .<span class="call">labelStyle</span>(.<span class="dotAccess">iconOnly</span>)
  .<span class="call">labelStyle</span>(.<span class="dotAccess">hStacl</span>)
</code></pre><img src="https://www.fivestars.blog/assets/posts/label/image2.png"/><p>Note that if we don't return a <code>Label</code> within a <code>LabelStyle</code>, we probably shouldn't use <code>Label</code> in the first place.</p><h2>Accessible Labels</h2><p>While <code>LabelStyle</code> is mainly thought of for adding new styles, we can also use it to make our <code>Label</code>s more accessible.</p><p>For example, when the system content size is among the accessibility ones, we might want to strip any <code>Label</code> effect and hide the icon, leaving the bare minimum necessary for the user to go on with their task.</p><p>This is a great example where <code>LabelStyle</code> excels, along with our <a href="https://www.fivestars.blog/articles/conditional-modifiers/">conditional modifier extension</a>:</p><pre><code><span class="keyword">struct</span> AccessibleLabelStyle: <span class="type">LabelStyle</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">sizeCategory</span>) <span class="keyword">var</span> sizeCategory: <span class="type">ContentSizeCategory</span>

  <span class="keyword">func</span> makeBody(configuration: <span class="type">Configuration</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Label</span>(configuration)
      .<span class="call">if</span>(sizeCategory.<span class="property">isAccessibilityCategory</span>) { $0.<span class="call">labelStyle</span>(.<span class="dotAccess">titleOnly</span>) }
  }
}
</code></pre><p>Here's an example with all possible content sizes:</p><img src="https://www.fivestars.blog/assets/posts/label/image6.png"/><h2><code>Label</code> Extensions</h2><p>Despite label styles being the main way to customize and standardize our labels, sometimes we can get away by creating a <code>Label</code> extension instead.</p><p>For example, among <a href="https://www.wwdcnotes.com/notes/wwdc20/10207/">SF Symbols updates</a> this year, we've gained color variants for some of them:<br>Unfortunately, <code>Label</code> defaults to display the mono color variant with no way to change it out of the box.</p><p>This can be addressed via <code>Label</code> extension:</p><pre><code><span class="keyword">extension</span> <span class="type">Label</span> <span class="keyword">where</span> <span class="type">Title</span> == <span class="type">Text</span>, <span class="type">Icon</span> == <span class="type">Image</span> {
  <span class="keyword">init</span>(<span class="keyword">_</span> title: <span class="type">LocalizedStringKey</span>, colorfulSystemImage systemImage: <span class="type">String</span>) {
    <span class="keyword">self</span>.<span class="keyword">init</span> {
      <span class="type">Text</span>(title)
    } icon: {
      <span class="type">Image</span>(systemName: systemImage)
        .<span class="call">renderingMode</span>(.<span class="dotAccess">original</span>)
    }
  }
}
</code></pre><p>Which we can use by replacing the <code>systemImage</code> argument name with <code>colorfulSystemImage</code>, for example:</p><pre><code><span class="type">Label</span>(<span class="string">"Title"</span>, colorfulSystemImage: <span class="string">"moon.circle.fill"</span>)
  .<span class="call">labelStyle</span>(<span class="type">ShadowLabelStyle</span>())
</code></pre><img src="https://www.fivestars.blog/assets/posts/label/image7.png"/><h2>Conclusions</h2><p>If I told you at the beginning of this article that you were about to read ~1500 words on <code>Label</code>, I bet you wouldn't believe me: it's <em>just</em> an icon and a text, right?</p><p><code>Label</code> is another example of SwiftUI looking incredibly simple on the surface, but hiding lots of complexity and flexibility behind the scenes.</p><p>This shouldn't surprise us anymore: and yet here we are, wondering what else we can discover about SwiftUI, <a href="https://www.fivestars.blog/feed.rss">subscribe to the five star RSS feed</a> for more SwiftUI content.</p><p>Thanks for reading and please <a href="https://twitter.com/zntfdr">let me know</a> if I've missed anything!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/impossible-swiftui-views</guid><title>Why SwiftUI primitives have body type Never?</title><description>An exploration of impossible SwiftUI views</description><link>https://www.fivestars.blog/articles/impossible-swiftui-views</link><pubDate>Wed, 16 Sep 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>When defining a SwiftUI view, it comes automatically to write <code>struct MyView: View</code> followed by <code>var body: some View { ... }</code>. All our views are composed of other views. At some point SwiftUI needs to draw something on the screen:<br>when does it end? How does SwiftUI know that it has reached the bottom of a view hierarchy?</p><p>In this article, let's continue our exploration of SwiftUI's inner workings.</p><h2>The <code>View</code> protocol</h2><p>Every SwiftUI view is a type conforming to the <code>View</code> protocol:</p><pre><code><span class="keyword">public protocol</span> View {
  <span class="keyword">associatedtype</span> Body: <span class="type">View</span>
  <span class="keyword">@ViewBuilder var</span> body: <span class="type">Self</span>.<span class="type">Body</span> { <span class="keyword">get</span> }
}
</code></pre><p>Since this is a Swift protocol, we can make any type conform to it, for example, <code>String</code>:</p><pre><code><span class="keyword">extension</span> <span class="type">String</span>: <span class="type">View</span> {
  <span class="keyword">public var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Text</span>(<span class="keyword">self</span>)
  }
}
</code></pre><blockquote><p>this is just an example, not a suggestion nor a best practice.</p></blockquote><p>Which allows us to write a <code>String</code> directly into a <code>View</code> declaration:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="string">"Hello world!"</span>
  }
}
</code></pre><p>As we're using the <code>Text</code> <em>primitive</em> in <code>String</code>'s <code>body</code>, this will work as expected, but what if we didn't? Let's build a new SwiftUI view which uses none of SwiftUI's primitives:</p><pre><code><span class="keyword">struct</span> MyView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">self</span>
  }
}
</code></pre><p>In this case we're defining a new <code>View</code> called <code>MyView</code>, here we use it in <code>ContentView</code>:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">MyView</span>()
  }
}
</code></pre><p>This builds fine. However, as <code>MyView</code>'s' <code>body</code> is the view itself, we're stuck on an infinite recursion, which will make SwiftUI terminate our app.</p><p>Despite the <code>View</code> protocol letting us conform pretty much anything to it, if we want to use those declarations in SwiftUI, we must use SwiftUI primitives or be ready to see our app crash.</p><p>What do these SwiftUI primitives have that make them unique, allowing SwiftUI to break the infinite recursion we've found ourselves in? The answer is in their <code>body</code> type: <code>Never</code>.</p><h2><code>Never</code></h2><p><a href="https://github.com/apple/swift-evolution/blob/master/proposals/0102-noreturn-bottom-type.md">Swift 3.0 has brought us <code>Never</code></a>, an <a href="https://en.wikipedia.org/wiki/Bottom_type"><em>uninhabited</em> type</a>: <code>Never</code> is a type with no possible values, making it impossible for us to get or create an instance.</p><p>We might have met <code>Never</code> for example:</p><ul><li>in Combine's Publishers, typically to indicate that a given publisher can't throw errors</li><li>as the return type of <a href="https://developer.apple.com/documentation/swift/1539374-preconditionfailure"><code>preconditionFailure(_:file:line:)</code></a> or <a href="https://developer.apple.com/documentation/swift/1538698-fatalerror"><code>fatalError(_:file:line:)</code></a>, indicating that once called, there's no way out</li></ul><p>Let's declare a view with body type <code>Never</code>:</p><pre><code><span class="keyword">struct</span> ImpossibleView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="type">Never</span>
}
</code></pre><p>This builds! However, we cannot use it: we can't instantiate it without passing something like <code>fatalError()</code>. Regardless, let's implement the body and run our app:</p><pre><code><span class="keyword">struct</span> ImpossibleView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="type">Never</span> {
    <span class="call">fatalError</span>(<span class="string">"This will make our app 💥"</span>)
  }
}
</code></pre><p>Here's our <code>ContentView</code>:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ImpossibleView</span>()
  }
}
</code></pre><p>Unsurprisingly, our app will crash once again, However, the crash reason is <code>Fatal error: ImpossibleView may not have Body == Never: file SwiftUI, line 0</code>, not our <code>This will make our app 💥</code>.</p><p>Reading throughout the stack trace, we will see an <code>assertionFailure</code> within an internal SwiftUI's <code>BodyAccessor.makeBody(container:inputs:fields:)</code> method, which is not happy with our <code>ImpossibleView</code> <code>body</code> type.</p><blockquote><p>This is the same method that will crash our app if we pass a <code>class</code> instance instead of a value type.</p></blockquote><p>Only views declared within SwiftUI are allowed to have body type <code>Never</code>. Despite not having access to <code>BodyAccessor</code>'s code, it's clear that those views would either pass this assertion or that they'd take a different, special path.</p><p>SwiftUI can't keep asking for view bodies forever: it needs a special set of views, a.k.a. a set of <em>primitives</em>, that it can draw without asking for their body. This is why <code>Text</code>, <code>ZStack</code>, <code>Color</code>, etc., have <code>Never</code> as their body type.</p><h2>Is <code>Never</code> a <code>View</code>?</h2><p>A type conforming to <code>View</code> needs to return a body that is also a <code>View</code>: <code>Never</code> is a view.</p><p>SwiftUI knows not to ask for the body of views with <code>body</code> type <code>Never</code>, either by crashing if it's not a primitive or doing something else otherwise. However, since we must make our code compile, SwiftUI needs to extend <code>Never</code> to be a <code>View</code>: the ultimate, impossible view.</p><p>To confirm this, we can inspect SwiftUI's headers, where we will find the following declaration (spread in a few places):</p><pre><code><span class="keyword">extension</span> <span class="type">Never</span>: <span class="type">View</span> {
  <span class="keyword">public typealias</span> Body = <span class="type">Never</span>
  <span class="keyword">public var</span> body: <span class="type">Never</span> { <span class="keyword">get</span> }
}
</code></pre><p>The SwiftUI team could have declared another special type to be used instead of <code>Never</code>. However, I find this solution very elegant and perfectly fitting for the use case.</p><h2>Conclusions</h2><p>In this article, we've explored how SwiftUI breaks the infinite recursion challenge when drawing views and how it uses the special Swift's type <code>Never</code> to achieve that elegantly.</p><p>I hope you've found this article helpful: <a href="https://twitter.com/zntfdr">please let me know</a> if I've missed anything!</p><p>Thank you for reading, and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more SwiftUI articles!</p><h2>Bonus track</h2><p>Since the article is about impossible views, just for fun, I want to share another completely legit and 100% crashing way to <em>"build"</em> views: declare nothing but <strong>any</strong> modifier.</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">border</span>(<span class="type">Color</span>.<span class="property">black</span>)
  }
}

<span class="comment">// or</span>

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">padding</span>()
  }
}

<span class="comment">// or</span>

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">ignoresSafeArea</span>()
  }
}

<span class="comment">// etc</span>
</code></pre><p>So many possibilities! 💣</p><p>Are you aware of any other <em>interesting</em> ways to crash SwiftUI? <a href="https://twitter.com/zntfdr">I'd love to know</a>!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/preferencekey-reduce</guid><title>How SwiftUI's Preference Keys are propagated</title><description>Demystifiying how PreferenceKey's reduce method works</description><link>https://www.fivestars.blog/articles/preferencekey-reduce</link><pubDate>Wed, 9 Sep 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>SwiftUI's <a href="https://developer.apple.com/documentation/swiftui/preferencekey"><code>PreferenceKey</code></a> declaration is as following:</p><pre><code><span class="keyword">public protocol</span> PreferenceKey {
  <span class="keyword">associatedtype</span> Value
  <span class="keyword">static var</span> defaultValue: <span class="type">Self</span>.<span class="type">Value</span> { <span class="keyword">get</span> }
  <span class="keyword">static func</span> reduce(value: <span class="keyword">inout</span> <span class="type">Self</span>.<span class="type">Value</span>, nextValue: () -&gt; <span class="type">Self</span>.<span class="type">Value</span>)
}
</code></pre><p>While it's clear what both <a href="https://developer.apple.com/documentation/swiftui/preferencekey/value"><code>Value</code></a> and <a href="https://developer.apple.com/documentation/swiftui/preferencekey/defaultvalue-11lde"><code>defaultValue</code></a> are and do, the same cannot be said for <a href="https://developer.apple.com/documentation/swiftui/preferencekey/reduce(value:nextvalue:)"><code>reduce(value:nextValue:)</code></a>:<br>let's take a deep dive into this mysterious method.</p><h2>Official definition</h2><p>Here's the current documentation for <code>reduce</code>:</p><pre><code><span class="comment">/// Combines a sequence of values by modifying the previously-accumulated
/// value with the result of a closure that provides the next value.
///
/// This method receives its values in view-tree order. Conceptually, this
/// combines the preference value from one tree with that of its next
/// sibling.
///
/// - Parameters:
///   - value: The value accumulated through previous calls to this method.
///     The implementation should modify this value.
///   - nextValue: A closure that returns the next value in the sequence.</span>
<span class="keyword">static func</span> reduce(value: <span class="keyword">inout</span> <span class="type">Self</span>.<span class="type">Value</span>, nextValue: () -&gt; <span class="type">Self</span>.<span class="type">Value</span>)
</code></pre><p>This definition sets some foundation on <code>reduce</code> core functionality:<br>it's used to compute a view preference key value, only when multiple children modify that key.</p><p>Let's make an example.</p><h2><code>NumericPreferenceKey</code></h2><p>The following is a simple preference definition that holds an integer as its value:</p><pre><code><span class="keyword">struct</span> NumericPreferenceKey: <span class="type">PreferenceKey</span> {
  <span class="keyword">static var</span> defaultValue: <span class="type">Int</span> = <span class="number">0</span>
  <span class="keyword">static func</span> reduce(value: <span class="keyword">inout</span> <span class="type">Int</span>, nextValue: () -&gt; <span class="type">Int</span>) { ... }
}
</code></pre><p>From now on, every view in any view hierarchy has a default value of <code>0</code> for <code>NumericPreferenceKey</code>, regardless of the <code>reduce</code> implementation.</p><h2>When is <code>reduce</code> invoked</h2><p>Imagine a small view hierarchy with one root, two leaves, and nothing in between:</p><pre><code><span class="type">VStack</span> {
  <span class="type">Text</span>(<span class="string">"A"</span>)
  <span class="type">Text</span>(<span class="string">"B"</span>)
}
</code></pre><blockquote><p>For clarity's sake: <code>VStack</code> is the root, while the two <code>Text</code>s are the leaves.</p></blockquote><p>We will use this hierarchy in different scenarios.</p><h3>No child alters/sets the preference key</h3><pre><code><span class="type">VStack</span> {
  <span class="type">Text</span>(<span class="string">"A"</span>)
  <span class="type">Text</span>(<span class="string">"B"</span>)
}
</code></pre><p>As no view sets the <code>NumericPreferenceKey</code> value, all views have a <code>NumericPreferenceKey</code> value of <code>NumericPreferenceKey.defaultValue</code>, which is <code>0</code>, as per our definition.</p><p><code>NumericPreferenceKey.reduce</code> will never be called on the <code>Text</code>s, as no one can pass a value to a leaf.</p><p><code>reduce</code> is also not called on <code>VStack</code>, because its children don't set/pass a <code>NumericPreferenceKey</code> value to their parent.</p><h3>One child alters/sets the preference key</h3><pre><code><span class="type">VStack</span> {
  <span class="type">Text</span>(<span class="string">"A"</span>)
    .<span class="call">preference</span>(key: <span class="type">NumericPreferenceKey</span>.<span class="keyword">self</span>, value: <span class="number">1</span>)
  <span class="type">Text</span>(<span class="string">"B"</span>)
}
</code></pre><p>In this case:</p><ul><li><code>Text("A")</code> sets its <code>NumericPreferenceKey</code> value to <code>1</code> and pass it to its parent</li><li><code>Text("B")</code> defaults <code>NumericPreferenceKey</code> to <code>defaultValue</code>, and not pass anything to its parent</li></ul><p>What about <code>VStack</code>? Let's take a look at the <code>reduce</code> definition once again: <code>Combines a sequence of values by modifying the previously-accumulated value with the result of a closure that provides the next value.</code></p><p>Since only children that have set/changed the <code>NumericPreferenceKey</code> value will pass it to their parents, <code>VStack</code> will only have accumulated one value: <code>1</code> from <code>Text("A")</code>.</p><p>Once again, <code>NumericPreferenceKey.reduce</code> is not called on <code>VStack</code>, and the <code>NumericPreferenceKey</code> value associated to <code>VStack</code> is now <code>1</code>.</p><h3>Multiple children alter/set the preference key</h3><pre><code><span class="type">VStack</span> {
  <span class="type">Text</span>(<span class="string">"A"</span>)
    .<span class="call">preference</span>(key: <span class="type">NumericPreferenceKey</span>.<span class="keyword">self</span>, value: <span class="number">1</span>)
  <span class="type">Text</span>(<span class="string">"B"</span>)
    .<span class="call">preference</span>(key: <span class="type">NumericPreferenceKey</span>.<span class="keyword">self</span>, value: <span class="number">3</span>)
}
</code></pre><p>In this example:</p><ul><li>both <code>Text</code>s set and pass to their parent a <code>NumericPreferenceKey</code> value of <code>1</code> and <code>3</code>, respectively</li><li><code>VStack</code> accumulates two <code>NumericPreferenceKey</code> values</li></ul><p>SwiftUI doesn't know what <code>NumericPreferenceKey</code> value to assign to <code>VStack</code>, as multiple values are proposed from its children:<br>this is where our <code>NumericPreferenceKey.reduce</code> comes to the rescue, helping SwiftUI <em>reduce</em> these multiple values into one, which will be then assigned to our <code>VStack</code>.</p><blockquote><p><code>NumericPreferenceKey.reduce</code> would be called even if all passed values were the same.</p></blockquote><p>So what's the value of <code>VStack</code>? Before answering this, we need to know in what order the values are passed to <code>VStack</code>.</p><h2>Reduce call order</h2><p><code>PreferenceKey</code>'s <code>reduce</code> method always contains two parameters: the current <code>value</code>, and the next value to merge.</p><p>Going back to our example:</p><ol><li><code>VStack</code> first receives the value <code>1</code> from <code>Text("A")</code>. As no other value was previously accumulated, this becomes the current value of <code>VStack</code></li><li>then <code>VStack</code> receives the value <code>3</code> from <code>Text("B")</code>, now SwiftUI needs to combine this value with the current value, therefore calling <code>NumericPreferenceKey.reduce</code> with <code>1</code> as the <code>value</code> parameter, and <code>3</code> as the <code>nextValue</code></li></ol><p>This is what the SwiftUI header meant by <code>This method receives its values in view-tree order.</code>: <code>reduce</code> is always called by traversing our view's children from first to last, in declaration order.</p><p>If our <code>VStack</code> had <code>Text</code>s from <code>"A"</code> to <code>"Z"</code>, all setting their <code>NumericPreferenceKey</code> value, <code>reduce</code> would be called first with the current value, inherited from <code>Text("A")</code> and <code>Text("B")</code>, then with the new current value and <code>Text("C")</code>, etc.</p><p><code>reduce</code> is called <strong>only</strong> between values accumulated within siblings: if a <code>VStack</code> child had its own children, the same concepts would be applied recursively, and then that child would pass to <code>VStack</code> its final value, regardless of how it was obtained.</p><p>It's finally time to compute our <code>VStack</code>'s <code>NumericPreferenceKey</code> value:<br>to do so, we need to take a look at the <code>NumericPreferenceKey.reduce</code> implementation.</p><h2>Common reduce implementations</h2><p>Each preference key declaration has its own <code>reduce</code> implementation:<br>in this section, let's cover some of the most common ones.</p><h3>value = nextValue()</h3><p>The most common definition assigns <code>nextValue()</code> to <code>value</code>, this could also be <code>NumericPreferenceKey</code>'s implementation:</p><pre><code><span class="keyword">struct</span> NumericPreferenceKey: <span class="type">PreferenceKey</span> {
  <span class="keyword">static var</span> defaultValue: <span class="type">Int</span> = <span class="number">0</span>

  <span class="keyword">static func</span> reduce(value: <span class="keyword">inout</span> <span class="type">Int</span>, nextValue: () -&gt; <span class="type">Int</span>) { 
    value = <span class="call">nextValue</span>()
  }
}
</code></pre><p>Let's go back to our example where both <code>Text("A")</code> and <code>Text("B")</code> pass a value, and compute <code>VStack</code>'s <code>NumericPreferenceKey</code>:</p><ul><li>first <code>VStack</code> takes in the value passed by <code>Text("A")</code>, as there was no prior accumulated value, this is the new <code>VStack</code> current value</li><li>then <code>VStack</code> gets the value passed by <code>Text("B")</code>, as we have two values <code>reduce</code> is called, and the new <code>VStack</code> value will be whatever the new proposed value is (that's what <code>value = nextValue()</code> does).</li></ul><p>In other words, with this implementation, when multiple children pass a value, <code>reduce</code> will discard all of them but the last one, which will become the value of our view.</p><h3>Empty implementation</h3><p><a href="https://www.fivestars.blog/articles/swiftui-share-layout-information/">In</a> <a href="https://www.fivestars.blog/articles/flexible-swiftui/">previous</a> <a href="https://www.fivestars.blog/articles/scrollview-offset/">articles</a> we've defined various preference keys with an empty <code>reduce</code> implementation:</p><pre><code><span class="keyword">struct</span> NumericPreferenceKey: <span class="type">PreferenceKey</span> {
  <span class="keyword">static var</span> defaultValue: <span class="type">Int</span> = <span class="number">0</span>

  <span class="keyword">static func</span> reduce(value: <span class="keyword">inout</span> <span class="type">Int</span>, nextValue: () -&gt; <span class="type">Int</span>) { 
  }
}
</code></pre><p>Once again, let's go back to our example and compute <code>VStack</code>'s <code>NumericPreferenceKey</code>:</p><ul><li>first <code>VStack</code> takes in the value passed by <code>Text("A")</code>, as there was no prior accumulated value, this is the new <code>VStack</code> current value</li><li>then <code>VStack</code> gets the value passed by <code>Text("B")</code>, as we have two values <code>reduce</code> is called, and nothing happens, as our <code>reduce</code> does nothing. <code>VStack</code> keeps the current value.</li></ul><p>This implementation is the opposite of the previous one: our view will keep the very first collected value, and ignore the rest.</p><h3>value += nextValue()</h3><p>Other common implementations use <code>reduce</code> to combine all values with some math operators such as sum:</p><pre><code><span class="keyword">struct</span> NumericPreferenceKey: <span class="type">PreferenceKey</span> {
  <span class="keyword">static var</span> defaultValue: <span class="type">Int</span> = <span class="number">0</span>

  <span class="keyword">static func</span> reduce(value: <span class="keyword">inout</span> <span class="type">Int</span>, nextValue: () -&gt; <span class="type">Int</span>) { 
    value += <span class="call">nextValue</span>()
  }
}
</code></pre><p>It should be intuitive by now that, in this case, our view will have as its value the sum of all the values passed by its children.</p><h3>And many more</h3><p>Other implementations worth mentioning are on preference keys whose <code>Value</code> is either an array or a dictionary, and where the <code>reduce</code> method is used to group all the children values together (via <a href="https://developer.apple.com/documentation/swift/array/3126939-append"><code>append(contentsOf:)</code></a> or similar).</p><p>Once we understand the inner workings of preference key, it becomes intuitive to read and understand the effects of <code>reduce</code>.</p><h2><code>PreferenceKey</code> is a function of the current state</h2><p>Like SwiftUI views, preference key values are the outcome of the current state and are not persisted.</p><p>If we look at the <code>value += nextValue()</code> <code>reduce</code> implementation for example, the current view value is the sum of the current passed values: if one children changes the passed value, SwiftUI will re-compute our view preference key value from scratch.</p><p>The same is true for any preference key <code>Value</code>: even in case of arrays or dictionaries. We always start over, nothing is persisted.</p><h2>When is the preference key computed?</h2><p>If the complete view in our app is our <code>VStack</code> example, <code>reduce</code> is actually never called:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">Text</span>(<span class="string">"A"</span>)
        .<span class="call">preference</span>(key: <span class="type">NumericPreferenceKey</span>.<span class="keyword">self</span>, value: <span class="number">1</span>)
      <span class="type">Text</span>(<span class="string">"B"</span>)
        .<span class="call">preference</span>(key: <span class="type">NumericPreferenceKey</span>.<span class="keyword">self</span>, value: <span class="number">3</span>)
    }
  }
}
</code></pre><p>This is true despite <code>VStack</code> having multiple <code>NumericPreferenceKey</code> values passed: did this article lied to us?</p><p>SwiftUI always strives to do as little as possible to present the outcome to the user. No one is reading or using the preference key in this example; therefore, SwiftUI will ignore it.</p><p>All our keys are there and present in their proper place in the view hierarchy. They're just not used. Therefore SwiftUI won't spend any time resolving them.</p><p>If we want to see <code>reduce</code> getting called, we need to somehow read <code>NumericPreferenceKey</code>, one way is to add an <code>onPreferenceChange(_:perform:)</code> function in our <code>VStack</code>:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">Text</span>(<span class="string">"A"</span>)
        .<span class="call">preference</span>(key: <span class="type">NumericPreferenceKey</span>.<span class="keyword">self</span>, value: <span class="number">1</span>)
      <span class="type">Text</span>(<span class="string">"B"</span>)
        .<span class="call">preference</span>(key: <span class="type">NumericPreferenceKey</span>.<span class="keyword">self</span>, value: <span class="number">3</span>)
    }
    .<span class="call">onPreferenceChange</span>(<span class="type">NumericPreferenceKey</span>.<span class="keyword">self</span>) { value <span class="keyword">in</span>
      <span class="call">print</span>(<span class="string">"VStack's NumericPreferenceKey value is now:</span> \(value)<span class="string">"</span>)
    }
  }
}
</code></pre><p><code>onPreferenceChange(_:perform:)</code> tells SwiftUI that we're interested in knowing <code>VStack</code>'s <code>VStackNumericPreferenceKey</code> value and when it changes: this is all we need to set up to see our <code>reduce</code> method getting called.</p><h2>Why is <code>reduce</code>'s <code>nextValue</code> a function?</h2><pre><code><span class="keyword">public protocol</span> PreferenceKey {
  <span class="keyword">associatedtype</span> Value
  <span class="keyword">static var</span> defaultValue: <span class="type">Self</span>.<span class="type">Value</span> { <span class="keyword">get</span> }
  <span class="keyword">static func</span> reduce(value: <span class="keyword">inout</span> <span class="type">Self</span>.<span class="type">Value</span>, nextValue: () -&gt; <span class="type">Self</span>.<span class="type">Value</span>)
}
</code></pre><p>Something that probably comes out as perplexing when reading <code>PreferenceKey</code>'s definition is that <code>reduce</code>'s arguments are one value and a function: we're combining two values. Why doesn't SwiftUI give us two values?</p><p>The answer is SwiftUI's laziness.</p><p>Let's take our previous <code>reduce</code> empty implementation and use it in a slightly more complicated example:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">Text</span>(<span class="string">"A"</span>)
        .<span class="call">preference</span>(key: <span class="type">NumericPreferenceKey</span>.<span class="keyword">self</span>, value: <span class="number">1</span>)

      <span class="type">VStack</span> {
        <span class="type">Text</span>(<span class="string">"X"</span>)
          .<span class="call">preference</span>(key: <span class="type">NumericPreferenceKey</span>.<span class="keyword">self</span>, value: <span class="number">5</span>)
        <span class="type">Text</span>(<span class="string">"Y"</span>)
          .<span class="call">preference</span>(key: <span class="type">NumericPreferenceKey</span>.<span class="keyword">self</span>, value: <span class="number">6</span>)
      }
    }.<span class="call">onPreferenceChange</span>(<span class="type">NumericPreferenceKey</span>.<span class="keyword">self</span>) { value <span class="keyword">in</span>
      <span class="call">print</span>(<span class="string">"VStack's NumericPreferenceKey value is now:</span> \(value)<span class="string">"</span>)
    }
  }
}

<span class="keyword">struct</span> NumericPreferenceKey: <span class="type">PreferenceKey</span> {
  <span class="keyword">static var</span> defaultValue: <span class="type">Int</span> = <span class="number">0</span>
  <span class="keyword">static func</span> reduce(value: <span class="keyword">inout</span> <span class="type">Int</span>, nextValue: () -&gt; <span class="type">Int</span>) { 
  }
}
</code></pre><p>Here we have a <code>VStack</code> as our root, this <code>VStack</code> contains two children: a <code>Text("A")</code> and another <code>VStack</code>, which, in turn, has two <code>Text</code>s as children.</p><p>All <code>Text</code>s in the view set their <code>NumericPreferenceKey</code> value, and we call <code>onPreferenceChange(_:perform:)</code> on our root.</p><p>Let's compute the root <code>NumericPreferenceKey</code> value:</p><ul><li>first <code>VStack</code> receives the value passed by <code>Text("A")</code>, as there was no prior accumulated value, this is the new <code>VStack</code> current value</li><li>then it receives another value from its other child, the inner <code>VStack</code>, and our <code>reduce</code> method gets called</li></ul><p>In this example, <code>reduce</code> does nothing. We don't need to know what the exact value passed by our inner <code>VStack</code> is.</p><p>Since we do not access to <code>nextValue</code>, SwiftUI won't compute it.</p><p>This means that the inner <code>VStack</code> preference key is not computed at all, as no one reads it, therefore our <code>reduce</code> is called just once, to resolve the root <code>VStack</code> preference key only.</p><p>And this is why <code>reduce</code> takes in a value and a method: the <code>nextValue()</code> method is a way for SwiftUI to check if that value is needed, and if it's not, it won't resolve it.</p><p>SwiftUI needs to resolve the whole view hierarchy as quickly and as efficiently as possible, this is yet another optimization.</p><h2>Conclusions</h2><p>SwiftUI's <code>PreferenceKey</code> is one of those <em>behind the scenes</em> tools that are not very popular but yet are indispensable to <a href="https://www.fivestars.blog/articles/swiftui-share-layout-information/">obtain</a> <a href="https://www.fivestars.blog/articles/flexible-swiftui/">certain</a> <a href="https://www.fivestars.blog/articles/scrollview-offset/">results</a>:<br>in this article, we explored <code>PreferenceKey</code>'s inner workings and revealed how its <a href="https://developer.apple.com/documentation/swiftui/preferencekey/reduce(value:nextvalue:)"><code>reduce</code></a> method is used and what it is for, discovering even more SwiftUI efficiency.</p><p>All we've seen today is very much undocumented: please <a href="https://twitter.com/zntfdr">let me know</a> if I've missed anything!</p><p>Thank you for reading, and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more SwiftUI articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/conditional-modifiers</guid><title>Conditional view modifiers</title><description>How and when to apply different modifiers based on conditions.</description><link>https://www.fivestars.blog/articles/conditional-modifiers</link><pubDate>Tue, 1 Sep 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>When working with SwiftUI views, sometimes we would like to apply different modifiers based on conditions/states:</p><pre><code><span class="keyword">var</span> body: <span class="keyword">some</span> view {
  myView
    <span class="comment">// if X .padding(8)
    // if Y .background(Color.blue)</span>
}
</code></pre><p>For most cases, SwiftUI offers inert modifiers, where we can pass a different <em>argument value</em> based on the condition:</p><pre><code><span class="keyword">var</span> body: <span class="keyword">some</span> view {
  myView
    .<span class="call">padding</span>(<span class="type">X</span> ? <span class="number">8</span> : <span class="number">0</span>)
    .<span class="call">background</span>(<span class="type">Y</span> ? <span class="type">Color</span>.<span class="property">blue</span> : <span class="type">Color</span>.<span class="property">clear</span>)
}
</code></pre><p>As covered at WWDC21's session <a href="https://www.wwdcnotes.com/notes/wwdc21/10022/">Demystify SwiftUI</a>, this is the recommended way, and we should strive to use this approach as much as possible.<br><br>However, there are other modifiers, when applying styles for example, where this solution doesn't work:<br>in this article, let's explore how we can take care of such cases.</p><h2>if view extension</h2><p>The most common solution is to define a new <code>if</code> <code>View</code> extension:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">@ViewBuilder
  func</span> `if`&lt;<span class="type">Transform</span>: <span class="type">View</span>&gt;(
    <span class="keyword">_</span> condition: <span class="type">Bool</span>, 
    transform: (<span class="type">Self</span>) -&gt; <span class="type">Transform</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">if</span> condition {
      <span class="call">transform</span>(<span class="keyword">self</span>)
    } <span class="keyword">else</span> {
      <span class="keyword">self</span>
    }
  }
}
</code></pre><p>This function will apply <code>transform</code> to our view when <code>condition</code> is <code>true</code>. Otherwise, it will leave the original view untouched.</p><p>Going back to our style example, this is one way to use it:</p><pre><code><span class="keyword">var</span> body: <span class="keyword">some</span> view {
  myView
    .<span class="call">if</span>(<span class="type">X</span>) { $0.<span class="call">buttonStyle</span>(.<span class="dotAccess">bordered</span>) }
    .<span class="call">if</span>(<span class="type">Y</span>) { $0.<span class="call">buttonStyle</span>(.<span class="dotAccess">borderedProminent</span>) }
}
</code></pre><h2>If-else view extension</h2><p>Depending on how compact we want our declarations to be, applying different modifiers based on the <code>condition</code> <code>true</code>/<code>false</code> value would cost us at least two modifiers:</p><pre><code><span class="keyword">var</span> body: <span class="keyword">some</span> view {
  myView
    .<span class="call">if</span>(<span class="type">X</span>) { $0.<span class="call">buttonStyle</span>(.<span class="dotAccess">bordered</span>) }
    .<span class="call">if</span>(!<span class="type">X</span>) { $0.<span class="call">buttonStyle</span>(.<span class="dotAccess">borderedProminent</span>) }
}
</code></pre><p>This is clear and already succinct, however, if we really want to go all-in with <code>View</code> extensions, we can define a new <code>if</code> overload that lets us modify the <code>else</code> branch as well:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">@ViewBuilder
  func</span> `if`&lt;<span class="type">TrueContent</span>: <span class="type">View</span>, <span class="type">FalseContent</span>: <span class="type">View</span>&gt;(
    <span class="keyword">_</span> condition: <span class="type">Bool</span>, 
    if ifTransform: (<span class="type">Self</span>) -&gt; <span class="type">TrueContent</span>, 
    else elseTransform: (<span class="type">Self</span>) -&gt; <span class="type">FalseContent</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">if</span> condition {
      <span class="call">ifTransform</span>(<span class="keyword">self</span>)
    } <span class="keyword">else</span> {
      <span class="call">elseTransform</span>(<span class="keyword">self</span>)
    }
  }
}
</code></pre><p>Which will make our example use a single modifier:</p><pre><code><span class="keyword">var</span> body: <span class="keyword">some</span> view {
  myView
    .<span class="call">if</span>(<span class="type">X</span>) { $0.<span class="call">buttonStyle</span>(.<span class="dotAccess">bordered</span>) } else: { $0.<span class="call">buttonStyle</span>(.<span class="dotAccess">borderedProminent</span>) }
}
</code></pre><h2>If-let view extension</h2><p>Sometimes we want to apply a modifier only when another value is not <code>nil</code>, and then use that value in the modifier itself.</p><p>In this case we can define a new <code>View</code> extension that lets us do just that:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">@ViewBuilder
  func</span> ifLet&lt;V, Transform: <span class="type">View</span>&gt;(
    <span class="keyword">_</span> value: <span class="type">V</span>?, 
    transform: (<span class="type">Self</span>, <span class="type">V</span>) -&gt; <span class="type">Transform</span>
  ) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">if let</span> value = value {
      <span class="call">transform</span>(<span class="keyword">self</span>, value)
    } <span class="keyword">else</span> {
      <span class="keyword">self</span>
    }
  }
}
</code></pre><p>The difference from before is that this new function:</p><ul><li>takes in an optional generic <code>value</code> <code>V</code> instead of a <code>Bool</code> condition</li><li>passes this generic <code>value</code> <code>V</code> as a parameter of the transform function</li></ul><p>Here's an example where a <code>View</code> applies a foreground color only when <code>optionalColor</code> is set:</p><pre><code><span class="keyword">var</span> body: <span class="keyword">some</span> view {
  myView
    .<span class="call">ifLet</span>(optionalStyle) { $0.<span class="call">buttonStyle</span>($1) }
}
</code></pre><h2>A word of warning</h2><p>While these conditional modifiers enable us to apply different things to our views based on conditions, it is very important to understand what happens when the condition changes at run time.</p><p>As the view identity changes between the condition branches, switching between them means:</p><ul><li>the current view is destroyed, and another one from the other condition branch is created</li><li>if any view where the conditional modifier is applied holds a state, this state is also destroyed and will be reset to its initial value</li><li>the complete view body is re-evaluated and the view will need to be re-redrawn</li><li>no animations</li></ul><p>It would be fairly simple for the SwiftUI team to add such extensions in the library. However, third-party developers would probably use them without realizing the consequences, and we'd quickly find ourselves in some performance pitfalls, with only the framework to blame.</p><p>Where is it ok to use these conditional modifiers then? Whenever the condition is static.<br>Continuing with our style modifier example, we might have a <em>template</em> view that sets a different style based on the user flow. The style won't change at run time during the flow, and the only way for that style to change is to start a new, different flow.</p><h2>Conclusions</h2><p>SwiftUI declarative APIs make <code>View</code>s definition a breeze. When our views need to apply different modifiers based on certain conditions, we should try to use the inert modifier that SwiftUI offers as much as possible.</p><p>If we can't use those, we can define and use our conditional view modifiers, letting us keep the same declarativeness that we're accustomed to.</p><p>Again, these conditional modifiers work great when the condition value is static. However, they come with a high cost if we need to change them at run time: if you find yourself in this situation, please <a href="https://feedbackassistant.apple.com">file a feedback</a> with your use case.</p><p>Thank you for reading, and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more SwiftUI articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/scrollview-offset</guid><title>SwiftUI ScrollView offset</title><description>How to get the scroll view offeset in SwiftUI</description><link>https://www.fivestars.blog/articles/scrollview-offset</link><pubDate>Tue, 25 Aug 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Searching online for "<em>SwiftUI ScrollView offset</em>" yields to plenty of discussions on how to control the scroll position of a <code>ScrollView</code>:<br>with iOS 14 SwiftUI has gained <a href="https://developer.apple.com/documentation/swiftui/scrollviewreader"><code>ScrollViewReader</code></a>, covered <a href="https://www.fivestars.blog/articles/section-title-index-swiftui/">here</a>, which makes the old <a href="https://github.com/siteline/SwiftUI-Introspect">introspection</a> tricks obsolete.</p><p>Does it mean that we no longer need the <code>ScrollView</code> offset?<br>In this article, let's explore how to obtain the offset and some of its uses.</p><h2>ScrollView offset 101</h2><p>Similarly to <code>UIScrollView</code>, <code>ScrollView</code> is composed of two layers:</p><ul><li>the frame layer, used to position the <code>ScrollView</code> itself in the view hierarchy</li><li>the content layer, where all the <code>ScrollView</code> content is placed</li></ul><p>If we look at a vertical scroll view, which we will use in this article, the offset represents the <em>gap</em> between the smallest value for the y-coordinate of the frame layer with the smallest value for the y-coordinate of the content layer.</p><h2>Getting the offset</h2><p>This is SwiftUI's <code>ScrollView</code> initializer definition:</p><pre><code><span class="keyword">public struct</span> ScrollView&lt;Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">public init</span>(
    <span class="keyword">_</span> axes: <span class="type">Axis</span>.<span class="type">Set</span> = .<span class="dotAccess">vertical</span>, 
    showsIndicators: <span class="type">Bool</span> = <span class="keyword">true</span>, 
    <span class="keyword">@ViewBuilder</span> content: () -&gt; <span class="type">Content</span>
  )
}
</code></pre><p>Besides a <code>content</code> view builder, there's not much we can play with. Let's create a simple <code>ScrollView</code> with a few <code>Text</code>s in it:</p><pre><code><span class="type">ScrollView</span> {
  <span class="type">Text</span>(<span class="string">"A"</span>)
  <span class="type">Text</span>(<span class="string">"B"</span>)
  <span class="type">Text</span>(<span class="string">"C"</span>)
}
</code></pre><p>The offset will be the same as the offset of the first element in the content, <code>Text("A")</code>:<br>how do we get this element offset?</p><p>Once again, we need to go back to SwiftUI's <a href="https://en.wikipedia.org/wiki/Swiss_Army_knife">Swiss army knife</a>, a.k.a. <code>GeometryReader</code>, along with a new <code>PreferenceKey</code>.</p><p>First, let's define the preference key:</p><pre><code><span class="comment">/// Contains the gap between the smallest value for the y-coordinate of 
/// the frame layer and the content layer.</span>
<span class="keyword">private struct</span> OffsetPreferenceKey: <span class="type">PreferenceKey</span> {
  <span class="keyword">static var</span> defaultValue: <span class="type">CGFloat</span> = .<span class="dotAccess">zero</span>
  <span class="keyword">static func</span> reduce(value: <span class="keyword">inout</span> <span class="type">CGFloat</span>, nextValue: () -&gt; <span class="type">CGFloat</span>) {}
}
</code></pre><p>Second, let's add our geometry reader as the background of our element of interest:</p><pre><code><span class="type">ScrollView</span> {
  <span class="type">Text</span>(<span class="string">"A"</span>)
    .<span class="call">background</span> {
      <span class="type">GeometryReader</span> { proxy <span class="keyword">in</span>
        <span class="type">Color</span>.<span class="property">clear</span>
          .<span class="call">preference</span>(
            key: <span class="type">OffsetPreferenceKey</span>.<span class="keyword">self</span>,
            value: proxy.<span class="call">frame</span>(in: .<span class="dotAccess">local</span>).<span class="property">minY</span>
          )
      }
    }
  <span class="type">Text</span>(<span class="string">"B"</span>)
  <span class="type">Text</span>(<span class="string">"C"</span>)
}
</code></pre><p>The geometry reader, as we've seen in <a href="https://www.fivestars.blog/articles/swiftui-share-layout-information/"><code>Sharing layout information in SwiftUI</code></a>, is used to share information of our element in the view hierarchy:<br>we're using it to extract the smallest value for the y-coordinate of our view, which matches our offset definition.</p><p>This is great. However, it doesn't work:<br>we're querying <code>GeometryProxy</code> for a frame in the local coordinate space, which is the space proposed to our background view.</p><p>In short, the <code>minY</code> of <code>Color.clear</code> is, and always be, zero in the local coordinates.</p><p>Asking for the frame in the <code>.global</code> coordinate space, which is the space from the device screen point of view, is a non-starter: our <code>ScrollView</code>s can be placed anywhere in the view hierarchy, the <code>.global</code> coordinate space won't help us here.</p><p>What happens if we put the <code>GeometryReader</code> just above <code>Text("A")</code>?</p><pre><code><span class="type">ScrollView</span> {
  <span class="type">GeometryReader</span> { proxy <span class="keyword">in</span>
    <span class="type">Color</span>.<span class="property">clear</span>
      .<span class="call">preference</span>(
        key: <span class="type">OffsetPreferenceKey</span>.<span class="keyword">self</span>,
        value: proxy.<span class="call">frame</span>(in: .<span class="dotAccess">local</span>).<span class="property">minY</span>
      )
  }
  <span class="type">Text</span>(<span class="string">"A"</span>)
  <span class="type">Text</span>(<span class="string">"B"</span>)
  <span class="type">Text</span>(<span class="string">"C"</span>)
}
</code></pre><p>This might seem to be more promising. However, it still wouldn't work:<br>in this case, the <code>.local</code> coordinate space is the <code>ScrollView</code>'s <em>content layer</em>. Instead, we need the frame according to our <code>ScrollView</code>'s <em>frame layer</em>.</p><p>In order to get the <code>GeometryProxy</code> frame according to our <code>ScrollView</code>'s <em>frame layer</em>, we need to define a new coordinate space on the <code>ScrollView</code>, and refer to that within our <code>GeometryReader</code>:</p><pre><code><span class="type">ScrollView</span> {
  <span class="type">Text</span>(<span class="string">"A"</span>)
    .<span class="call">background</span>(
      <span class="type">GeometryReader</span> { proxy <span class="keyword">in</span>
        <span class="type">Color</span>.<span class="property">clear</span>
          .<span class="call">preference</span>(
            key: <span class="type">OffsetPreferenceKey</span>.<span class="keyword">self</span>,
            value: proxy.<span class="call">frame</span>(in: .<span class="call">named</span>(<span class="string">"frameLayer"</span>)).<span class="property">minY</span> <span class="comment">// 👈🏻</span> 
          )
      }
    )
  <span class="type">Text</span>(<span class="string">"B"</span>)
  <span class="type">Text</span>(<span class="string">"C"</span>)
}
.<span class="call">coordinateSpace</span>(name: <span class="string">"frameLayer"</span>) <span class="comment">// 👈🏻</span> 
</code></pre><p>This works because <code>ScrollView</code> exposes the <em>frame layer</em> from the outside, great! Now the correct <code>ScrollView</code> offset is available in the view hierarchy.</p><h3>Creating a <code>ScrollViewOffset</code> View</h3><p>Before proceeding, it would be great if we could generalize this approach to work with any content, making it easy to get the offset when needed.</p><p>There's a challenge: <code>ScrollView</code> takes in a <code>content</code> view builder, which makes it impossible from our side to get the first element of that content.</p><p>We could apply a <code>.background</code> modifier to the whole <code>content</code>. However, this doesn't take into account the possibility that the content itself could be a <code>Group</code>:<br>in that case, the modifier would be applied to each group element, which is not what we wanted.</p><p>A solution is moving the geometry reader above the <code>ScrollView</code>'s content, and then hide it with a negative padding on the actual content:</p><pre><code><span class="keyword">struct</span> ScrollViewOffset&lt;Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">let</span> content: () -&gt; <span class="type">Content</span>

  <span class="keyword">init</span>(<span class="keyword">@ViewBuilder</span> content: <span class="keyword">@escaping</span> () -&gt; <span class="type">Content</span>) {
    <span class="keyword">self</span>.<span class="property">content</span> = content
  }

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollView</span> {
      offsetReader
      <span class="call">content</span>()
        .<span class="call">padding</span>(.<span class="dotAccess">top</span>, -<span class="number">8</span>) <span class="comment">// 👈🏻 places the real content as if our `offsetReader` was not there.</span>
    }
    .<span class="call">coordinateSpace</span>(name: <span class="string">"frameLayer"</span>)
  }

  <span class="keyword">var</span> offsetReader: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">GeometryReader</span> { proxy <span class="keyword">in</span>
      <span class="type">Color</span>.<span class="property">clear</span>
        .<span class="call">preference</span>(
          key: <span class="type">OffsetPreferenceKey</span>.<span class="keyword">self</span>,
          value: proxy.<span class="call">frame</span>(in: .<span class="call">named</span>(<span class="string">"frameLayer"</span>)).<span class="property">minY</span>
        )
    }
    .<span class="call">frame</span>(height: <span class="number">0</span>) <span class="comment">// 👈🏻 make sure that the reader doesn't affect the content height</span>
  }
}
</code></pre><p>Similarly to our <a href="https://www.fivestars.blog/articles/swiftui-share-layout-information/"><code>readSize</code> modifier</a>, we can make <code>ScrollViewOffset</code> ask for a callback to be called every time the offset is changed as well:</p><pre><code><span class="keyword">struct</span> ScrollViewOffset&lt;Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">let</span> content: () -&gt; <span class="type">Content</span>
  <span class="keyword">let</span> onOffsetChange: (<span class="type">CGFloat</span>) -&gt; <span class="type">Void</span>

  <span class="keyword">init</span>(
    <span class="keyword">@ViewBuilder</span> content: <span class="keyword">@escaping</span> () -&gt; <span class="type">Content</span>,
    onOffsetChange: <span class="keyword">@escaping</span> (<span class="type">CGFloat</span>) -&gt; <span class="type">Void</span>
  ) {
    <span class="keyword">self</span>.<span class="property">content</span> = content
    <span class="keyword">self</span>.<span class="property">onOffsetChange</span> = onOffsetChange
  }

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollView</span> {
      ...
    }
    .<span class="call">coordinateSpace</span>(name: <span class="string">"frameLayer"</span>)
    .<span class="call">onPreferenceChange</span>(<span class="type">OffsetPreferenceKey</span>.<span class="keyword">self</span>, perform: onOffsetChange)
  }

  <span class="keyword">var</span> offsetReader: <span class="keyword">some</span> <span class="type">View</span> {
    ...
  }
}
</code></pre><p>Going back to our example, this is how this new view can be used:</p><pre><code><span class="type">ScrollViewOffset</span> {
  <span class="type">Text</span>(<span class="string">"A"</span>)
  <span class="type">Text</span>(<span class="string">"B"</span>)
  <span class="type">Text</span>(<span class="string">"C"</span>)
} onOffsetChange: { offset <span class="keyword">in</span>
  <span class="call">print</span>(<span class="string">"New ScrollView offset:</span> \(offset)<span class="string">"</span>) 
}
</code></pre><p>The final gist can be found <a href="https://github.com/zntfdr/FiveStarsCodeSamples/tree/main/ScrollView-Offset">here</a>.</p><h2>Uses</h2><p>Now that we have this new powerful information, it's really up to us what to do with it.</p><p>Probably the most common use is around changing the color of the top safe area when scrolling:</p><img src="https://www.fivestars.blog/assets/posts/scrollview-offset/status.gif"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> scrollOffset: <span class="type">CGFloat</span> = .<span class="dotAccess">zero</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ZStack</span> {
      scrollView
      statusBarView
    }
  }

  <span class="keyword">var</span> scrollView: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollViewOffset</span> {
      <span class="type">LazyVStack</span> {
        <span class="type">ForEach</span>(<span class="number">0</span>..&lt;<span class="number">100</span>) { index <span class="keyword">in</span>
          <span class="type">Text</span>(<span class="string">"</span>\(index)<span class="string">"</span>)
        }
      }
    } onOffsetChange: {
      scrollOffset = $0
    }
  }

  <span class="keyword">var</span> statusBarView: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">GeometryReader</span> { geometry <span class="keyword">in</span>
      <span class="type">Color</span>.<span class="property">red</span>
        .<span class="call">opacity</span>(opacity)
        .<span class="call">frame</span>(height: geometry.<span class="property">safeAreaInsets</span>.<span class="property">top</span>, alignment: .<span class="dotAccess">top</span>)
        .<span class="call">edgesIgnoringSafeArea</span>(.<span class="dotAccess">top</span>)
    }
  }

  <span class="keyword">var</span> opacity: <span class="type">Double</span> {
    <span class="keyword">switch</span> scrollOffset {
    <span class="keyword">case</span> -<span class="number">100</span>...<span class="number">0</span>:
      <span class="keyword">return</span> <span class="type">Double</span>(-scrollOffset) / <span class="number">100.0</span>
    <span class="keyword">case</span> ...(-<span class="number">100</span>):
      <span class="keyword">return</span> <span class="number">1</span>
    <span class="keyword">default</span>:
      <span class="keyword">return</span> <span class="number">0</span>
    }
  }
}
</code></pre><p>The limit is our imagination, here's a view that changes the background color based on the scroll position:</p><img src="https://www.fivestars.blog/assets/posts/scrollview-offset/rainbow.gif"/><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@State var</span> scrollOffset: <span class="type">CGFloat</span> = .<span class="dotAccess">zero</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ZStack</span> {
      backgroundColor
      scrollView
    }
  }

  <span class="keyword">var</span> backgroundColor: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Color</span>(
      <span class="comment">//         This number determines how fast the color changes 👇🏻</span>
      hue: <span class="type">Double</span>(<span class="call">abs</span>(scrollOffset.<span class="call">truncatingRemainder</span>(dividingBy: <span class="number">3500</span>))) / <span class="number">3500</span>,
      saturation: <span class="number">1</span>,
      brightness: <span class="number">1</span>
    )
    .<span class="call">ignoresSafeArea</span>()
  }

  <span class="keyword">var</span> scrollView: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollViewOffset</span> {
      <span class="type">LazyVStack</span>(spacing: <span class="number">8</span>) {
        <span class="type">ForEach</span>(<span class="number">0</span>..&lt;<span class="number">100</span>) { index <span class="keyword">in</span>
          <span class="type">Text</span>(<span class="string">"</span>\(index)<span class="string">"</span>)
            .<span class="call">font</span>(.<span class="dotAccess">title</span>)
        }
      }
    } onOffsetChange: {
      scrollOffset = $0
    }
  }
}
</code></pre><h3>iOS 13 vs iOS 14</h3><p>All we've seen above works great on iOS 14. However, in iOS 13, the initial offset is different.</p><p>In iOS 13, the offset considers the top safe area:<br>for example, the initial offset of a <code>ScrollViewOffset</code> embedded in a <code>NavigationView</code> with a large title is <code>140</code> points. The same view in iOS 14 will have the initial (correct) offset value of <code>0</code> points.</p><p>You've been warned!</p><h2>Conclusions</h2><p>Thanks to <code>ScrollViewReader</code>, we no longer need to access the <code>ScrollView</code> offset for most of the use cases: for the rest, <code>GeometryReader</code> has our back.</p><p>Do you have any other use for the scroll View offset? <a href="https://twitter.com/zntfdr">Let me know</a>!</p><p>Thank you for reading, and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more SwiftUI articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/flexible-swiftui</guid><title>Flexible layouts in SwiftUI</title><description>A journey on how to build a wrapping view in SwiftUI</description><link>https://www.fivestars.blog/articles/flexible-swiftui</link><pubDate>Tue, 18 Aug 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<blockquote><p>Article inspired by <a href="https://twitter.com/MauricioM">Mauricio Meirelles</a>'s <a href="https://github.com/mauriciomeirelles/GridView">GridView</a>.</p></blockquote><p>This year SwiftUI has learned grid layouts thanks to the new <a href="https://developer.apple.com/documentation/swiftui/lazyvgrid"><code>LazyVGrid</code></a> and <a href="https://developer.apple.com/documentation/swiftui/lazyhgrid"><code>LazyHGrid</code></a>.</p><p>While these new components unlock very powerful layouts, SwiftUI doesn't offer the same flexibility as <code>UICollectionView</code> just yet.</p><p>I'm referring to the possibility of having multiple views of different sizes in the same container, and make the container automatically wrap to the next row when there's no more space available.</p><p>Let's wait for another year? In this article, let's explore how we can build our <code>FlexibleView</code>. Here's a sneak peek of the final result:</p><img src="https://www.fivestars.blog/assets/posts/flexible-swiftui/flexible.gif"/><h2>Introduction</h2><p>From the preview above, it should be clear what we're aiming for. Let's see what our view needs to know to achieve that:</p><ol><li>the total horizontal space available</li><li>the size of each element</li><li>a way to distribute each element into the right place</li></ol><p>Time to get started!</p><h2>Getting the size of a view</h2><p>The first two points come down to getting the size of a view: <em>coincidentally</em>, last week's article, <a href="https://www.fivestars.blog/articles/swiftui-share-layout-information/">Sharing layout information in SwiftUI</a>, covers how to do so. If you haven't already, please <a href="https://www.fivestars.blog/articles/swiftui-share-layout-information/">have a read</a>.</p><p>This article will use <a href="https://gist.github.com/zntfdr/f735039b13265d7b25099f524e2116d9">the extension</a> from that article:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">func</span> readSize(onChange: <span class="keyword">@escaping</span> (<span class="type">CGSize</span>) -&gt; <span class="type">Void</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">background</span>(
      <span class="type">GeometryReader</span> { geometryProxy <span class="keyword">in</span>
        <span class="type">Color</span>.<span class="property">clear</span>
          .<span class="call">preference</span>(key: <span class="type">SizePreferenceKey</span>.<span class="keyword">self</span>, value: geometryProxy.<span class="property">size</span>)
      }
    )
    .<span class="call">onPreferenceChange</span>(<span class="type">SizePreferenceKey</span>.<span class="keyword">self</span>, perform: onChange)
  }
}

<span class="keyword">private struct</span> SizePreferenceKey: <span class="type">PreferenceKey</span> {
  <span class="keyword">static var</span> defaultValue: <span class="type">CGSize</span> = .<span class="dotAccess">zero</span>
  <span class="keyword">static func</span> reduce(value: <span class="keyword">inout</span> <span class="type">CGSize</span>, nextValue: () -&gt; <span class="type">CGSize</span>) {}
}
</code></pre><blockquote><p>For an in-depth explanation, refer to <a href="https://www.fivestars.blog/articles/swiftui-share-layout-information/">Sharing layout information in SwiftUI</a>.</p></blockquote><h2>1. Getting the available horizontal space</h2><p>The first piece of information <code>FlexibleView</code> needs is the total horizontal available space:<br>to get it, we will use one view that fills all the space offered while ensuring that its height doesn't exceed what our <code>FlexibleView</code> needs.</p><p>A few examples of such views are <code>Color</code> and <code>Rectangle</code>:</p><pre><code><span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
  <span class="type">Color</span>.<span class="property">clear</span>
    .<span class="call">frame</span>(height: <span class="number">1</span>)
    .<span class="call">readSize</span> { size <span class="keyword">in</span>
      <span class="comment">// the horizontal available space is size.width</span>
    }
}
</code></pre><p>Since this first component is used only to get layout information, we use <code>Color.clear</code> as, effectively, it's an invisible layer that doesn't obstruct the rest of the view.</p><p>We also set a <code>.frame</code> modifier to limit the <code>Color</code> height to <code>1</code> point, ensuring that our view will take as much height as the rest of the view components need.</p><p>This <code>Color</code> is not really part of our view hierarchy, we can hide it with a <code>ZStack</code>:</p><pre><code><span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
  <span class="type">ZStack</span> {
    <span class="type">Color</span>.<span class="property">clear</span>
      .<span class="call">frame</span>(height: <span class="number">1</span>)
      .<span class="call">readSize</span> { size <span class="keyword">in</span>
        <span class="comment">// the horizontal available space is size.width</span>
      }

    <span class="comment">// Rest of our implementation</span>
  }
}
</code></pre><p>Lastly, let's take advantage of the callback from <code>readSize</code> to store our available horizontal space in <code>FlexibleView</code>:</p><pre><code><span class="keyword">struct</span> FlexibleView: <span class="type">View</span> {
  <span class="keyword">@State private var</span> availableWidth: <span class="type">CGFloat</span> = <span class="number">0</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ZStack</span> {
      <span class="type">Color</span>.<span class="property">clear</span>
        .<span class="call">frame</span>(height: <span class="number">1</span>)
        .<span class="call">readSize</span> { size <span class="keyword">in</span>
          availableWidth = size.<span class="property">width</span>
        }

      <span class="comment">// Rest of our implementation</span>
    }
  }
}
</code></pre><p>Great! We have a view that fills all the available horizontal space and only takes one point in height. We can move to the second step.</p><h2>2. Getting each element size</h2><p>Before talking about how to get each element size, let's set up our view to accept elements.</p><p>For simplicity's sake, and for reasons that will become clear later, we will ask for:</p><ol><li>a <code>Collection</code> of <code>Hashable</code> elements</li><li>a method that, given an element of that collection, returns a <code>View</code>.</li></ol><pre><code><span class="keyword">struct</span> FlexibleView&lt;Data: <span class="type">Collection</span>, Content: <span class="type">View</span>&gt;: <span class="type">View</span> 
  <span class="keyword">where</span> <span class="type">Data</span>.<span class="type">Element</span>: <span class="type">Hashable</span> {
  <span class="keyword">let</span> data: <span class="type">Data</span>
  <span class="keyword">let</span> content: (<span class="type">Data</span>.<span class="type">Element</span>) -&gt; <span class="type">Content</span>

  <span class="comment">// ...</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="comment">// ...</span>
  }
}
</code></pre><p>Let's forget about the final layout and focus only on getting each element size:<br>- since our elements conform to <code>Hashable</code>, we can define a new dictionary that holds the size of each element view - then we can use a <code>ForEach</code> to layout all elements and read their view sizes via the <code>.readSize</code> extension</p><pre><code><span class="keyword">struct</span> FlexibleView&lt;...&gt;: <span class="type">View</span> <span class="keyword">where</span> ... {
  <span class="keyword">let</span> data: <span class="type">Data</span>
  <span class="keyword">let</span> content: (<span class="type">Data</span>.<span class="type">Element</span>) -&gt; <span class="type">Content</span>
  <span class="keyword">@State private var</span> elementsSize: [<span class="type">Data</span>.<span class="type">Element</span>: <span class="type">CGSize</span>] = [:]

  <span class="comment">// ...</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ZStack</span> {
      <span class="comment">// ...</span>

      <span class="type">ForEach</span>(data, id: \.<span class="keyword">self</span>) { element <span class="keyword">in</span>
        <span class="call">content</span>(element)
          .<span class="call">fixedSize</span>()
          .<span class="call">readSize</span> { size <span class="keyword">in</span>
            elementsSize[element] = size
          }
      }
    }
  }
}
</code></pre><p>Note how we use the <a href="https://developer.apple.com/documentation/swiftui/button/fixedsize()"><code>.fixedSize</code></a> modifier on the element view, to let it take as much space as needed, regardless of how much space is available.</p><p>And with this, we now have each element size! Time to face the last step.</p><h2>3. A way to distribute each element into the right place</h2><p>Before proceeding, let's see what we have so far:</p><ul><li>a collection of elements</li><li>the total available width of our view</li><li>the size of each element view</li></ul><p>This is all <code>FlexibleView</code> needs to distribute the elements views into multiple lines:</p><pre><code><span class="keyword">struct</span> FlexibleView&lt;...&gt;: <span class="type">View</span> <span class="keyword">where</span> ... {
  <span class="comment">// ...</span>

  <span class="keyword">func</span> computeRows() -&gt; [[<span class="type">Data</span>.<span class="type">Element</span>]] {
    <span class="keyword">var</span> rows: [[<span class="type">Data</span>.<span class="type">Element</span>]] = [[]]
    <span class="keyword">var</span> currentRow = <span class="number">0</span>
    <span class="keyword">var</span> remainingWidth = availableWidth

    <span class="keyword">for</span> element <span class="keyword">in</span> data {
      <span class="keyword">let</span> elementSize = elementSizes[element, default: <span class="type">CGSize</span>(width: availableWidth, height: <span class="number">1</span>)]

      <span class="keyword">if</span> remainingWidth - elementSize.<span class="property">width</span> &gt;= <span class="number">0</span> {
        rows[currentRow].<span class="call">append</span>(element)
      } <span class="keyword">else</span> {
        <span class="comment">// start a new row</span>
        currentRow = currentRow + <span class="number">1</span>
        rows.<span class="call">append</span>([element])
        remainingWidth = availableWidth
      }

      remainingWidth = remainingWidth - elementSize.<span class="property">width</span>
    }

    <span class="keyword">return</span> rows
  }
}
</code></pre><p><code>computeRows</code> distributes all elements in multiple rows, while keeping the order of the elements and ensuring that each row width doesn't exceed the <code>availableWidth</code> obtained earlier.</p><p>In other words, the function returns an array of rows, where each row contains the array of elements for that row.</p><p>We can then combine this new function with <code>HStack</code>s and <code>VStack</code>s to obtain our final layout:</p><pre><code><span class="keyword">struct</span> FlexibleView&lt;...&gt;: <span class="type">View</span> <span class="keyword">where</span> ... {
  <span class="comment">// ...</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ZStack</span> {
      <span class="comment">// ...</span>

      <span class="type">VStack</span> {
        <span class="type">ForEach</span>(<span class="call">computeRows</span>(), id: \.<span class="keyword">self</span>) { rowElements <span class="keyword">in</span>
          <span class="type">HStack</span> {
            <span class="type">ForEach</span>(rowElements, id: \.<span class="keyword">self</span>) { element <span class="keyword">in</span>
              <span class="call">content</span>(element)
                .<span class="call">fixedSize</span>()
                .<span class="call">readSize</span> { size <span class="keyword">in</span>
                  elementsSize[element] = size
                }
            }
          }
        }
      }
    }
  }

  <span class="comment">// ...</span>
}
</code></pre><blockquote><p>At this point, <code>FlexibleView</code> will only take as much height as this <code>VStack</code>.</p></blockquote><p>And with this, we're done! The <a href="https://github.com/zntfdr/FiveStarsCodeSamples/tree/48e493a2b4acd7196c176689a8f3038936f0ed41/Flexible-SwiftUI">final project</a> also addresses spacing between elements and different alignments: adding these features is trivial once the fundamentals above are understood.</p><h2>Conclusions</h2><p>This year SwiftUI has gained powerful and welcome new components that let us create new interfaces that were tricky to build before. SwiftUI still lacks certain important features/aspects: this should not discourage us and try and find a solution on our own!</p><p><code>FlexibleView</code> is just an example. What other views have you built yourself? Please <a href="https://twitter.com/zntfdr">let me know</a>!</p><p>As always, thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more SwiftUI articles.</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-share-layout-information</guid><title>How to read a view size in SwiftUI</title><description>Explore how to share layout information in SwiftUI's view hierarchy</description><link>https://www.fivestars.blog/articles/swiftui-share-layout-information</link><pubDate>Wed, 12 Aug 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>SwiftUI views layout depends on each view state. This state is composed of internal properties, external values coming from the environment, etc.</p><p>When it comes to advanced custom layouts, sometimes a view might need layout information from its children.</p><p>A typical example is when ancestors need to know their children's size: let's explore how can do so.</p><blockquote><p>The view size is an example: the same concept applies to any other type conforming to <code>Equatable</code>.</p></blockquote><h2>Reading a view size</h2><p>When we need space information, we have one option in SwiftUI: the <a href="https://developer.apple.com/documentation/swiftui/geometryreader"><code>GeometryReader</code></a>.</p><p><code>GeometryReader</code> is a view that fills all the available space in all directions and comes with a <a href="https://developer.apple.com/documentation/swiftui/geometryproxy"><code>GeometryProxy</code></a> instance, which gives us access to its container's size and coordinate space.</p><pre><code><span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
  <span class="type">GeometryReader</span> { geometryProxy <span class="keyword">in</span>
    ...
    <span class="comment">// Use geometryProxy to get space information here.</span>
  }
}
</code></pre><p>In our case, we don't want to use <code>GeometryReader</code> directly: instead, we're interested in the space information of specific views.</p><p>SwiftUI provides <a href="https://developer.apple.com/documentation/swiftui/view/overlay(_:alignment:)"><code>.overlay()</code></a> and <a href="https://developer.apple.com/documentation/swiftui/view/background(_:alignment:)"><code>.background()</code></a>, which, respectively, add an extra view in front and behind another view. Most importantly, the proposed size for these views is equal to the size of the view they're applied to, making them a perfect candidate for what we are looking for:</p><pre><code><span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
  childView
    .<span class="call">background</span>(
      <span class="type">GeometryReader</span> { geometryProxy <span class="keyword">in</span>
        ...
        <span class="comment">// Use geometryProxy to get childView space information here.</span>
      }
    )
}
</code></pre><p><code>GeometryReader</code> requires us to declare a view within its body, we can use <code>Color.clear</code> to create an invisible layer:</p><pre><code><span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
  childView
    .<span class="call">background</span>(
      <span class="type">GeometryReader</span> { geometryProxy <span class="keyword">in</span>
        <span class="type">Color</span>.<span class="property">clear</span>
        <span class="comment">// Use geometryProxy to get childView space information here.</span>
      }
    )
}
</code></pre><p>Great! We now have our space information: it's time for our children to learn to communicate with their ancestors.</p><h2>Child to ancestors communication</h2><p>SwiftUI gives us the power of <a href="https://developer.apple.com/documentation/swiftui/preferencekey"><code>PreferenceKey</code></a>s, which is SwiftUI's way to pass information "up" on the view tree.</p><p>Let's start by defining our <code>PreferenceKey</code>, <code>SizePreferenceKey</code>:</p><pre><code><span class="keyword">struct</span> SizePreferenceKey: <span class="type">PreferenceKey</span> {
  <span class="keyword">static var</span> defaultValue: <span class="type">CGSize</span> = .<span class="dotAccess">zero</span>
  <span class="keyword">static func</span> reduce(value: <span class="keyword">inout</span> <span class="type">CGSize</span>, nextValue: () -&gt; <span class="type">CGSize</span>) {}
}
</code></pre><p><code>PreferenceKey</code> is a generic protocol that requires one static function and one static default value:</p><ul><li><code>defaultValue</code> is the value used when a view has no explicit value for this key</li><li><code>reduce(value:nextValue:)</code> combines the key values found in the tree with a new one</li></ul><blockquote><p>Refer to <a href="https://www.fivestars.blog/articles/preferencekey-reduce/">PreferenceKey's reduce method demystified</a> for a deeper look into the <code>reduce(value:nextValue:)</code> method.</p></blockquote><p>We will use <code>PreferenceKey</code> to store the measured size of our child, going back to our example:</p><pre><code><span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
  childView
    .<span class="call">background</span>(
      <span class="type">GeometryReader</span> { geometryProxy <span class="keyword">in</span>
        <span class="type">Color</span>.<span class="property">clear</span>
          .<span class="call">preference</span>(key: <span class="type">SizePreferenceKey</span>.<span class="keyword">self</span>, value: geometryProxy.<span class="property">size</span>)
      }
    )
}
</code></pre><p>Now the child size is in the tree hierarchy! How can we read it?<br><br>SwiftUI provides a <code>View</code> extension, <a href="https://developer.apple.com/documentation/swiftui/button/onpreferencechange(_:perform:)"><code>onPreferenceChange(_:perform:)</code></a>, which lets us specify the key we're interested in, and let us pass a closure to be executed when that preference changes:</p><pre><code><span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
  childView
    .<span class="call">background</span>(
      <span class="type">GeometryReader</span> { geometryProxy <span class="keyword">in</span>
        <span class="type">Color</span>.<span class="property">clear</span>
          .<span class="call">preference</span>(key: <span class="type">SizePreferenceKey</span>.<span class="keyword">self</span>, value: geometryProxy.<span class="property">size</span>)
      }
    )
    .<span class="call">onPreferenceChange</span>(<span class="type">SizePreferenceKey</span>.<span class="keyword">self</span>) { newSize <span class="keyword">in</span>
      <span class="call">print</span>(<span class="string">"The new child size is:</span> \(newSize)<span class="string">"</span>)
    }
}
</code></pre><p>Thanks to <code>onPreferenceChange</code>, any ancestor interested in this key can extract and get notified when our values change.</p><h2>Extension</h2><p>This way to obtain a child size is so handy that I find myself using it all the times, instead of copy-pasting it over and over, we can write a <code>View</code> extension for it:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">func</span> readSize(onChange: <span class="keyword">@escaping</span> (<span class="type">CGSize</span>) -&gt; <span class="type">Void</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">background</span>(
      <span class="type">GeometryReader</span> { geometryProxy <span class="keyword">in</span>
        <span class="type">Color</span>.<span class="property">clear</span>
          .<span class="call">preference</span>(key: <span class="type">SizePreferenceKey</span>.<span class="keyword">self</span>, value: geometryProxy.<span class="property">size</span>)
      }
    )
    .<span class="call">onPreferenceChange</span>(<span class="type">SizePreferenceKey</span>.<span class="keyword">self</span>, perform: onChange)
  }
}
</code></pre><p>This extension takes in a closure to be called whenever the view size is updated. Going back to our example, our new body declaration is:</p><pre><code><span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
  childView
    .<span class="call">readSize</span> { newSize <span class="keyword">in</span>
      <span class="call">print</span>(<span class="string">"The new child size is:</span> \(newSize)<span class="string">"</span>)
    }
}
</code></pre><p>Much better. The <em>plug-and-play</em> gist can be found <a href="https://github.com/FiveStarsBlog/CodeSamples/tree/0d764ee68ee1cfd634f6726f2867dcba383ebe99/SwiftUI-read-a-view-size">here</a>.</p><h2>Conclusions</h2><p>Once an ancestor has access to the key value, it's really up to us to decide what to do with it:<br>we can use it, for example, to <em>force</em> multiple elements to share the same value (e.g., the same size) and much, much more.</p><p>We will use this technique in many articles to come: do you use <code>PreferenceKey</code>? have you seen any cool examples? Please <a href="https://twitter.com/zntfdr">let me know</a>!</p><p>As always, thank you for reading, and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more SwiftUI articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/app-scene-storage</guid><title>Swift types with @AppStorage and @SceneStorage</title><description></description><link>https://www.fivestars.blog/articles/app-scene-storage</link><pubDate>Tue, 4 Aug 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p><code>@AppStorage</code> and <code>@SceneStorage</code> are two SwiftUI property wrappers that have been introduced this year.</p><p>Since both are backed by plists behind the scenes, out of the box, we can use them with the following types: <code>Bool</code>, <code>Int</code>, <code>Double</code>, <code>String</code>, <code>URL</code>, and <code>Data</code>.</p><p>What about other types?</p><h2><code>RawRepresentable</code> types</h2><p>Both <code>@AppStorage</code> and <code>@SceneStorage</code> offer two initializers accepting values conforming to the <code>RawRepresentable</code> protocol with an associated type <code>RawValue</code> of either <a href="https://developer.apple.com/documentation/swiftui/appstorage/init(wrappedvalue:_:store:)-26q9z"><code>Int</code></a> or <a href="https://developer.apple.com/documentation/swiftui/appstorage/init(wrappedvalue:_:store:)-7zk4r"><code>String</code></a>:</p><pre><code><span class="keyword">extension</span> <span class="type">AppStorage</span> {
  <span class="keyword">public init</span>(
    wrappedValue: <span class="type">Value</span>, 
    <span class="keyword">_</span> key: <span class="type">String</span>, 
    store: <span class="type">UserDefaults</span>? = <span class="keyword">nil</span>
  ) <span class="keyword">where</span> <span class="type">Value</span>: <span class="type">RawRepresentable</span>, <span class="type">Value</span>.<span class="type">RawValue</span> == <span class="type">Int</span>

  <span class="keyword">public init</span>(
    wrappedValue: <span class="type">Value</span>, 
    <span class="keyword">_</span> key: <span class="type">String</span>, 
    store: <span class="type">UserDefaults</span>? = <span class="keyword">nil</span>
  ) <span class="keyword">where</span> <span class="type">Value</span>: <span class="type">RawRepresentable</span>, <span class="type">Value</span>.<span class="type">RawValue</span> == <span class="type">String</span>
}

<span class="keyword">extension</span> <span class="type">SceneStorage</span> {
  <span class="keyword">public init</span>(
    wrappedValue: <span class="type">Value</span>, 
    <span class="keyword">_</span> key: <span class="type">String</span>, 
  ) <span class="keyword">where</span> <span class="type">Value</span>: <span class="type">RawRepresentable</span>, <span class="type">Value</span>.<span class="type">RawValue</span> == <span class="type">Int</span>

  <span class="keyword">public init</span>(
    wrappedValue: <span class="type">Value</span>, 
    <span class="keyword">_</span> key: <span class="type">String</span>, 
  ) <span class="keyword">where</span> <span class="type">Value</span>: <span class="type">RawRepresentable</span>, <span class="type">Value</span>.<span class="type">RawValue</span> == <span class="type">String</span>
}
</code></pre><blockquote><p>If you would like to dig deeper into <code>RawRepresentable</code>, I recommend <a href="https://nshipster.com/rawrepresentable/">this NSHipster article</a> by <a href="http://twitter.com/mattt">Mattt</a>.</p></blockquote><p>These initializers make it easy to store types such as <code>enum</code>s:</p><pre><code><span class="comment">// RawValue == Int</span>
<span class="keyword">enum</span> Fruit: <span class="type">Int</span>, <span class="type">Identifiable</span>, <span class="type">CaseIterable</span> {
  <span class="keyword">case</span> banana
  <span class="keyword">case</span> orange
  <span class="keyword">case</span> mango

  <span class="keyword">var</span> id: <span class="type">Int</span> { rawValue }
}

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@AppStorage</span>(<span class="string">"fruit"</span>) <span class="keyword">private var</span> fruit: <span class="type">Fruit</span> = .<span class="dotAccess">mango</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Picker</span>(<span class="string">"My Favorite Fruit"</span>, selection: $fruit) {
      <span class="type">ForEach</span>(<span class="type">Fruit</span>.<span class="property">allCases</span>, id: \.<span class="keyword">self</span>) {
        <span class="type">Text</span>(<span class="string">"</span>\($0)<span class="string">"</span> <span class="keyword">as</span> <span class="type">String</span>)
      }
    }.<span class="call">pickerStyle</span>(<span class="type">SegmentedPickerStyle</span>())
  }
}

<span class="comment">// RawValue == String</span>
<span class="keyword">enum</span> Fruit: <span class="type">String</span>, <span class="type">Identifiable</span>, <span class="type">CaseIterable</span> {
  <span class="keyword">case</span> banana
  <span class="keyword">case</span> orange
  <span class="keyword">case</span> mango

  <span class="keyword">var</span> id: <span class="type">String</span> { rawValue }
}

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@AppStorage</span>(<span class="string">"fruit"</span>) <span class="keyword">private var</span> fruit: <span class="type">Fruit</span> = .<span class="dotAccess">mango</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Picker</span>(<span class="string">"My Favorite Fruit"</span>, selection: $fruit) {
      <span class="type">ForEach</span>(<span class="type">Fruit</span>.<span class="property">allCases</span>, id: \.<span class="keyword">self</span>) {
        <span class="type">Text</span>($0.<span class="property">id</span>)
      }
    }.<span class="call">pickerStyle</span>(<span class="type">SegmentedPickerStyle</span>())
  }
}
</code></pre><p>While this is great, our apps most likely need to store a set of settings/preferences:<br>we could store each of them separately, but we'd probably rather have an easy way to fetch and store a <code>Codable</code> instance instead. How can we do so?</p><h2><code>Codable</code> types</h2><p>Imagine having a <code>Preference</code> struct with all our app settings:</p><pre><code><span class="keyword">enum</span> Appearance: <span class="type">String</span>, <span class="type">Codable</span>, <span class="type">CaseIterable</span>, <span class="type">Identifiable</span> {
  <span class="keyword">case</span> dark
  <span class="keyword">case</span> light
  <span class="keyword">case</span> system

  <span class="keyword">var</span> id: <span class="type">String</span> { rawValue }
}

<span class="keyword">struct</span> Preferences: <span class="type">Codable</span> {
  <span class="keyword">var</span> appearance: <span class="type">Appearance</span>
  <span class="comment">// TODO: add more settings here</span>
}
</code></pre><p>How can we use <code>@AppStorage</code> and <code>@SceneStorage</code> with our <code>Codable</code> type?<br>Unfortunately, so far, this doesn't seem to be possible. <a href="https://twitter.com/zntfdr">Please let me know</a> if you have found a way.</p><p>In the meantime, we can take up the challenge and solve it ourselves.</p><p>There are many ways to approach this, one of the simplest is probably extending <code>@Published</code>:</p><pre><code><span class="keyword">private var</span> cancellableSet: <span class="type">Set</span>&lt;<span class="type">AnyCancellable</span>&gt; = []

<span class="keyword">extension</span> <span class="type">Published</span> <span class="keyword">where</span> <span class="type">Value</span>: <span class="type">Codable</span> {
  <span class="keyword">init</span>(wrappedValue defaultValue: <span class="type">Value</span>, <span class="keyword">_</span> key: <span class="type">String</span>, store: <span class="type">UserDefaults</span>? = <span class="keyword">nil</span>) {
    <span class="keyword">let</span> _store: <span class="type">UserDefaults</span> = store ?? .<span class="dotAccess">standard</span>

    <span class="keyword">if
      let</span> data = _store.<span class="call">data</span>(forKey: key),
      <span class="keyword">let</span> value = <span class="keyword">try</span>? <span class="type">JSONDecoder</span>().<span class="call">decode</span>(<span class="type">Value</span>.<span class="keyword">self</span>, from: data) {
      <span class="keyword">self</span>.<span class="keyword">init</span>(initialValue: value)
    } <span class="keyword">else</span> {
      <span class="keyword">self</span>.<span class="keyword">init</span>(initialValue: defaultValue)
    }

    projectedValue
      .<span class="call">sink</span> { newValue <span class="keyword">in
        let</span> data = <span class="keyword">try</span>? <span class="type">JSONEncoder</span>().<span class="call">encode</span>(newValue)
        _store.<span class="call">set</span>(data, forKey: key)
      }
      .<span class="call">store</span>(in: &amp;cancellableSet)
  }
}
</code></pre><blockquote><p>Credits to <a href="https://twitter.com/filimo">Victor Kushnerov</a> for the <a href="https://github.com/filimo/ReaderTranslator/blob/ca5f7e17dba09385f5d325ac27a335f4666dfc1c/ReaderTranslator/Property%20Wrappers/Published.swift">original implementation</a>.</p></blockquote><p>Since <code>@Published</code> doesn't have the same kind of limitations as <code>@AppStorage</code> and <code>@SceneStorage</code>, we can extend it with this new initializer where:</p><ul><li>the first part sets the initial value (either the one currently stored or the passed default value)</li><li>the second part creates an observer that will update <code>UserDefaults</code> every time our <code>@Published</code> value changes.</li></ul><p>Thanks to this new initializer <code>@Published</code> behaves similarly to <code>@AppStorage</code>, but for <code>Codable</code> types (the same approach can be used to bring <code>@AppStorage</code> support to iOS 13).</p><p>We can now go back to our <code>ContentView</code> and use our <code>Codable</code> <code>Preference</code> type:</p><pre><code><span class="keyword">class</span> ContentViewModel: <span class="type">ObservableObject</span> {
  <span class="keyword">@Published</span>(<span class="string">"userPreferences"</span>) <span class="keyword">var</span> preferences = <span class="type">Preferences</span>(appearance: .<span class="dotAccess">system</span>)
}

<span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">@StateObject var</span> model = <span class="type">ContentViewModel</span>()

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Picker</span>(<span class="string">"Appearance"</span>, selection: $model.<span class="property">preferences</span>.<span class="property">appearance</span>) {
      <span class="type">ForEach</span>(<span class="type">Appearance</span>.<span class="property">allCases</span>, id: \.<span class="keyword">self</span>) {
        <span class="type">Text</span>(verbatim: $0.<span class="property">rawValue</span>)
      }
    }.<span class="call">pickerStyle</span>(<span class="type">SegmentedPickerStyle</span>())
  }
}
</code></pre><p>It's not as concise as an <code>@AppStorage</code> variable definition, but not too bad.</p><p>Similarly, we can take care of <code>@SceneStorage</code> where, instead of <code>UserDefaults</code>, we pass an <a href="https://developer.apple.com/documentation/uikit/uiscenesession"><code>UISceneSession</code></a> instance:</p><pre><code><span class="keyword">private var</span> cancellableSet: <span class="type">Set</span>&lt;<span class="type">AnyCancellable</span>&gt; = []

<span class="keyword">extension</span> <span class="type">Published</span> <span class="keyword">where</span> <span class="type">Value</span>: <span class="type">Codable</span> {
  <span class="keyword">init</span>(wrappedValue defaultValue: <span class="type">Value</span>, <span class="keyword">_</span> key: <span class="type">String</span>, session: <span class="type">UISceneSession</span>) {
    <span class="keyword">if
      let</span> data = session.<span class="property">userInfo</span>?[key] <span class="keyword">as</span>? <span class="type">Data</span>,
      <span class="keyword">let</span> value = <span class="keyword">try</span>? <span class="type">JSONDecoder</span>().<span class="call">decode</span>(<span class="type">Value</span>.<span class="keyword">self</span>, from: data) {
      <span class="keyword">self</span>.<span class="keyword">init</span>(initialValue: value)
    } <span class="keyword">else</span> {
      <span class="keyword">self</span>.<span class="keyword">init</span>(initialValue: defaultValue)
    }

    projectedValue
      .<span class="call">sink</span> { newValue <span class="keyword">in
        let</span> data = <span class="keyword">try</span>? <span class="type">JSONEncoder</span>().<span class="call">encode</span>(newValue)
        session.<span class="property">userInfo</span>?[key] = data
      }
      .<span class="call">store</span>(in: &amp;cancellableSet)
  }
}
</code></pre><p>The final gist can be found <a href="https://gist.github.com/zntfdr/d0cf1f7ba01613960577ac92ec89c5c5">here</a>.</p><h2>Conclusions</h2><p><code>@AppStorage</code> and <code>@SceneStorage</code> are two very welcome SwiftUI additions. Unfortunately, they support the same types supported by plists. In this article, we've seen how we can extend SwiftUI to take care of other types as well.</p><p>Do you use a different approach? Have you found a way to extend <code>@AppStorage</code> and <code>@SceneStorage</code> directly? Please <a href="https://twitter.com/zntfdr">let me know</a>!</p><p>Thank you for reading!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/redacted-custom-effects</guid><title>How to create custom redacted effects</title><description></description><link>https://www.fivestars.blog/articles/redacted-custom-effects</link><pubDate>Tue, 28 Jul 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>With the recent release of Xcode 12 we've gained a new <a href="https://developer.apple.com/documentation/swiftui/containerrelativeshape/redacted(reason:)"><code>.redacted(reason:)</code></a> SwiftUI modifier.</p><blockquote><p>See <a href="https://www.fivestars.blog/articles/swiftui-widgetkit">last week's article</a> for a quick refresher.</p></blockquote><p>This new modifier accepts an instance of <a href="https://developer.apple.com/documentation/swiftui/redactionreasons"><code>RedactionReasons</code></a>, which, as of beta 3, has one option available: <a href="https://developer.apple.com/documentation/swiftui/redactionreasons/placeholder"><code>.placeholder</code></a>.</p><p>While this is great, and I'm sure more options will come in the future, the effects are not very customizable:<br>in this article, let's see how expand what SwiftUI offers with our own effects.</p><h3>Extending <code>RedactionReasons</code></h3><p>If we target iOS 14 and later, one way to do so is to piggyback on the current APIs and define our own <code>RedactionReasons</code> instances, as this type conforms to <code>OptionSet</code>:</p><pre><code><span class="keyword">extension</span> <span class="type">RedactionReasons</span> {
  <span class="keyword">public static let</span> confidential = <span class="type">RedactionReasons</span>(rawValue: <span class="number">1</span> &lt;&lt; <span class="number">10</span>)
}
</code></pre><blockquote><p>The high number was chosen to avoid clashes with future additions from the SwiftUI team.</p></blockquote><p>We can then create a new view modifier that checks for the <code>redactionReasons</code> environment, and, if our reason is found, modify the view:</p><pre><code><span class="keyword">struct</span> Redactable: <span class="type">ViewModifier</span> {
  <span class="keyword">@Environment</span>(\.<span class="property">redactionReasons</span>) <span class="keyword">private var</span> reasons

  <span class="keyword">@ViewBuilder
  func</span> body(content: <span class="type">Content</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">if</span> reasons.<span class="call">contains</span>(.<span class="dotAccess">confidential</span>) {
      content
        .<span class="call">accessibility</span>(label: <span class="type">Text</span>(<span class="string">"Confidential"</span>))
        .<span class="call">overlay</span>(<span class="type">Color</span>.<span class="property">black</span>)
    } <span class="keyword">else</span> {
      content
    }
  }
}
</code></pre><p>At call site, instead of calling <code>.modifier(Redactable())</code> wherever needed, we can create a View extension:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">func</span> redactable() -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">modifier</span>(<span class="type">Redactable</span>())
  }
}
</code></pre><p>Here's how we would use it:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">Text</span>(<span class="string">"Hello world"</span>)

      <span class="type">Text</span>(<span class="string">"Hello world"</span>)
        .<span class="call">redactable</span>()
    }
    .<span class="call">redacted</span>(reason: .<span class="dotAccess">confidential</span>)
    .<span class="call">font</span>(.<span class="dotAccess">title</span>)
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/redacted-custom-effects/14.gif"/><p>The final gist can be found <a href="https://gist.github.com/zntfdr/c56ed8f9190f8416baa7527359e78ed6">here</a>.</p><p>While this works, we're using APIs that we don't own and don't have any inner insights: this makes our solution fragile. The SwiftUI team can, unintentionally, break our code in any future release.</p><p>To avoid this, we can create our own API.</p><h3>Building our own Redacted API</h3><p>Let's start by defining our reasons:</p><pre><code><span class="keyword">public enum</span> RedactionReason {
  <span class="keyword">case</span> placeholder
  <span class="keyword">case</span> confidential
  <span class="keyword">case</span> blurred
}
</code></pre><blockquote><p>This enum definition doesn't clash with SwiftUI's <code>RedactionReasons</code> because we omit the <code>s</code> at the end of the type (SwiftUI <code>RedactionReasons</code> is an <code>OptionSet</code>, hence the plural in the type name).</p></blockquote><p>Then we define a modifier for each of our reasons:</p><pre><code><span class="keyword">struct</span> Placeholder: <span class="type">ViewModifier</span> {
  <span class="keyword">func</span> body(content: <span class="type">Content</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    content
      .<span class="call">accessibility</span>(label: <span class="type">Text</span>(<span class="string">"Placeholder"</span>))
      .<span class="call">opacity</span>(<span class="number">0</span>)
      .<span class="call">overlay</span>(
        <span class="type">RoundedRectangle</span>(cornerRadius: <span class="number">2</span>)
          .<span class="call">fill</span>(<span class="type">Color</span>.<span class="property">black</span>.<span class="call">opacity</span>(<span class="number">0.1</span>))
          .<span class="call">padding</span>(.<span class="dotAccess">vertical</span>, <span class="number">4.5</span>)
    )
  }
}

<span class="keyword">struct</span> Confidential: <span class="type">ViewModifier</span> {
  <span class="keyword">func</span> body(content: <span class="type">Content</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    content
      .<span class="call">accessibility</span>(label: <span class="type">Text</span>(<span class="string">"Confidential"</span>))
      .<span class="call">overlay</span>(<span class="type">Color</span>.<span class="property">black</span>)
    )
  }
}

<span class="keyword">struct</span> Blurred: <span class="type">ViewModifier</span> {
  <span class="keyword">func</span> body(content: <span class="type">Content</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    content
      .<span class="call">accessibility</span>(label: <span class="type">Text</span>(<span class="string">"Blurred"</span>))
      .<span class="call">blur</span>(radius: <span class="number">4</span>)
  }
}
</code></pre><p>As we did before, we then define a <code>Redactable</code> view modifier:</p><pre><code><span class="keyword">struct</span> Redactable: <span class="type">ViewModifier</span> {
  <span class="keyword">let</span> reason: <span class="type">RedactionReason</span>?

  <span class="keyword">@ViewBuilder
  func</span> body(content: <span class="type">Content</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">switch</span> reason {
    <span class="keyword">case</span> .<span class="dotAccess">placeholder</span>:
      content
        .<span class="call">modifier</span>(<span class="type">Placeholder</span>())
    <span class="keyword">case</span> .<span class="dotAccess">confidential</span>:
      content
        .<span class="call">modifier</span>(<span class="type">Confidential</span>())
    <span class="keyword">case</span> .<span class="dotAccess">blurred</span>:
      content
        .<span class="call">modifier</span>(<span class="type">Blurred</span>())
    <span class="keyword">case nil</span>:
      content
    }
  }
}
</code></pre><blockquote><p>As described in the conclusions, there's little gain in making our <code>RedactionReason</code> available in the environment instead of passing it directly to the view modifier. Therefore I opted for the simpler API.</p></blockquote><p>Lastly, let's create the <code>View</code> extension to be used at call site:</p><pre><code><span class="keyword">extension</span> <span class="type">View</span> {
  <span class="keyword">func</span> redacted(reason: <span class="type">RedactionReason</span>?) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="call">modifier</span>(<span class="type">Redactable</span>(reason: reason))
  }
}
</code></pre><p>Here's an example on how to use it:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">Text</span>(<span class="string">"Hello World"</span>)
        .<span class="call">redacted</span>(reason: <span class="keyword">nil</span>)
      <span class="type">Text</span>(<span class="string">"Hello World"</span>)
        .<span class="call">redacted</span>(reason: .<span class="dotAccess">placeholder</span>)
      <span class="type">Text</span>(<span class="string">"Hello World"</span>)
        .<span class="call">redacted</span>(reason: .<span class="dotAccess">confidential</span>)
      <span class="type">Text</span>(<span class="string">"Hello World"</span>)
        .<span class="call">redacted</span>(reason: .<span class="dotAccess">blurred</span>)
    }
  }
}
</code></pre><p>And just like that, we have our own redacted API, compatible with iOS 13 as well.</p><img src="https://www.fivestars.blog/assets/posts/redacted-custom-effects/13.gif"/><p>The final gist can be found <a href="https://gist.github.com/zntfdr/0fd00e65ba775fbfa638f94dbcf6331f">here</a>.</p><h2>Conclusions</h2><p>SwiftUI's new modifier is great and I'm sure we will find plenty of awesome uses for it, especially when more effects will be added. For the moment, if what SwiftUI offers is not enough for our needs, we can create our own effects without too much trouble.</p><p>To be fair, as we don't own the actual rendering of each view, our custom effects are more limited than SwiftUI's <code>.redacted</code> ones:<br>for example there's no easy way for us to get the environment's <code>.foregroundColor</code> (FB8161189), and our effects take place even when a nested view has been marked with <a href="https://developer.apple.com/documentation/watchkit/nowplayingview/3664129-unredacted"><code>.unredacted()</code></a>.</p><p>What effects would you like to see implemented? Do you already a feature in mind where you will use <code>.redacted</code>? Please <a href="https://twitter.com/zntfdr">let me know</a>!</p><p>As always, thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more SwiftUI articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-widgetkit</guid><title>SwiftUI features in WidgetKit</title><description>All the new SwiftUI features designed for WidgetKit</description><link>https://www.fivestars.blog/articles/swiftui-widgetkit</link><pubDate>Tue, 21 Jul 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>This year we've gained new SwiftUI features designed for <a href="https://developer.apple.com/documentation/widgetkit/"><code>WidgetKit</code></a>: let's explore what they are.</p><h2>.redacted</h2><img src="https://www.fivestars.blog/assets/posts/swiftui-widgetkit/placeholder.gif"/><p>Released in Xcode 12b3, the <a href="https://developer.apple.com/documentation/swiftui/containerrelativeshape/redacted(reason:)"><code>.redacted</code></a> modifier renders our views as placeholders instead of displaying the actual content.</p><pre><code><span class="type">VStack</span> {
  <span class="type">Text</span>(<span class="string">"Five Stars"</span>)
    .<span class="call">font</span>(.<span class="dotAccess">title2</span>)

  <span class="type">Text</span>(<span class="string">"This"</span>)
  <span class="type">Text</span>(<span class="string">"is"</span>)
  <span class="type">Text</span>(<span class="string">"redacted!"</span>)
}
.<span class="call">redacted</span>(reason: .<span class="dotAccess">placeholder</span>)
</code></pre><p>If we have a <code>Text("Five Stars")</code> view, adding a <code>.redacted(reason: .placeholder)</code> modifier replaces the text with a rounded rectangle of the same length of the text, the same color as the text foreground, and some opacity to let the background still show through the rectangle.</p><blockquote><p>During WWDC this modifier was announced as <code>.isPlaceholder(_:)</code>: with the new <code>.redacted(reason:)</code> API, the SwiftUI team can add different placeholder styles (a.k.a. <a href="https://developer.apple.com/documentation/swiftui/redactionreasons"><code>RedactionReasons</code></a>) in the future.</p></blockquote><h3>Why is this for WidgetKit</h3><p><a href="https://developer.apple.com/documentation/widgetkit/creating-a-widget-extension">When making widgets</a>, a requirement is to provide a generic preview of our widget to be displayed in the Widget Gallery: the purpose of this gallery is to give a glimpse of the actual widget.</p><p><code>.redacted(reason:)</code> is perfect for this scenario, as each widget can create its custom preview, while still being consistent with the rest.</p><h3>Why this is great for SwiftUI</h3><p>There are multiple scenarios where we could use this.</p><p>Imagine, for example, having a screen where the data comes from a server:<br>instead of displaying a spinner or an empty view, we can now display a placeholder of the final view, to be replaced as soon as the data is received.</p><h2>Text Date Interpolation</h2><img src="https://www.fivestars.blog/assets/posts/swiftui-widgetkit/countdown.gif"/><p><code>Text</code> has gained new <a href="https://developer.apple.com/documentation/widgetkit/keeping-a-widget-up-to-date#Display-Dynamic-Dates">semantic API</a>s.</p><p>We can now pass a <code>Date</code> instance to <code>Text</code> and, <a href="https://developer.apple.com/documentation/swiftui/text/init%28_:style:%29">by setting</a> its <a href="https://developer.apple.com/documentation/swiftui/text/datestyle"><code>Text.DateStyle</code></a> to one of the relative options, we enable a special SwiftUI logic that knows when to refresh itself:</p><pre><code><span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
  <span class="type">VStack</span> {
    <span class="type">Text</span>(<span class="string">"Countdown"</span>)
    <span class="type">Text</span>(<span class="type">Date</span>().<span class="call">addingTimeInterval</span>(<span class="number">60</span>), style: .<span class="dotAccess">offset</span>)
      .<span class="call">font</span>(.<span class="dotAccess">title</span>)
      .<span class="call">bold</span>()
      .<span class="call">multilineTextAlignment</span>(.<span class="dotAccess">center</span>)
  }
}
</code></pre><p>The best part is that when this logic update happens, it won't trigger a redraw of the whole <code>body</code>, but just for the specific <code>Text</code>.</p><blockquote><p>As of Xcode 12 beta 2 the dynamic text only takes as much space as needed for the first draw, meaning that if, in the future, the time to be shown requires more space, it will be truncated. This is a bug and will hopefully be fixed by the time Xcode 12 is released (FB8053971).</p></blockquote><h3>Why is this for WidgetKit</h3><p>iOS 14 widgets are completely static: they're not interactive, we can't do animations, etc.<br>Using these new <code>Text</code> API will make our widget feel alive, despite the fact it's really not.</p><h3>Why this is great for SwiftUI</h3><p>While less necessary, it's still nice to have these new <code>Text</code> initializers that won't re-run the whole view <code>body</code> at every update.</p><h2>Link</h2><p><a href="https://developer.apple.com/documentation/swiftui/link "><code>Link</code></a> is a new SwiftUI element similar to <code>Button</code>, but specializes exclusively in opening <code>URL</code>s.</p><pre><code><span class="type">Link</span>(
  <span class="string">"Visit my blog! ✨"</span>,
  destination: <span class="type">URL</span>(string: <span class="string">"https://www.fivestars.blog"</span>)!
)
</code></pre><h3>Why is this for WidgetKit</h3><p>As we can't run any logic in our widgets, we cannot use SwiftUI buttons:<br>however, any <code>medium</code>/<code>large</code> widget can have multiple elements that, when tapped, deep link into the main app. This behavior is possible thanks to <code>Link</code>s.</p><h3>Why this is great for SwiftUI</h3><p><code>Link</code> makes it more apparent what the wrapped element functionality is without worrying about other side effects that we could add in a regular <code>Button</code> action.</p><blockquote><p>Besides universal links, <code>Link</code> can also open an URL on the device's default browser.</p></blockquote><h3>ContainerRelativeShape</h3><img src="https://www.fivestars.blog/assets/posts/swiftui-widgetkit/rainbow.gif"/><blockquote><p>Note how the rainbow colors follow the widget shape.</p></blockquote><p><a href="https://developer.apple.com/documentation/swiftui/containerrelativeshape"><code>ContainerRelativeShape</code></a> lets us get a hold of the <a href="https://developer.apple.com/documentation/swiftui/shape"><code>Shape</code></a> of our view container.</p><pre><code><span class="keyword">struct</span> PlaceholderView: <span class="type">View</span> {
  <span class="keyword">let</span> colors: [<span class="type">Color</span>] = [.<span class="dotAccess">red</span>, .<span class="dotAccess">orange</span>, .<span class="dotAccess">yellow</span>, .<span class="dotAccess">green</span>,
                         .<span class="dotAccess">blue</span>, .<span class="dotAccess">purple</span>, .<span class="dotAccess">pink</span>, .<span class="dotAccess">white</span>]
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ZStack</span> {
      <span class="type">ForEach</span>(<span class="number">0</span>..&lt;colors.<span class="property">count</span>) { index <span class="keyword">in</span>
        <span class="type">ContainerRelativeShape</span>()
          .<span class="call">inset</span>(by: <span class="type">CGFloat</span>(index) * <span class="number">3</span>)
          .<span class="call">fill</span>(colors[index])
      }

      <span class="type">Text</span>(<span class="string">"Five Stars"</span>)
        .<span class="call">font</span>(.<span class="dotAccess">title</span>)
        .<span class="call">bold</span>()
    }
  }
}
</code></pre><h3>Why is this for WidgetKit</h3><p>Currently, <code>ContainerRelativeShape</code> is used mainly to get the widget shape:<br>this is needed because different devices have different widget shapes and sizes, making it tricky to have a proper widget border to fit all widgets.</p><h3>Why this is great for SwiftUI</h3><p>While <code>ContainerRelativeShape</code> is great for widgets, in future betas it will be possible to use it with the <a href="https://developer.apple.com/documentation/swiftui/view/clipshape(_:style:"><code>.clipShape</code></a> modifier.</p><p>It might be a long shot, but it would be great if we could use it to also get the shape of the device our app is running on: think for example the screen shape of an Apple Watch or an iPad pro.</p><p>These are small details but would unlock designs simply not possible at this moment.</p><blockquote><p>If you agree, please feel free to dump my feedback: FB7953118</p></blockquote><h3>.widgetURL</h3><p><code>Link</code> works only on <code>medium</code>-/<code>large</code>-sized widgets. The widget itself is a tappable button for the <code>small</code> family: what URL the widget deep links to is set via the <a href="https://developer.apple.com/documentation/swiftui/view/widgeturl(_:)"><code>.widgetURL</code></a> modifier.</p><p>If we don't set <code>.widgetURl</code> in our widget, tapping it will just open the app.</p><pre><code><span class="keyword">struct</span> FiveStarsWidgetView: <span class="type">View</span> {
  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">FSWidget</span>()
      .<span class="call">widgetURL</span>(<span class="type">URL</span>(string: <span class="string">"fivestars.blog"</span>))
  }
}
</code></pre><p>Despite being part of SwiftUI, this modifier currently has no use besides in WidgetKit (as of Xcode 12b2).</p><h2>And More</h2><p>We've also gained more SwiftUI elements, such as <a href="https://developer.apple.com/documentation/widgetkit/widgetpreviewcontext"><code>WidgetPreviewContext</code></a> to preview widgets with the proper preview layout in the canvas, <a href="https://developer.apple.com/documentation/widgetkit/staticconfiguration/onbackgroundurlsessionevents(matching:_:)-2c8e7"><code>onBackgroundURLSessionEvents</code></a> to fetch data in our widgets, but those are within the WidgetKit framework, therefore out of this article scope.</p><h2>Conclusions</h2><p>Even if we don't plan to implement widgets in our apps, these new SwiftUI elements can undoubtedly benefit any SwiftUI app. While some feature might not ever be used outside WidgetKit, it's still nice to have more choices when building our next app.</p><p>Are you going to add a widget to your app? Will you use any of the new features somewhere else? <a href="https://twitter.com/zntfdr">I would love to know</a> 😃</p><p>Thank you for reading, and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more SwiftUI articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swiftui-hierarchy-list</guid><title>SwiftUI Hierarchy Lists</title><description></description><link>https://www.fivestars.blog/articles/swiftui-hierarchy-list</link><pubDate>Tue, 14 Jul 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Among the SwiftUI enhancements from this year, <a href="https://developer.apple.com/documentation/swiftui/list"><code>List</code></a> has learned to traverse hierarchical data.</p><p>This is useful when we'd like to display a tree-like structure, for example a Swift Package folder:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-hierarchy-list/spm.gif"/><p>The best part? Its implementation is <strong>one line of code</strong>.</p><p>Given our recursive model:</p><pre><code><span class="keyword">struct</span> FileItem: <span class="type">Identifiable</span> {
  <span class="keyword">let</span> name: <span class="type">String</span>
  <span class="keyword">var</span> children: [<span class="type">FileItem</span>]? <span class="comment">// 👈🏻</span>

  <span class="keyword">var</span> id: <span class="type">String</span> { name }
}
</code></pre><p>This is our complete view:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">let</span> data: [<span class="type">FileItem</span>]

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">List</span>(data, children: \.<span class="property">children</span>, rowContent: { <span class="type">Text</span>($0.<span class="property">name</span>) })
  }
}
</code></pre><p>This is all <code>List</code> needs:</p><ul><li>an array of elements</li><li>a keypath to the optional children</li><li>a view builder that, given one element, creates a row</li></ul><p><code>List</code> will take care of everything else.</p><p>This is yet another example of an elegant SwiftUI API, tucking away complexity from the developer:<br>let's see how we can implement our own hierarchy lists.</p><h2>Implementing a Hierarchy List</h2><blockquote><p>We will mimic the same signature of the <a href="https://developer.apple.com/documentation/swiftui/list/init(_:children:rowcontent:)"><code>List</code> initializer</a> used above.</p></blockquote><p>Our new structure will be composed of two pieces:</p><ol><li>a simple <code>List</code>, to preserve the lazy loading of our views</li><li>a <code>RecursiveView</code>, that will display a given "hierarchy level" (e.g. the content of a folder)</li></ol><h3>Generic Constraints</h3><p>Our Hierarchy List will support any inputs and views; therefore we will define two generics:</p><ul><li>the type of collection data that comes in, <code>Data</code></li><li>the output of the row view builder, <code>RowContent</code></li></ul><p>To make it work, we need to set some limitations on these generics:</p><ul><li><code>Data</code> will need to be a <code>RandomAccessCollection</code>, as our <code>List</code> can access any of its elements at will</li><li><code>Data</code> collection's <code>Element</code>, <code>Data.Element</code>, will need to conform to <code>Identifiable</code>, as required by <code>List</code> to identify each row</li><li>Lastly, the output of the row view builder, <code>RowContent</code>, must be a <code>View</code></li></ul><h3>1. HierarchyList</h3><p><code>HierarchyList</code> is the first of the two components just mentioned above, this is the component that developers will see when using our API.</p><pre><code><span class="keyword">struct</span> HierarchyList&lt;Data, RowContent: <span class="type">View</span>&gt;: <span class="type">View</span> <span class="keyword">where</span> <span class="type">Data</span>: <span class="type">RandomAccessCollection</span>, <span class="type">Data</span>.<span class="type">Element</span>: <span class="type">Identifiable</span> {
  <span class="keyword">let</span> recursiveView: <span class="type">RecursiveView</span>&lt;<span class="type">Data</span>, <span class="type">RowContent</span>&gt;

  <span class="keyword">init</span>(
    data: <span class="type">Data</span>, 
    children: <span class="type">KeyPath</span>&lt;<span class="type">Data</span>.<span class="type">Element</span>, <span class="type">Data</span>?&gt;, 
    rowContent: <span class="keyword">@escaping</span> (<span class="type">Data</span>.<span class="type">Element</span>) -&gt; <span class="type">RowContent</span>
  ) {
    <span class="keyword">self</span>.<span class="property">recursiveView</span> = <span class="type">RecursiveView</span>(data: data, children: children, rowContent: rowContent)
  }

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">List</span> {
      recursiveView
    }
  }
}
</code></pre><p>As promised, this is really just a <code>List</code> that invokes the second component of our structure: <code>RecursiveView</code>.</p><h3>2. RecursiveView</h3><p>This view will display one level of elements and, if one or more of these elements have any "sub-elements", will invoke itself via a <a href="https://developer.apple.com/documentation/swiftui/DisclosureGroup"><code>DisclosureGroup</code></a> (that we <a href="https://www.fivestars.blog/articles/optional-binding/">met previously</a>).</p><blockquote><p><code>DisclosureGroup</code>'s uses a lazy approach: it computes its <code>content</code> on demand, based on what it needs to display.</p></blockquote><p>In either case, the given <code>rowContent</code> view builder will be used to draw each row in the screen:</p><pre><code><span class="keyword">struct</span> RecursiveView&lt;Data, RowContent: <span class="type">View</span>&gt;: <span class="type">View</span> <span class="keyword">where</span> <span class="type">Data</span>: <span class="type">RandomAccessCollection</span>, <span class="type">Data</span>.<span class="type">Element</span>: <span class="type">Identifiable</span> {
  <span class="keyword">let</span> data: <span class="type">Data</span>
  <span class="keyword">let</span> children: <span class="type">KeyPath</span>&lt;<span class="type">Data</span>.<span class="type">Element</span>, <span class="type">Data</span>?&gt;
  <span class="keyword">let</span> rowContent: (<span class="type">Data</span>.<span class="type">Element</span>) -&gt; <span class="type">RowContent</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ForEach</span>(data) { child <span class="keyword">in
      if let</span> subChildren = child[keyPath: children] {
        <span class="type">DisclosureGroup</span> {
          <span class="type">RecursiveView</span>(data: subChildren, children: children, rowContent: rowContent)
        } label: {
          <span class="call">rowContent</span>(child)
        }
      } <span class="keyword">else</span> {
        <span class="call">rowContent</span>(child)
      }
    }
  }
}
</code></pre><h3>Wrap up</h3><p>That's it! With just 33 lines of code, we have perfectly cloned the new <code>List</code> hierarchical behavior:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-hierarchy-list/spm.gif"/><p>Lastly, let's add some access control modifiers in order to complete our API:</p><pre><code><span class="keyword">public struct</span> HierarchyList&lt;Data, RowContent: <span class="type">View</span>&gt;: <span class="type">View</span> <span class="keyword">where</span> <span class="type">Data</span>: <span class="type">RandomAccessCollection</span>, <span class="type">Data</span>.<span class="type">Element</span>: <span class="type">Identifiable</span> {
  <span class="keyword">private let</span> recursiveView: <span class="type">RecursiveView</span>&lt;<span class="type">Data</span>, <span class="type">RowContent</span>&gt;

  <span class="keyword">public init</span>(
    data: <span class="type">Data</span>, 
    children: <span class="type">KeyPath</span>&lt;<span class="type">Data</span>.<span class="type">Element</span>, <span class="type">Data</span>?&gt;, 
    rowContent: <span class="keyword">@escaping</span> (<span class="type">Data</span>.<span class="type">Element</span>) -&gt; <span class="type">RowContent</span>
  ) {
    <span class="keyword">self</span>.<span class="property">recursiveView</span> = <span class="type">RecursiveView</span>(data: data, children: children, rowContent: rowContent)
  }

  <span class="keyword">public var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">List</span> {
      recursiveView
    }
  }
}

<span class="keyword">private struct</span> RecursiveView&lt;Data, RowContent: <span class="type">View</span>&gt;: <span class="type">View</span> <span class="keyword">where</span> <span class="type">Data</span>: <span class="type">RandomAccessCollection</span>, <span class="type">Data</span>.<span class="type">Element</span>: <span class="type">Identifiable</span> {
  <span class="keyword">let</span> data: <span class="type">Data</span>
  <span class="keyword">let</span> children: <span class="type">KeyPath</span>&lt;<span class="type">Data</span>.<span class="type">Element</span>, <span class="type">Data</span>?&gt;
  <span class="keyword">let</span> rowContent: (<span class="type">Data</span>.<span class="type">Element</span>) -&gt; <span class="type">RowContent</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ForEach</span>(data) { child <span class="keyword">in
      if let</span> subChildren = child[keyPath: children] {
        <span class="type">DisclosureGroup</span> {
          <span class="type">RecursiveView</span>(data: subChildren, children: children, rowContent: rowContent)
        } label: {
          <span class="call">rowContent</span>(child)
        }
      } <span class="keyword">else</span> {
        <span class="call">rowContent</span>(child)
      }
    }
  }
}
</code></pre><p>The final gist can be found <a href="https://github.com/FiveStarsBlog/CodeSamples/blob/323bb83d198300ccb125f02259906774d026d76a/Hierarchy-List/ContentView-1.swift">here</a>.</p><h2>A Custom Hierarchy List Behavior</h2><p>Since we have built our Hierarchy List, we can now add behaviors that SwiftUI doesn't offer.</p><p>For example, SwiftUI's <code>List</code> initially only displays the first level of the hierarchy, with all disclosure groups collapsed:<br>how can we have the opposite behavior, where the full hierarchy is displayed from the start?</p><p>As we've covered in a <a href="https://www.fivestars.blog/articles/optional-binding/">How to add optional @<code>Binding</code>s to SwiftUI views</a>, <code>DisclosureGroup</code> comes with multiple initializers, some of which accept an <code>isExpanded</code> binding, letting us control the group expanded/collapsed state.</p><p>We can use one of those initializers to wrap the default <code>DisclosureGroup</code> in a new view, <code>FSDisclosureGroup</code>, that will set the initial <code>isExpanded</code> state to <code>true</code> instead of <code>false</code>:</p><pre><code><span class="keyword">struct</span> FSDisclosureGroup&lt;Label, Content&gt;: <span class="type">View</span> <span class="keyword">where</span> <span class="type">Label</span>: <span class="type">View</span>, <span class="type">Content</span>: <span class="type">View</span> {
  <span class="keyword">@State var</span> isExpanded: <span class="type">Bool</span> = <span class="keyword">true
  @ViewBuilder var</span> content: () -&gt; <span class="type">Content</span>
  <span class="keyword">@ViewBuilder var</span> label: <span class="type">Label</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">DisclosureGroup</span>(
      isExpanded: $isExpanded,
      content: content,
      label: label
    )
  }
}
</code></pre><p>We can now replace <code>DisclosureGroup</code> in our <code>RecursiveView</code> with <code>FSDisclosureGroup</code> and <em>voila'</em>, the full hierarchy is displayed by default, and users can still collapse/expand groups later on:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-hierarchy-list/spmExpanded.gif"/><p>The final gist can be found <a href="https://github.com/FiveStarsBlog/CodeSamples/blob/323bb83d198300ccb125f02259906774d026d76a/Hierarchy-List/ContentView-2.swift">here</a>.</p><h2>Hierarchy Lists in iOS 13</h2><p>Both the new hierarchical <code>List</code> API and <code>DisclosureGroup</code> are iOS 14+. However, we've already built our <code>DisclosureGroup</code>:<br>without bringing the whole API here, let's replace <code>FSDisclosureGroup</code> with the following:</p><pre><code><span class="keyword">struct</span> FSDisclosureGroup&lt;Label, Content&gt;: <span class="type">View</span> <span class="keyword">where</span> <span class="type">Label</span>: <span class="type">View</span>, <span class="type">Content</span>: <span class="type">View</span> {
  <span class="keyword">@State var</span> isExpanded: <span class="type">Bool</span> = <span class="keyword">false
  @ViewBuilder var</span> content: () -&gt; <span class="type">Content</span>
  <span class="keyword">@ViewBuilder var</span> label: <span class="type">Label</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span> { 
      isExpanded.<span class="call">toggle</span>() 
    } label: { 
      label.<span class="call">foregroundColor</span>(.<span class="dotAccess">blue</span>) 
    }

    <span class="keyword">if</span> isExpanded {
      <span class="call">content</span>()
    }
  }
}
</code></pre><p>This iOS-13 compatible View makes the whole row tappable, making it simple to see which elements have sub elements or not:</p><img src="https://www.fivestars.blog/assets/posts/swiftui-hierarchy-list/ios13-1.gif"/><blockquote><p>Making the UI more similar to the iOS 14 <code>DisclosureGroup</code> is left as an exercise to the reader.</p></blockquote><p>From the image above, we can see that our list is missing something that <code>DisclosureGroup</code> was taking care of: the padding!<br>When a group is expanded, its children are displayed with a leading padding to show the data structure clearly. However, we do not get that in our implementation.</p><p>Luckily for us, this is simple to address: all we need to do is add a <code>.padding(.leading)</code> modifier in our recursive call of <code>RecursiveView</code>.</p><pre><code><span class="keyword">private struct</span> RecursiveView&lt;Data, RowContent&gt;: <span class="type">View</span> <span class="keyword">where</span> <span class="type">Data</span>: <span class="type">RandomAccessCollection</span>, <span class="type">Data</span>.<span class="type">Element</span>: <span class="type">Identifiable</span>, <span class="type">RowContent</span>: <span class="type">View</span> {
  ...

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ForEach</span>(data) { child <span class="keyword">in
      if let</span> subChildren = child[keyPath: children] {
        <span class="type">DisclosureGroup</span> {
          <span class="type">RecursiveView</span>(data: subChildren, children: children, rowContent: rowContent)
            .<span class="call">padding</span>(.<span class="dotAccess">leading</span>) <span class="comment">// 👈🏻</span>
        } label: {
          ...
        }
      } <span class="keyword">else</span> {
        ...
      }
    }
  }
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/swiftui-hierarchy-list/ios13-2.gif"/><p>And that's it! Now we have a complete port of the Hierarchy List to iOS 13 😃</p><p>The complete gist, compatible with Xcode 11 and Swift 5.2, can be found <a href="https://github.com/FiveStarsBlog/CodeSamples/blob/323bb83d198300ccb125f02259906774d026d76a/Hierarchy-List/ContentView-xcode-11.swift">here</a>.</p><h2>Conclusions</h2><p>This week we've explored and re-implemented another great SwiftUI addition: are you going to use it in your projects?<br>What other new SwiftUI feature would you like me to write about? Please <a href="https://twitter.com/zntfdr">let me know</a>!</p><p>Thank you for reading, and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more SwiftUI articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/section-title-index-swiftui</guid><title>How to add SectionIndexTitles in SwiftUI</title><description></description><link>https://www.fivestars.blog/articles/section-title-index-swiftui</link><pubDate>Tue, 7 Jul 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>When SwiftUI launched, one of the missing pieces was the ability to control the offset of <a href="https://developer.apple.com/documentation/swiftui/scrollview"><code>ScrollView</code></a>s programmatically:<br>in Xcode 12 and iOS 14, we've gained <a href="https://developer.apple.com/documentation/swiftui/scrollviewreader"><code>ScrollViewReader</code></a>, which addresses precisely this shortcoming.</p><p>Another missing feature is the possibility to add <a href="https://developer.apple.com/documentation/uikit/uitableviewdatasource/1614857-sectionindextitles"><code>sectionIndexTitles</code></a> to <a href="https://developer.apple.com/documentation/swiftui/list"><code>List</code></a>s: this is the index list (for example, <code>A</code> through <code>Z</code>) placed on the trailing side of a table view, to jump to a specific section quickly.</p><p>In this article we're going to use <code>ScrollViewReader</code> and <code>DragGesture</code> and implement our <code>SectionIndexTitles</code>:</p><img src="https://www.fivestars.blog/assets/posts/section-title-index-swiftui/preview.gif"/><p>A lot is going on in this screen: let's build each component one by one.</p><h2>The List</h2><img src="https://www.fivestars.blog/assets/posts/section-title-index-swiftui/listonly.gif"/><ul><li>we will use a <code>ScrollView</code> and our <code>ScrollViewReader</code> (an example with <code>List</code> is given at the end of the article).</li><li>while we use a <code>ScrollView</code>, we still want to have a lazy loading of our UI elements; Therefore, we're going to use a <code>LazyVstack</code>.</li><li>lastly, the data comes in a dictionary, where the keys are the section headers, and the elements are the section content.</li></ul><blockquote><p>There are more efficient ways to display this data; however, it's not the focus of this article.</p></blockquote><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  <span class="keyword">let</span> devices: [<span class="type">String</span>: [<span class="type">String</span>]]

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollView</span> {
      <span class="type">LazyVStack</span> {
        devicesList
      }
    }
    .<span class="call">navigationBarTitle</span>(<span class="string">"Apple Devices"</span>)
  }

  <span class="keyword">var</span> devicesList: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ForEach</span>(devices.<span class="call">sorted</span>(by: { (lhs, rhs) -&gt; <span class="type">Bool</span> <span class="keyword">in</span>
      lhs.<span class="property">key</span> &lt; rhs.<span class="property">key</span>
    }), id: \.<span class="property">key</span>) { categoryName, devicesArray <span class="keyword">in</span>
      <span class="type">Section</span>(
        header: <span class="type">HeaderView</span>(title: categoryName)
      ) {
        <span class="type">ForEach</span>(devicesArray, id: \.<span class="keyword">self</span>) { deviceName <span class="keyword">in</span>
          <span class="type">RowView</span>(text: deviceName)
        }
      }
    }
  }
}
</code></pre><p>Here we're also introducing a couple of helper views, <code>RowView</code> and <code>HeaderView</code>, to make the code more readable:</p><pre><code><span class="keyword">struct</span> HeaderView: <span class="type">View</span> {
  <span class="keyword">let</span> title: <span class="type">String</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Text</span>(title)
      .<span class="call">font</span>(.<span class="dotAccess">title</span>)
      .<span class="call">fontWeight</span>(.<span class="dotAccess">bold</span>)
      .<span class="call">padding</span>()
      .<span class="call">frame</span>(maxWidth: .<span class="dotAccess">infinity</span>, alignment: .<span class="dotAccess">leading</span>)
  }
}

<span class="keyword">struct</span> RowView: <span class="type">View</span> {
  <span class="keyword">let</span> text: <span class="type">String</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Text</span>(text)
      .<span class="call">padding</span>()
      .<span class="call">frame</span>(maxWidth: .<span class="dotAccess">infinity</span>, alignment: .<span class="dotAccess">leading</span>)
  }
}
</code></pre><blockquote><p>Instead of wrapping <code>Text</code> in an <code>HStack</code> and pushing it to the left with a <code>Spacer</code>, we're wrapping <code>Text</code> in a <code>.frame</code> modifier, set to take all the width given, and align its width content leading edge.</p></blockquote><h2>Section Index Titles</h2><img src="https://www.fivestars.blog/assets/posts/section-title-index-swiftui/sectiontitles.gif"/><p>This is another standalone view. To make it more fun, let's use SF Symbols instead of text:</p><pre><code><span class="keyword">struct</span> SectionIndexTitles: <span class="type">View</span> {
  <span class="keyword">let</span> titles: [<span class="type">String</span>]

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">ForEach</span>(titles, id: \.<span class="keyword">self</span>) { title <span class="keyword">in</span>
        <span class="type">SectionIndexTitle</span>(image: <span class="call">sfSymbol</span>(for: title))
      }
    }
  }

  <span class="keyword">func</span> sfSymbol(for deviceCategory: <span class="type">String</span>) -&gt; <span class="type">Image</span> {
    <span class="keyword">let</span> systemName: <span class="type">String</span>
    <span class="keyword">switch</span> deviceCategory {
    <span class="keyword">case</span> <span class="string">"iPhone"</span>: systemName = <span class="string">"iphone"</span>
    <span class="keyword">case</span> <span class="string">"iPad"</span>: systemName = <span class="string">"ipad"</span>
    <span class="keyword">case</span> <span class="string">"iPod"</span>: systemName = <span class="string">"ipod"</span>
    <span class="keyword">case</span> <span class="string">"Apple TV"</span>: systemName = <span class="string">"appletv"</span>
    <span class="keyword">case</span> <span class="string">"Apple Watch"</span>: systemName = <span class="string">"applewatch"</span>
    <span class="keyword">case</span> <span class="string">"HomePod"</span>: systemName = <span class="string">"homepod"</span>
    <span class="keyword">default</span>: systemName = <span class="string">"xmark"</span>
    }
    <span class="keyword">return</span> <span class="type">Image</span>(systemName: systemName)
  }
}
</code></pre><p>As before, I'm introducing a new view, <code>SectionIndexTitle</code>, to improve readability:</p><pre><code><span class="keyword">struct</span> SectionIndexTitle: <span class="type">View</span> {
  <span class="keyword">let</span> image: <span class="type">Image</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">RoundedRectangle</span>(cornerRadius: <span class="number">8</span>, style: .<span class="dotAccess">continuous</span>)
      .<span class="call">foregroundColor</span>(<span class="type">Color</span>.<span class="property">gray</span>.<span class="call">opacity</span>(<span class="number">0.1</span>))
      .<span class="call">frame</span>(width: <span class="number">40</span>, height: <span class="number">40</span>)
      .<span class="call">overlay</span>(
        image
          .<span class="call">foregroundColor</span>(.<span class="dotAccess">blue</span>)
      )
  }
}
</code></pre><h2>Putting it together</h2><p>Now that we have both the list of devices and our <code>SectionIndexTitles</code>, we can put them together via an overlay:</p><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  ...

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollView</span> {
      <span class="type">LazyVStack</span> {
        devicesList
      }
    }
    .<span class="call">overlay</span>(sectionIndexTitles)
    .<span class="call">navigationBarTitle</span>(<span class="string">"Apple Devices"</span>)
  }

  ...

  <span class="keyword">var</span> sectionIndexTitles: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">SectionIndexTitles</span>(titles: devices.<span class="property">keys</span>.<span class="call">sorted</span>())
      .<span class="call">frame</span>(maxWidth: .<span class="dotAccess">infinity</span>, alignment: .<span class="dotAccess">trailing</span>)
      .<span class="call">padding</span>()
  }
}
</code></pre><blockquote><p>We could have used a <code>ZStack</code> as well; however, we want our <code>SectionIndexTitles</code> to be on top of our <code>ScrollView</code>, and avoid the titles expanding further than the <code>ScrollView</code> itself.</p></blockquote><h2>ScrollViewReader</h2><p>With the UI in place, it's time to use the new <code>ScrollViewReader</code> component.</p><p>By wrapping our <code>ScrollView</code> into a <code>ScrollViewReader</code>, we're given a <a href="https://developer.apple.com/documentation/swiftui/scrollviewproxy"><code>ScrollViewProxy</code></a> instance, used to trigger a scroll programmatically:<br>this works by calling the <a href="https://developer.apple.com/documentation/swiftui/scrollviewproxy/scrollto(_:anchor:)"><code>scrollTo(_:)</code></a> method on the instance and passing the <code>id</code> of the view we want to scroll to.</p><blockquote><p>Note that the element we want to scroll to might not be loaded yet: <code>ScrollViewProxy</code> will still work as expected.</p></blockquote><pre><code><span class="keyword">struct</span> ContentView: <span class="type">View</span> {
  ...

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">ScrollViewReader</span> { proxy <span class="keyword">in</span>
      <span class="type">ScrollView</span> {
        <span class="type">LazyVStack</span> {
          devicesList
        }
      }
      .<span class="call">overlay</span>(sectionIndexTitles)
    }
    .<span class="call">navigationBarTitle</span>(<span class="string">"Apple Devices"</span>)
  }

  ...
}
</code></pre><h2>ScrollViewProxy</h2><p>In our first attempt, we can make each section title a button to jump to that section. To do so, we need to:</p><ul><li>pass the proxy to our <code>SectionIndexTitles</code></li><li>wrap each <code>SectionIndexTitle</code> into a button with a trigger to scroll to that section</li></ul><pre><code><span class="keyword">struct</span> SectionIndexTitles: <span class="type">View</span> {
  <span class="keyword">let</span> proxy: <span class="type">ScrollViewProxy</span>
  <span class="keyword">let</span> titles: [<span class="type">String</span>]

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">ForEach</span>(titles, id: \.<span class="keyword">self</span>) { title <span class="keyword">in</span>
        <span class="type">Button</span> {
          proxy.<span class="call">scrollTo</span>(title)
        } label: {
          <span class="type">SectionIndexTitle</span>(image: <span class="call">sfSymbol</span>(for: title))
        }
      }
    }
  }

  ...
}
</code></pre><p>These two steps will already make our <code>SectionIndexTitles</code> work:<br>we don't need to add an explicit <code>.id</code> modifier in our <code>ScrollView</code> sections, because our <code>devicesList</code> is defined via a <code>ForEach</code>, where each view has an implicit identifier <code>id: \.key</code> (set in the <code>ForEach</code>), which is equal to our device categories.</p><img src="https://www.fivestars.blog/assets/posts/section-title-index-swiftui/button.gif"/><h2>DragGesture</h2><p>While our first attempt works fine, it doesn't precisely mimic <code>UITableView</code>'s <code>sectionIndexTitles</code>:<br>the way <code>sectionIndexTitles</code> used to work is by dragging our finger on the titles, and only that will make the <code>tableView</code> scroll to the right section.</p><p>In this second attempt, we will add a drag gesture that spans the whole <code>SectionIndexTitles</code> view, and then trigger the right <code>scrollTo</code> action when the finger is over one of the index titles.</p><p>One way to do so is by:</p><ul><li>storing the global <code>dragLocation</code> in a <code>@GestureState</code> variable</li><li>add a "drag location observer" to each section index title, which will trigger the <code>scrollTo</code> action when the gesture is happening on that title.</li></ul><pre><code><span class="keyword">struct</span> SectionIndexTitles: <span class="type">View</span> {
  <span class="keyword">let</span> proxy: <span class="type">ScrollViewProxy</span>
  <span class="keyword">let</span> titles: [<span class="type">String</span>]
  <span class="keyword">@GestureState private var</span> dragLocation: <span class="type">CGPoint</span> = .<span class="dotAccess">zero</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">VStack</span> {
      <span class="type">ForEach</span>(titles, id: \.<span class="keyword">self</span>) { title <span class="keyword">in</span>
        <span class="type">SectionIndexTitle</span>(image: <span class="call">sfSymbol</span>(for: title))
          .<span class="call">background</span>(<span class="call">dragObserver</span>(title: title))
      }
    }
    .<span class="call">gesture</span>(
      <span class="type">DragGesture</span>(minimumDistance: <span class="number">0</span>, coordinateSpace: .<span class="dotAccess">global</span>)
        .<span class="call">updating</span>($dragLocation) { value, state, <span class="keyword">_ in</span>
          state = value.<span class="property">location</span>
        }
    )
  }

  <span class="keyword">func</span> dragObserver(title: <span class="type">String</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">GeometryReader</span> { geometry <span class="keyword">in</span>
      <span class="call">dragObserver</span>(geometry: geometry, title: title)
    }
  }

  <span class="comment">// This function is needed as view builders don't allow to have 
  // pure logic in their body.</span>
  <span class="keyword">private func</span> dragObserver(geometry: <span class="type">GeometryProxy</span>, title: <span class="type">String</span>) -&gt; <span class="keyword">some</span> <span class="type">View</span> {
    <span class="keyword">if</span> geometry.<span class="call">frame</span>(in: .<span class="dotAccess">global</span>).<span class="call">contains</span>(dragLocation) {
      <span class="comment">// we need to dispatch to the main queue because we cannot access to the
      // `ScrollViewProxy` instance while the body is rendering</span>
      <span class="type">DispatchQueue</span>.<span class="property">main</span>.<span class="call">async</span> {
        proxy.<span class="call">scrollTo</span>(title, anchor: .<span class="dotAccess">center</span>)
      }
    }
    <span class="keyword">return</span> <span class="type">Rectangle</span>().<span class="call">fill</span>(<span class="type">Color</span>.<span class="property">clear</span>)
  }

  ...
}
</code></pre><p>And with this, we we've finally achieved our goal! 🎉</p><img src="https://www.fivestars.blog/assets/posts/section-title-index-swiftui/preview.gif"/><p>The final gist can be found <a href="https://gist.github.com/zntfdr/30664611ab48e9f73a22a290fa9064a6">here</a>.</p><h2>List</h2><p>Starting from Xcode 12 beta 3, all we've discussed here holds true for <code>List</code>s as well:</p><img src="https://www.fivestars.blog/assets/posts/section-title-index-swiftui/list.gif"/><p>The new gist can be found <a href="https://gist.github.com/zntfdr/f2cb9f8f5edff371784288f27756cef3">here</a>.</p><h2>Conclusions</h2><p>SwiftUI doesn't offer everything that UIKit has. It probably never will. However, this shouldn't stop us from experimenting and come up with our SwiftUI solutions that will make our migration to SwiftUI easier.</p><p>I like the example above because now our <code>SectionIndexTitles</code> is just another SwiftUI view, making it incredibly easy to customize it (if SF Symbols were not already good enough for you) in ways that would not be possible in UIKit.</p><p>Thank you for reading, and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more SwiftUI articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/optional-binding</guid><title>How to add optional @Bindings to SwiftUI views</title><description></description><link>https://www.fivestars.blog/articles/optional-binding</link><pubDate>Tue, 30 Jun 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Among the new SwiftUI views from this year WWDC we have <a href="https://developer.apple.com/documentation/swiftui/DisclosureGroup"><code>DisclosureGroup</code></a>.</p><p><code>DisclosureGroup</code> shows/hides its content based on a disclosure state:</p><pre><code><span class="type">DisclosureGroup</span>(isExpanded: $showingContent) {
   <span class="type">Text</span>(<span class="string">"Content"</span>)
} label: {
   <span class="type">Text</span>(<span class="string">"Tap to show content"</span>)
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/optional-binding/tap.gif"/><p>What caught my eye is that <code>DisclosureGroup</code> comes with a few initializers, some require a <code>isExpanded</code> <code>Binding&lt;Bool&gt;</code> parameter, some don't:</p><pre><code><span class="comment">// No binding</span>
<span class="type">DisclosureGroup</span> {
  <span class="type">Text</span>(<span class="string">"Content"</span>)
} label: {
  <span class="type">Text</span>(<span class="string">"Tap to show content"</span>)
}

<span class="comment">// With binding</span>
<span class="type">DisclosureGroup</span>(isExpanded: $showingContent) {
  <span class="type">Text</span>(<span class="string">"Content"</span>)
} label: {
  <span class="type">Text</span>(<span class="string">"Tap to show content"</span>)
}
</code></pre><p>How can a view deal with getting, and also not getting, a <code>@Binding</code>? Let's create a new view mocking this API.</p><h2>Why having these options?</h2><p>In the <a href="https://www.wwdcnotes.com/notes/wwdc20/10040/">WWDC20 session <code>Data Essentials in SwiftUI</code></a> the SwiftUI team teaches us to ask the following questions when creating a new view:</p><ol><li>What data does this view need?</li><li>How will the view manipulate that data?</li><li>Where will the data come from?</li><li>Who owns the data?</li></ol><p>With <code>DisclosureGroup</code>, it's clear that the <code>isExpanded</code> state could be handled both internally and externally:</p><ul><li>internally, if the state doesn't effect any other part of the view hierarchy</li><li>externally, if we want to access and manipulate this state somewhere else as well</li></ul><p>It makes sense for <code>DisclosureGroup</code> to expose and handle both options. Let's see how we can create this behavior ourselves.</p><h2>Getting started</h2><p>Despite <code>isExpanded</code> not being present in all initializers, a <code>Binding&lt;Bool&gt;</code> state <strong>is necessary</strong> for the view to work. Let's create a view that requires this binding:</p><pre><code><span class="keyword">struct</span> FSDisclosureGroup&lt;Label: <span class="type">View</span>, Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">@Binding var</span> isExpanded: <span class="type">Bool</span>
  <span class="keyword">@ViewBuilder var</span> content: () -&gt; <span class="type">Content</span>
  <span class="keyword">@ViewBuilder var</span> label: <span class="type">Label</span>

  <span class="keyword">@ViewBuilder
  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span> { isExpanded.<span class="call">toggle</span>() } label: { label }
    <span class="keyword">if</span> isExpanded {
      <span class="call">content</span>()
    }
  }
}
</code></pre><p>We can now replace <code>DisclosureGroup</code> in our code with <code>FSDisclosureGroup</code>, everything works exactly in the same way:</p><pre><code><span class="type">FSDisclosureGroup</span>(isExpanded: $showingContent) {
   <span class="type">Text</span>(<span class="string">"Content"</span>)
} label: {
   <span class="type">Text</span>(<span class="string">"Tap to show content"</span>)
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/optional-binding/tap2.gif"/><blockquote><p>This article aims to mimic the API and behavior of <code>DisclosureGroup</code>, not its UI.</p></blockquote><h2>Making the binding state optional</h2><p>With <code>FSDisclosureGroup</code> there's no way around it: it needs a <code>Binding&lt;Bool&gt;</code> state.</p><p>However, it doesn't matter where this binding comes from, for example we can wrap <code>FSDisclosureGroup</code> into a container that:</p><ul><li>acts as its public interface</li><li>declares a <code>State&lt;Bool&gt;</code></li></ul><p>If a binding is given, the container will pass it to <code>FSDisclosureGroup</code>, otherwise it will pass its own state:</p><pre><code><span class="keyword">struct</span> FSDisclosureGroupContainer&lt;Label: <span class="type">View</span>, Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">@State private var</span> privateIsExpanded: <span class="type">Bool</span> = <span class="keyword">false
  var</span> isExpanded: <span class="type">Binding</span>&lt;<span class="type">Bool</span>&gt;?
  <span class="keyword">@ViewBuilder var</span> content: () -&gt; <span class="type">Content</span>
  <span class="keyword">@ViewBuilder var</span> label: <span class="type">Label</span>

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">FSDisclosureGroup</span>(
      isExpanded: isExpanded ?? $privateIsExpanded,
      content: content) {
      label
    }
  }
}
</code></pre><p>We can now initialize <code>FSDisclosureGroupContainer</code> by either passing or not a binding. The outcome will be the same:</p><pre><code><span class="comment">// No binding</span>
<span class="type">FSDisclosureGroupContainer</span> {
  <span class="type">Text</span>(<span class="string">"Content"</span>)
} label: {
  <span class="type">Text</span>(<span class="string">"Tap to show content"</span>)
}

<span class="comment">// With binding</span>
<span class="type">FSDisclosureGroupContainer</span>(isExpanded: $showingContent) {
  <span class="type">Text</span>(<span class="string">"Content"</span>)
} label: {
  <span class="type">Text</span>(<span class="string">"Tap to show content"</span>)
}
</code></pre><img src="https://www.fivestars.blog/assets/posts/optional-binding/tap2.gif"/><h2>Making our public API pretty</h2><p>Thanks to <code>FSDisclosureGroupContainer</code> we have a way to handle both cases where a <code>@Binding</code> is passed and not, however this view currently offers only the default initializer:</p><pre><code><span class="keyword">init</span>(
  isExpanded: <span class="type">Binding</span>&lt;<span class="type">Bool</span>&gt;? = <span class="keyword">nil</span>, 
  <span class="keyword">@ViewBuilder</span> content: <span class="keyword">@escaping</span> () -&gt; <span class="type">Content</span>, 
  <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>
)
</code></pre><p>Having an optional <code>isExpanded</code> parameter of type <code>Binding&lt;Bool&gt;?</code> is a source of confusion: what does <code>init(isExpanded: nil, ...)</code> do?</p><p>If we don't know the implementation details, this could raise quite a few eyebrows.</p><p>Therefore, let's create two new initializers instead:</p><ul><li>one will require no binding at all</li><li>one will require a non-optional binding</li></ul><pre><code><span class="keyword">struct</span> FSDisclosureGroupContainer&lt;Label: <span class="type">View</span>, Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">@State private var</span> privateIsExpanded: <span class="type">Bool</span> = <span class="keyword">false
  var</span> isExpanded: <span class="type">Binding</span>&lt;<span class="type">Bool</span>&gt;?
  <span class="keyword">@ViewBuilder var</span> content: () -&gt; <span class="type">Content</span>
  <span class="keyword">@ViewBuilder var</span> label: <span class="type">Label</span>

  <span class="comment">// No binding</span>
  <span class="keyword">init</span>(
    <span class="keyword">@ViewBuilder</span> content: <span class="keyword">@escaping</span> () -&gt; <span class="type">Content</span>,
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>
  ) {
    <span class="keyword">self</span>.<span class="keyword">init</span>(isExpanded: <span class="keyword">nil</span>, content: content, label: label)
  }

  <span class="comment">// With binding</span>
  <span class="keyword">init</span>(
    isExpanded: <span class="type">Binding</span>&lt;<span class="type">Bool</span>&gt;,
    <span class="keyword">@ViewBuilder</span> content: <span class="keyword">@escaping</span> () -&gt; <span class="type">Content</span>,
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>
  ) {
    <span class="keyword">self</span>.<span class="keyword">init</span>(isExpanded: .<span class="call">some</span>(isExpanded), content: content, label: label)
  }

  <span class="comment">// Private!</span>
  <span class="keyword">private init</span>(
    isExpanded: <span class="type">Binding</span>&lt;<span class="type">Bool</span>&gt;? = <span class="keyword">nil</span>,
    <span class="keyword">@ViewBuilder</span> content: <span class="keyword">@escaping</span> () -&gt; <span class="type">Content</span>,
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>
  ) {
    <span class="keyword">self</span>.<span class="property">isExpanded</span> = isExpanded
    <span class="keyword">self</span>.<span class="property">content</span> = content
    <span class="keyword">self</span>.<span class="property">label</span> = <span class="call">label</span>()
  }

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">FSDisclosureGroup</span>(
      isExpanded: isExpanded ?? $privateIsExpanded,
      content: content) {
      label
    }
  }
}
</code></pre><p>Our container now exposes two easy to understand initializers:</p><pre><code><span class="comment">// No binding</span>
<span class="keyword">init</span>(<span class="keyword">@ViewBuilder</span> content: <span class="keyword">@escaping</span> () -&gt; <span class="type">Content</span>, <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>)

<span class="comment">// With binding</span>
<span class="keyword">init</span>(isExpanded: <span class="type">Binding</span>&lt;<span class="type">Bool</span>&gt;, <span class="keyword">@ViewBuilder</span> content: <span class="keyword">@escaping</span> () -&gt; <span class="type">Content</span>, <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>)
</code></pre><p>This is much better, developers using these API immediately understand what they do, without worrying what's happening behind the scenes.</p><h2>A container?</h2><p>Let's review what we did so far:</p><ul><li>we've built a view, <code>FSDisclosureGroup</code>, with the actual implementation of our UI, which requires a binding</li><li>we've built a <code>FSDisclosureGroup</code> container, <code>FSDisclosureGroupContainer</code>, letting developers use <code>FSDisclosureGroup</code> by either passing a <code>@Binding</code>, or not</li></ul><p>Developers don't need to know how this works behind the scenes: <code>FSDisclosureGroupContainer</code> <strong>is an implementation detail</strong>.</p><p>The first fundamental of <a href="https://swift.org/documentation/api-design-guidelines/#fundamentals">Swift's API Design Guidelines</a> is <code>Clarity at the point of use</code>:<br>we should always strive to hide all the complexity of our views, while being clear on what they do.</p><p>With this in mind we can improve our code by:</p><ul><li>renaming <code>FSDisclosureGroupContainer</code> to <code>FSDisclosureGroup</code></li><li>renaming the original <code>FSDisclosureGroup</code> to <code>_FSDisclosureGroup</code>, and "hiding it" by not exposing it</li></ul><pre><code><span class="keyword">struct</span> FSDisclosureGroup&lt;Label: <span class="type">View</span>, Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">@State private var</span> privateIsExpanded: <span class="type">Bool</span> = <span class="keyword">false
  var</span> isExpanded: <span class="type">Binding</span>&lt;<span class="type">Bool</span>&gt;?
  <span class="keyword">@ViewBuilder var</span> content: () -&gt; <span class="type">Content</span>
  <span class="keyword">@ViewBuilder var</span> label: <span class="type">Label</span>

  <span class="keyword">init</span>(
    <span class="keyword">@ViewBuilder</span> content: <span class="keyword">@escaping</span> () -&gt; <span class="type">Content</span>,
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>
  ) {
    <span class="keyword">self</span>.<span class="keyword">init</span>(isExpanded: <span class="keyword">nil</span>, content: content, label: label)
  }

  <span class="keyword">init</span>(
    isExpanded: <span class="type">Binding</span>&lt;<span class="type">Bool</span>&gt;,
    <span class="keyword">@ViewBuilder</span> content: <span class="keyword">@escaping</span> () -&gt; <span class="type">Content</span>,
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>
  ) {
    <span class="keyword">self</span>.<span class="keyword">init</span>(isExpanded: .<span class="call">some</span>(isExpanded), content: content, label: label)
  }

  <span class="keyword">private init</span>(
    isExpanded: <span class="type">Binding</span>&lt;<span class="type">Bool</span>&gt;? = <span class="keyword">nil</span>,
    <span class="keyword">@ViewBuilder</span> content: <span class="keyword">@escaping</span> () -&gt; <span class="type">Content</span>,
    <span class="keyword">@ViewBuilder</span> label: () -&gt; <span class="type">Label</span>
  ) {
    <span class="keyword">self</span>.<span class="property">isExpanded</span> = isExpanded
    <span class="keyword">self</span>.<span class="property">content</span> = content
    <span class="keyword">self</span>.<span class="property">label</span> = <span class="call">label</span>()
  }

  <span class="keyword">var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">_FSDisclosureGroup</span>(
      isExpanded: isExpanded ?? $privateIsExpanded,
      content: content
    ) {
      label
    }
  }
}

<span class="comment">// Private!</span>
<span class="keyword">private struct</span> _FSDisclosureGroup&lt;Label: <span class="type">View</span>, Content: <span class="type">View</span>&gt;: <span class="type">View</span> {
  <span class="keyword">@Binding var</span> isExpanded: <span class="type">Bool</span>
  <span class="keyword">@ViewBuilder var</span> content: () -&gt; <span class="type">Content</span>
  <span class="keyword">@ViewBuilder var</span> label: <span class="type">Label</span>

  <span class="keyword">@ViewBuilder
  var</span> body: <span class="keyword">some</span> <span class="type">View</span> {
    <span class="type">Button</span> { isExpanded.<span class="call">toggle</span>() } label: { label }
    <span class="keyword">if</span> isExpanded {
      <span class="call">content</span>()
    }
  }
}
</code></pre><p>And with this last change, we've accomplished our goal! 🎉</p><h2>Conclusions</h2><p>The more we work with Swift, the more we see how we can expose powerful APIs, while also making them easy to use and even look <em>simple</em>. This is one of the best aspects of Swift and SwiftUI, and it's something that we should always strive to do in our own code as well.</p><p>Of course, I have no insights on the actual implementation of <code>DisclosureGroup</code>, but just by finding a way on how to mimic it, we can really appreciate all the tremendous work that both the Swift and SwiftUI team put into making things <em>simple</em> for us.</p><p>What do you think? Do you have any alternative on how to build this view? Please <a href="https://twitter.com/zntfdr">let me know</a>!</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more SwiftUI articles! 🚀</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/wwdc-notes</guid><title>Introducing WWDC Notes</title><description></description><link>https://www.fivestars.blog/articles/wwdc-notes</link><pubDate>Mon, 15 Jun 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>As a university student, there hasn't been a day where I came back home without plenty of new, lengthly notes from all the classes I attended.</p><p>Sometimes my professors would explain concepts way better than the course books, or cover topics that were not in the books at all. I didn't want to miss a thing!</p><p>While my university years are over, the habit of taking notes has never really faded away, and one place where this is clearly manifested at is Apple's WWDC.</p><p>Every year Apple gifts us with hundreds of new sessions that dive deep into new frameworks, show us the evolution of current frameworks, dictate new best practices, and much more.</p><p>All of this WWDC material is incredibly valuable, and I find myself referencing to my WWDC notes over and over, even many years after taking them.</p><p>While this is great, all of these notes were only growing year by year, and with not much of an organization, it started getting harder to find exactly what I needed.</p><p>I had to find a solution, and while I was at it, I thought that, maybe, these notes could be useful to other people as well.</p><p>Introducing...</p><h2><a href="https://www.wwdcnotes.com">WWDC Notes</a> 🎉</h2><p><a href="https://www.wwdcnotes.com">WWDC Notes</a> is a brand new website that contains all my past and future WWDC notes, all the way back to WWDC 2012 (for the moment!). All notes are separated by year, track (Design, Developer Tools, ...), and platform.</p><p>While the website currently contains only my notes, it doesn't have to be that way:<br><a href="https://www.wwdcnotes.com">WWDC Notes</a> content is completely <a href="https://github.com/zntfdr/WWDC-Notes">open source</a> and I welcome everybody to come in and add even more notes or update the current ones with corrections/enhancements/etc.</p><p>I had fun building it and I hope you will find WWDC Notes useful as much as I find it myself!</p><p>This is very much an MVP with more features to come, if you have a chance, <a href="https://www.wwdcnotes.com">please have a look</a>!</p><p>Any feedback/feature request/and more is very welcome, please <a href="https://twitter.com/zntfdr">hit me on Twitter</a>! 😊</p><p>Thank you for reading and I wish everybody a great WWDC 2020!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/wwdc-countdown</guid><title>The WWDC Countdown Talks Challenge</title><description></description><link>https://www.fivestars.blog/articles/wwdc-countdown</link><pubDate>Wed, 3 Jun 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>The amount of amazing content out there is incredible. With so much at our disposal, it's very easy to fall behind and never catch up.</p><p>With speculations indicating a WWDC happening on the first week of June, in early May, exactly 4 weeks before June, <a href="https://twitter.com/zntfdr/status/1257324514108518407">I've publicly challenged myself</a> to watch at least one talk per day for the rest of May, welcoming others to join as well.</p><p>In this post you can find all the videos that I watched, separated by topic. If you would like to see them in the same chronological order as I did, please refer to <a href="https://twitter.com/zntfdr/status/1257324514108518407">this Twitter thread</a> instead.</p><blockquote><p><a href="https://twitter.com/zntfdr/status/1257324514108518407">The Twitter thread</a> also has a short summary for each talk.</p></blockquote><p>Without further ado:</p><h2>SwiftUI &amp; Combine</h2><ul><li><a href="https://www.youtube.com/watch?v=GuK6wwX8M0E&list=PLleLsCcywRxKT3VgBPtzxtRjq-NXK0FZw"><em>SwiftUI under the hood</em></a> by <a href="https://twitter.com/chriseidhof">Chris Eidhof</a> at <a href="https://swiftable.io/">BA: Swiftable</a> 2019.</li><li><a href="https://vimeo.com/showcase/6225806/video/354579304"><em>Building for the Long Haul: Preparing Your App for SwiftUI</em></a> by <a href="https://twitter.com/Frankacy">Frank Courville</a> at <a href="https://www.swiftconf.to/">Swift TO</a> 2019.</li><li><a href="https://www.youtube.com/watch?v=vDzIeFzGAuU"><em>SwiftUI &amp; Combine</em></a> by <a href="https://twitter.com/dimsumthinking">Daniel Steinberg</a> at <a href="https://frenchkit.fr/">FrenchKit</a> 2019.</li><li><a href="https://www.youtube.com/watch?v=cawUX50Pt4k"><em>Getting Started with Combine</em></a> by <a href="https://twitter.com/freak4pc">Shai Mishali</a> at <a href="https://www.tryswift.co/">try! Swift New York</a> 2019.</li><li><a href="https://www.dotconferences.com/2020/02/donny-wals-the-combine-triad"><em>The Combine Triad</em></a> by <a href="https://twitter.com/DonnyWals">Donny Wals</a> at <a href="https://www.dotswift.io/">dotSwift</a> 2020.</li><li><a href="https://www.youtube.com/watch?v=5vMm-grRZBY&feature=youtu.be"><em>Writing game in SwiftUI —a madness?</em></a> by <a href="https://twitter.com/myridiphis">Pavel Zak</a> at <a href="https://moconf.by/">MobileOptimized</a> 2019.</li><li><a href="https://vimeo.com/showcase/6225806/video/354555005"><em>Pushing SwiftUI to the Limit</em></a> by <a href="https://twitter.com/nerdonica ">Veronica Ray</a> at <a href="https://www.swiftconf.to/">Swift TO</a> 2019.</li></ul><h2>iOS</h2><ul><li><a href="https://academy.realm.io/posts/tryswift-tim-oliver-advanced-graphics-with-core-animation/"><em>Advanced Graphics with Core Animation</em></a> (<a href="https://www.youtube.com/watch?v=QgRO-51I-pY">Japanese</a>) by <a href="https://twitter.com/TimOliverAU">Tim Oliver</a> at <a href="https://www.tryswift.co/">try! Swift Tokyo</a> 2016.</li><li><a href="https://www.youtube.com/watch?v=x_czrytFGrU&list=PLr5cF6ekyIDhnZ2ESdPshESfyAyxZ01TO&index=36"><em>OMG Collection Views!</em></a> by <a href="https://twitter.com/twostraws">Paul Hudson</a> at <a href="https://mobiconf.org/">Mobiconf</a> 2019.</li><li><a href="https://www.youtube.com/watch?v=HXFHumn0j1I&list=PLr5cF6ekyIDhnZ2ESdPshESfyAyxZ01TO&index=10"><em>0xFEEDFACE</em></a> by <a href="https://twitter.com/kam800">Kamil Borzym</a> at <a href="https://mobiconf.org/">Mobiconf</a> 2019.</li><li><a href="https://www.youtube.com/watch?v=9DIJvVCCMyI&list=PLljEvxF6pJBBSQXDRnQvACukLJGybS17O&index=20"><em>Animations are Assets</em></a> by <a href="https://twitter.com/_inside">Guilherme Rambo</a> at <a href="http://altconf.com/">AltConf</a> 2019.</li></ul><h2>Swift</h2><ul><li><a href="https://www.youtube.com/watch?v=iLDldae64xE"><em>High Performance Systems in Swift</em></a> <a href="https://www.youtube.com/watch?v=WCUj581Dpec">(part 2)</a> by <a href="https://twitter.com/johannesweiss">Johannes Weiss</a> (part 1) and <a href="https://twitter.com/Lukasaoz">Cory Benfield</a> (part 2) at <a href="https://www.dotswift.io/">dotSwift</a> 2019 (part 1) and <a href="https://www.hackingwithswift.com/live">Hacking with Swift Live</a> 2019 (part 2).</li><li><a href="https://www.youtube.com/watch?v=0rHG_Pa86oA"><em>Exploiting The Swift ABI</em></a> by <a href="https://twitter.com/Codafi_">Robert Widmann</a> at <a href="https://meetup.com/swift-language">Swift Language User Group (San Francisco)</a> 2019.</li><li><a href="https://www.youtube.com/watch?v=GzP2oaZRi7Q"><em>Stabilizing your ABI</em></a> by <a href="https://twitter.com/dimsumthinking">Daniel Steinberg</a> at <a href="https://swiftconf.com/">SwiftConf</a> 2019.</li><li><a href="https://www.youtube.com/watch?v=3BVkbWXcFS4"><em>Protocol Witnesses</em></a> by <a href="https://twitter.com/mbrandonw">Brandon Williams</a> at <a href="https://appbuilders.ch/">App Builders</a> 2019.</li><li><a href="https://www.youtube.com/watch?v=jDvk7nztqHw&list=PLr5cF6ekyIDhnZ2ESdPshESfyAyxZ01TO&index=21"><em>Pushing Protocols to Their Limits</em></a> by <a href="https://twitter.com/jgarnham">Josh Garnham</a> at <a href="https://mobiconf.org/">Mobiconf</a> 2019.</li><li><a href="https://vimeo.com/362179375"><em>Swift Programming and Logic</em></a> by <a href="https://twitter.com/inamiy">Yasuhiro Inami</a> at <a href="https://nsspain.com/">NSSPain</a> 2019.</li><li><a href="https://www.youtube.com/watch?v=GnT2ZeHVJe4"><em>Contributing to Swift</em></a> by <a href="https://twitter.com/ayanonagon">Ayaka Nonaka</a> at <a href="https://do-ios.com/">DO iOS</a> 2016.</li></ul><h2>iOS Tools</h2><ul><li><a href="https://academy.realm.io/posts/appbuilders-jp-simard-sourcekit/"><em>SourceKit and You</em></a> by <a href="https://twitter.com/simjp">JP Simard</a> at <a href="https://appbuilders.ch/">App Builders</a> 2016.</li><li><a href="https://www.youtube.com/watch?v=HR-OMaXDgAo"><em>Making Your Own Tools Using SwiftSyntax</em></a> by <a href="https://twitter.com/kitasuke">Yusuke kita</a> at <a href="https://devworld.com.au/">/dev/world/</a> 2019.</li><li><a href="https://www.youtube.com/watch?v=UFyx7EtbcMU"><em>Designing a Design System</em></a> by <a href="https://twitter.com/freak4pc">Shai Mishali</a> at <a href="https://swiftheroes.com/">Swift Heroes</a> 2019.</li><li><a href="https://vimeo.com/337785976"><em>Taking the boilerplate out of your tests with Sourcery</em></a> by <a href="https://twitter.com/v_pradeilles">Vincent Pradeilles</a> at <a href="https://appdevcon.nl/">Appdevcon</a> 2019.</li><li><a href="https://www.youtube.com/watch?v=_MuDpBfuVOw&list=PLAVm70iJlMussetgTInUWGthl0LZ78MOT&index=10"><em>Engineering best practices, mi fan un baffo!</em></a> by <a href="https://twitter.com/mattt">Mattt</a> at <a href="https://www.pragmaconference.com/">Pragma Conference</a> 2019.</li><li><a href="https://www.youtube.com/watch?v=NAPeWoimGx8"><em>Postmortem for switching Lyft's iOS app to Bazel</em></a> by <a href="https://twitter.com/SmileyKeith">Keith Smiley</a> and <a href="https://twitter.com/kastiglione">Dave Lee</a> at <a href="https://bazel.build/">BazelCon</a> 2019.</li><li><a href="https://atscaleconference.com/videos/blazing-fast-scaling-ios-at-uber/"><em>Blazing fast: Scaling iOS at Uber</em></a> by <a href="https://twitter.com/alanzeino">Alan Zeino</a> and <a href="https://twitter.com/loyaltyarm">Nick Cobb</a> at <a href="https://atscaleconference.com/">At Scale</a> 2016.</li></ul><h2>Developer Life</h2><ul><li><a href="https://www.youtube.com/watch?v=mAiNdU1go1A"><em>Mind the gap, user centered design in large organizations</em></a> by <a href="https://twitter.com/LukeW">Luke Wroblewski</a> at <a href="https://www.youtube.com/user/conversionsatgoogle">Conversions Summit</a> 2019.</li><li><a href="https://www.youtube.com/watch?v=pAy6v4MEsnc&list=PLTFt3GGfH3hlCnH183ojUSbKtPmif6OdZ&index=16"><em>Static Site Generation In Swift</em></a> by <a href="https://twitter.com/johnsundell">JohnSundell</a> at <a href="https://www.serversideswift.info/">ServerSide.swift</a> 2019.</li><li><a href="https://vimeo.com/71278954"><em>The Future of Programming</em></a> by <a href="https://twitter.com/worrydream">Bret Victor</a> at DBX conference 2013.</li></ul><h2>Conclusions</h2><p>Seeing this list makes me so grateful to the amazing Swift/iOS community: regardless of where you are, you can learn from people all around the world, on the topic that matters to you, for free.</p><p>How did the challenge go? Well, I watched two or more talks per day for most of the challenge: my goal was to reduce my talks-to-watch backlog, I think it went very well!</p><p>And since WWDC is still a few weeks away, this extra time allows me to launch a new project, very WWDC and community focused, in the upcoming weeks 😉</p><p>Observe this space, <a href="https://www.fivestars.blog/feed.rss">rss here</a>, to not miss it!<br>Until then 👋🏻</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swift-scripts-preferences-userDefaults</guid><title>Swift CLI Tools User Preferences</title><description></description><link>https://www.fivestars.blog/articles/swift-scripts-preferences-userDefaults</link><pubDate>Thu, 30 Apr 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>It's common for apps to store user preferences and settings, the same is true for scripts:<br><a href="https://www.ssh.com/ssh/command/"><code>ssh</code></a> needs a place to store the user keys, <a href="https://git-scm.com"><code>git</code></a> a place to store the user name and email, etc.</p><p>While many CLI tools store such data in <a href="https://en.wikipedia.org/w/index.php?title=Dot_file&redirect=no">dot files</a>, in this article let's explore how we can use Foundation's UserDefaults!</p><h2>UserDefaults</h2><p>In the surface there's no real difference between using UserDefaults in a script or an app, for example:</p><ul><li>we can read and set any data in the UserDefault's <a href="https://developer.apple.com/documentation/foundation/userdefaults/1416603-standard"><code>standard</code></a> object:</li></ul><pre><code><span class="keyword">import</span> Foundation

<span class="keyword">let</span> userDefaults = <span class="type">UserDefaults</span>.<span class="property">standard</span>

userDefaults.<span class="call">set</span>(<span class="number">5</span>, forKey: <span class="string">"stars"</span>)
<span class="call">print</span>(userDefaults.<span class="call">integer</span>(forKey: <span class="string">"stars"</span>)) <span class="comment">// prints "5"</span>
</code></pre><blockquote><p>The <code>main.swift</code> content.</p></blockquote><ul><li>we can create and use different suites:</li></ul><pre><code><span class="keyword">import</span> Foundation

<span class="keyword">guard 
  let</span> userDefaults = <span class="type">UserDefaults</span>(suiteName: <span class="string">"five.stars"</span>) 
  <span class="keyword">else</span> { <span class="call">exit</span>(<span class="type">EXIT_FAILURE</span>) }

userDefaults.<span class="call">set</span>(<span class="number">5</span>, forKey: <span class="string">"stars"</span>)
<span class="call">print</span>(userDefaults.<span class="call">integer</span>(forKey: <span class="string">"stars"</span>)) <span class="comment">// prints "5"</span>
</code></pre><blockquote><p>The <code>main.swift</code> content.</p></blockquote><ul><li>we can even observe for <code>UserDefaults</code> changes:</li></ul><pre><code><span class="keyword">extension</span> <span class="type">UserDefaults</span> {
  <span class="keyword">@objc dynamic public var</span> stars: <span class="type">Int</span> {
    <span class="keyword">get</span> { <span class="call">integer</span>(forKey: <span class="keyword">#function</span>) }
    <span class="keyword">set</span> { <span class="call">set</span>(newValue, forKey: <span class="keyword">#function</span>) }
  }
}

<span class="keyword">guard 
  let</span> userDefaults = <span class="type">UserDefaults</span>(suiteName: <span class="string">"five.stars"</span>) 
  <span class="keyword">else</span> { <span class="call">exit</span>(<span class="type">EXIT_FAILURE</span>) }

<span class="keyword">let</span> token: <span class="type">NSKeyValueObservation</span> = userDefaults.<span class="call">observe</span>(
  \.<span class="property">stars</span>,
  options: [.<span class="dotAccess">initial</span>, .<span class="dotAccess">new</span>]
) { defaults, <span class="keyword">_ in
  let</span> rating = defaults.<span class="property">stars</span>
  <span class="keyword">let</span> fullStars = <span class="type">String</span>(repeating: <span class="string">"★"</span>, count: rating)
  <span class="keyword">let</span> emptyStars = <span class="type">String</span>(repeating: <span class="string">"☆"</span>, count: <span class="number">5</span> - rating)
  <span class="keyword">let</span> stars: <span class="type">String</span> = fullStars + emptyStars
  <span class="call">print</span>(stars)
  <span class="comment">// 👆🏻 prints the initial value and whenever a new change is made.</span>
}

<span class="type">RunLoop</span>.<span class="property">current</span>.<span class="call">run</span>()
<span class="comment">// 👆🏻 stops the script from terminating</span>
</code></pre><blockquote><p>The <code>main.swift</code> content.</p></blockquote><img src="https://www.fivestars.blog/assets/posts/swift-script-userdefaults/5s-ud.gif"/><blockquote><p>The script in action</p></blockquote><h2>UserDefaults Location</h2><p>Regardless of the platform we're running on, UserDefaults are always stored in <a href="https://en.wikipedia.org/wiki/Property_list">Property List</a> files, which are XML files in disguise, where the elements alternate between <code>key</code> tags and other elements types.</p><blockquote><p>This explains why we can store only just a few handful types in UserDefaults.</p></blockquote><p>In iOS apps, the <a href="https://developer.apple.com/documentation/foundation/userdefaults/1416603-standard"><code>standard</code></a> UserDefaults plist is stored in the app <a href="https://developer.apple.com/documentation/foundation/1413045-nshomedirectory ">home directory</a> under the <code>Library/Preferences</code> folder: the name of the file is the app bundle identifier, for example <code>blog.fivestars.app.plist</code>.</p><p>On macOS, all user applications UserDefaults are stored at <code>~/Library/Preferences</code>, therefore this folder not only contains our app plist file, but all other apps as well.</p><blockquote><p>This is one reason why our apps need to have an unique bundle identifiers.</p></blockquote><p>This works great for apps, but our scripts don't have any bundle identifier: where the UserDefaults data of our tools are stored? It turns out that scripts also use the same folder: instead of using a bundle identifier, the script <a href="https://developer.apple.com/documentation/foundation/userdefaults/1416603-standard"><code>standard</code></a> UserDefaults plist is stored under the name of the script.</p><blockquote><p>e.g. a Swift executable named <code>hello</code> will have its <code>standard</code> UserDefaults stored at <code>~/Library/Preferences/hello.plist</code>.</p></blockquote><p>What about UserDefaults suites? In this case the suite name will be the name of the plist file.</p><blockquote><p>e.g. a script using <code>UserDefaults(suiteName: "five.stars")</code> will have this suite data stored at <code>~/Library/Preferences/five.stars.plist</code>.</p></blockquote><p>Scripts are unsandboxed processes, hence they can read/write <em>any</em> UserDefaults file located in <code>~/Library/Preferences/</code>, all it takes is to know the plist name (a.k.a. the app bundle id):<br>want to read/edit the user preferences for...</p><ul><li>Xcode? Use <code>UserDefaults(suiteName: "com.apple.dt.Xcode")</code></li></ul><ul><li>Finder? Use <code>UserDefaults(suiteName: "com.apple.finder")</code>.</li></ul><ul><li>Etc.</li></ul><blockquote><p>For an easy way to explore even more preferences of both system and 3rd party apps, I suggest to use the free <a href="http://apps.tempel.org/PrefsEditor/index.php">Prefs Editor</a> app.</p></blockquote><h2>Conclusions</h2><p>In this article we've explored the behind the scenes of our scripts UserDefaults preferences, where they're persisted in our machines, and how scripts can actually access to all apps UserDefaults.</p><p>Do your CLI tools store any preferences? What kind of preferences do you store? Do you use UserDefaults? Please <a href="https://twitter.com/zntfdr">let me know</a>!</p><p>As always, thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/how-covid-19-contact-tracing-works</guid><title>How Apple's and Google’s Coronavirus Contact Tracing System Works</title><description></description><link>https://www.fivestars.blog/articles/how-covid-19-contact-tracing-works</link><pubDate>Mon, 13 Apr 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<blockquote><p>This article is based on the official preliminary technical specifications.</p></blockquote><p>Last Friday <a href="https://www.apple.com/newsroom/2020/04/apple-and-google-partner-on-covid-19-contact-tracing-technology/">Apple</a> and <a href="https://blog.google/inside-google/company-announcements/apple-and-google-partner-covid-19-contact-tracing-technology">Google</a> have announced a new partnership to fight against the current COVID-19 pandemic.</p><p>This partnership comes in two waves:</p><ol><li>Next month, May 2020, the companies will release an API, for each platform, that will enable apps <strong>from public health authorities</strong> to assist in <em>enabling</em> contact tracing access. This will require users to download such apps from the platform app stores.</li></ol><ol start="2"><li>In the <em>coming months</em> both companies will embed a Contact Tracing system at the OS level.</li></ol><p>This article focuses on the second point.</p><blockquote><p>If you'd like a less technical article, please <a href="https://www.theverge.com/2020/4/10/21216715/apple-google-coronavirus-covid-19-contact-tracing-app-details-use">refer to this</a>.</p></blockquote><h2>Contact Tracing TL;DR</h2><p>Contact Tracing alerts participants of possible exposure to someone who has been positively diagnosed with corona virus.</p><p>In other words, Contact Tracing reports when/if our device has been in the proximity of a person diagnosed with corona virus device.</p><h2>Technical Details</h2><h3>Inter-Device Communication</h3><p>Contact Tracing uses <a href="https://en.wikipedia.org/wiki/Bluetooth_Low_Energy">Bluetooth LE (Low Energy)</a> for inter-device communication, all iPhones from the iPhone 4S are equipped with it, however I believe Contact Tracing will be released as an iOS 13-only update, which means only iPhones from the iPhone 6s (and 6s Plus) are eligible.</p><p>Bluethooth LE has a theoretical maximum distance/range of 100m, however the effective range is far less than that.</p><p>Similar to WiFi device discovery, the Contact Tracing is based on <strong>advertising</strong>, a.k.a. Bluetooth payloads that our device sends out to anyone within reach, and <strong>scanning</strong>, which is receiving and reading other devices advertisements.</p><p>No connection between devices is ever made:<br>all devices will literally throw messages out in the air and read whatever comes in.</p><h4>Advertising</h4><p>Advertisements will happen at a high frequency: the current specification suggests to send out one advertisement every 200-270 milliseconds.</p><h4>Scanning</h4><p>While advertisements happen with high frequency, the scanning interval and window only need to have sufficient coverage to discover nearby advertisers within 5 minutes.</p><p>Which means that the scanning approach is <em>opportunistic</em>: Contact Tracing scans will piggyback existing device wakes and scan windows (our devices are constantly scanning for WiFi networks and Bluetooth devices), and will do so at least once every 5 minutes.</p><h3>Advertising Message</h3><p>The whole system is based on devices Bluetooth advertisements. These advertisements are all the same for all devices of all platforms, the only difference is a traceable key.<br><br>Contact Tracing defines three keys:</p><ul><li><strong>Tracing Key</strong> An unique key that is generated once per device.</li></ul><ul><li><strong>Daily Tracing Key</strong> A key derived from the Tracing Key, that changes every 24 hours.</li></ul><ul><li><strong>Rolling Proximity Identifier</strong> A key derived from the Daily Tracing Key, that changes every 10 to 20 minutes.</li></ul><p>The Bluetooth advertisement will <strong>always only</strong> contain the current Rolling Proximity Identifier key.</p><blockquote><p>Why does the system need three keys? It doesn't:Contact Tracing would work fine with one unique key per device. These three levels are there for user privacy and safety: - The Tracing Key never leaves the device. - The Daily (Tracing) Key change is there for user privacy. - The ~15 minutes (Rolling Proximity) Key change is there to prevent wireless tracking.</p></blockquote><h3>Scanning Behavior</h3><p>The scanning part translates into collecting other devices Rolling Proximity Identifier keys:<br>such keys are stored and processed exclusively on device.</p><h3>Putting It All Together</h3><h4>What Happens When A User Tests Positive for COVID-19</h4><p>When a user tests positive, it will let the system know (it's unclear <em>how</em> at this stage, I assume via a new app or via the platform Health app) and the relevant user's device Daily Tracing Keys will be uploaded to a <strong>Diagnosis Server</strong>, along with the relative days of each key.</p><blockquote><p>This is the only possible way for the Daily Tracing Keys to leave the device.</p></blockquote><p>A Diagnosis Server is a server that aggregates all Daily Tracing Keys from the users who tested positive, and distributes them to all the users devices (of all platforms) who are using Contact Tracing.</p><h5>Why The Daily Tracing Keys Are Uploaded?</h5><p>A Rolling Proximity Identifier is derived by its Daily Tracing Key and a 10-minute window (remember that this key changes every 10 to 20 minutes?).</p><p>Given a Daily Tracing Key, we can obtain the complete sequence of Rolling Proximity Identifier keys for that day.</p><h4>Identify Any Exposure</h4><p>At this point we know that:</p><ul><li>Each device collects all Rolling Proximity Identifiers advertised by other nearby devices.</li></ul><ul><li>Daily Tracing Keys from diagnosed users devices are uploaded to a Diagnosis Server.</li></ul><ul><li>Given a Daily Tracing Key, we can obtain all its associated Rolling Proximity Identifiers.</li></ul><p>Every device will <em>frequently</em> (it's unclear <em>how</em> frequent at the moment) fetch the list of Daily Tracing Keys from the Diagnosis Server.</p><p>The last step is to compute all Rolling Proximity Identifier sequences for each Daily Tracing Keys fetched from the Diagnosis Server, and try to find a match with the local Rolling Proximity Identifiers collection.</p><p>If a match is found, the user will be alerted. The match information never leaves the device.</p><h2>Conclusions</h2><p>In this article we've seen how the Coronavirus Contact Tracing works, for more detailed information please refer to the official <a href="https://covid19-static.cdn-apple.com/applications/covid19/current/static/contact-tracing/pdf/ContactTracing-BluetoothSpecificationv1.1.pdf">Bluetooth</a> and <a href="https://covid19-static.cdn-apple.com/applications/covid19/current/static/contact-tracing/pdf/ContactTracing-CryptographySpecification.pdf">Cryptography specifications</a>.</p><p>The system is not flawless and can report false positives, albeit chances are low.</p><blockquote><p>For example: Additional validation can be done on the match step to prevent some false positives: if a computed Rolling Proximity Identifier associated with a 11:00 pm - 11:10 pm interval matches a Rolling Proximity Identifier collected at 8:00 am, chances are this is a coincidence and not a real match.</p></blockquote><p>One more concerning aspect of this system is the fact that all the (very good) privacy/security enforcements come with the cost of a moderation-less system: Diagnosis Server do not store any data beside the given keys and dates. Malicious actors may flood the servers with fake cases.</p><blockquote><p>Please <a href="https://twitter.com/zntfdr">let me know</a> if I missed something that would prevent this behavior.</p></blockquote><blockquote><p>2020/04/14 update: during a <a href="https://www.androidpolice.com/2020/04/13/apple-and-google-are-working-together-to-fight-coronavirus-with-a-new-contact-tracing-tool/">press briefing</a> on Monday April 13th the companies have announced that they are "considering working with healthcare operators to incorporate a lockstep test verification", this would solve the issue raised above.</p></blockquote><p>Lastly, this system will work only if everybody does their part:<br>once the system is available, please opt-in and, more importantly, #stayAtHome.</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p><p>References:</p><ul><li><a href="https://www.apple.com/covid19/contacttracing/">Privacy-Preserving Contact Tracing</a></li><li><a href="https://en.wikipedia.org/wiki/Bluetooth_Low_Energy">Bluetooth Low Energy</a></li><li><a href="https://covid19-static.cdn-apple.com/applications/covid19/current/static/contact-tracing/pdf/ContactTracing-BluetoothSpecificationv1.1.pdf">Contact Tracing - Bluetooth Specification</a></li><li><a href="https://covid19-static.cdn-apple.com/applications/covid19/current/static/contact-tracing/pdf/ContactTracing-CryptographySpecification.pdf">Contact Tracing - Cryptography Specification</a></li></ul>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swift-subcommands-galore</guid><title>How To Add Extra Commands To CLI Tools</title><description></description><link>https://www.fivestars.blog/articles/swift-subcommands-galore</link><pubDate>Wed, 1 Apr 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>In <a href="https://www.fivestars.blog/articles/ultimate-guide-swift-executables/">previous</a> <a href="https://www.fivestars.blog/articles/executables-argument-kind/">entries</a> we've <a href="https://www.fivestars.blog/articles/a-look-into-argument-parser/">extensively</a> <a href="https://www.fivestars.blog/articles/executables-progress/">covered</a> how we can build a new command line tool from scratch.</p><p>While this is great for brand new ideas, sometimes all we need is to add an extra feature to an existing tool: in this article we're going to do just that.</p><h2>Example</h2><p>Let's say that we would like to add a new <a href="https://git-scm.com"><code>git</code></a> feature for <em>grading</em> the current code base (😱):<br>in order to do so we will create a new <code>git</code> <strong>subcommand</strong>, <code>$ git star</code>, and we're also going to use <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>!</p><p>For simplicity's sake, our subcommand will ask for a rating from 1 to 5 as an input, and print it out in the terminal. But there's truly no limit on what can we can do.</p><p>Let's get started!</p><h2>Project setup</h2><p>First we need to <a href="https://www.fivestars.blog/articles/ultimate-guide-swift-executables/">create a new Swift Package Executable</a>.</p><h3>Naming Convention</h3><p>The package name doesn't really matter, however I suggest you to follow name patterns such as <code>git-star</code>, where the prefix is the tool we're extending, and the postfix is our subcommand, as those are patterns used by similar tools, and also the final name of our binary.</p><p>In our example we will do the following:</p><pre><code>$ mkdir git-star
$ cd git-star
$ swift package <span class="keyword">init</span> --type executable
</code></pre><h3>Adding ArgumentParser Dependency</h3><p>Any swift command tool needs some kind of input: in this example we're going to use Swift's <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>, which we have <a href="https://www.fivestars.blog/articles/ultimate-guide-swift-executables/">covered</a> <a href="https://www.fivestars.blog/articles/a-look-into-argument-parser/">previously</a>.</p><p><strong>TL;DR:</strong> your <code>Package.swift</code> should look like this:</p><pre><code><span class="comment">// swift-tools-version:5.1
// The swift-tools-version declares the minimum version of Swift required to build this package.</span>

<span class="keyword">import</span> PackageDescription

<span class="keyword">let</span> package = <span class="type">Package</span>(
    name: <span class="string">"git-swift"</span>,
    dependencies: [
        <span class="comment">// Dependencies declare other packages that this package depends on.</span>
        .<span class="call">package</span>(url: <span class="string">"https://github.com/apple/swift-argument-parser"</span>, from: <span class="string">"0.0.1"</span>),
    ],
    targets: [
        <span class="comment">// Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages which this package depends on.</span>
        .<span class="call">target</span>(
            name: <span class="string">"git-swift"</span>,
            dependencies: [<span class="string">"ArgumentParser"</span>]),
        .<span class="call">testTarget</span>(
            name: <span class="string">"git-swiftTests"</span>,
            dependencies: [<span class="string">"git-swift"</span>]),
    ]
)
</code></pre><blockquote><p>The <code>Package.swift</code> content.</p></blockquote><h2>main.swift</h2><p>At this point all is left is to write the actual command, without further ado:</p><pre><code><span class="keyword">import</span> ArgumentParser

<span class="keyword">struct</span> GitStar: <span class="type">ParsableCommand</span> {
    <span class="keyword">@Argument</span>(help: <span class="string">"Your code rating (1-5 only)."</span>)
    <span class="keyword">var</span> rating: <span class="type">Int</span>

    <span class="keyword">func</span> run() <span class="keyword">throws</span> {
        <span class="keyword">let</span> fullStars = <span class="type">String</span>(repeating: <span class="string">"★"</span>, count: rating)
        <span class="keyword">let</span> emptyStars = <span class="type">String</span>(repeating: <span class="string">"☆"</span>, count: <span class="number">5</span> - rating)
        <span class="keyword">let</span> stars: <span class="type">String</span> = fullStars + emptyStars
        <span class="call">print</span>(<span class="string">"Your rating</span> \(stars)<span class="string">"</span>)
    }

    <span class="keyword">func</span> validate() <span class="keyword">throws</span> {
        <span class="keyword">guard</span> <span class="number">1</span>...<span class="number">5</span> ~= rating <span class="keyword">else</span> {
            <span class="keyword">throw</span> <span class="type">ValidationError</span>(<span class="string">"Only ratings between 1 to 5 allowed."</span>)
        }
    }
}

<span class="type">GitStar</span>.<span class="call">main</span>()
</code></pre><blockquote><p>The <code>main.swift</code> content.</p></blockquote><p>The script is nothing out of the ordinary, with standard <code>validate()</code> and <code>run()</code> logics.</p><p>As we've seen in <a href="https://www.fivestars.blog/articles/a-look-into-argument-parser/">A Look Into ArgumentParser</a>, it's important that the name of our <code>ParsableCommand</code> type translates correctly into the final command line (unless defined otherwise via <a href="https://github.com/apple/swift-argument-parser/blob/6f58e68a6aa3b03bc2cbc36f340f2c12ca5775c3/Sources/ArgumentParser/Parsable%20Types/CommandConfiguration.swift#L13"><code>CommandConfiguration</code></a>).</p><p>The name <code>GitStar</code> translates into <code>git-star</code>, and we can verify so by running <code>$ swift run git-star --help</code>:</p><pre><code>$ swift run git-star --help
&gt; <span class="type">USAGE</span>: git-star &lt;rating&gt;
&gt; 
&gt; <span class="type">ARGUMENTS</span>:
&gt;   &lt;rating&gt;                <span class="type">Your</span> code rating (<span class="number">1</span>-<span class="number">5</span> only).

&gt; <span class="type">OPTIONS</span>:
&gt;   -h, --help              <span class="type">Show</span> help information.
</code></pre><blockquote><p>See the <code>USAGE</code> callout.</p></blockquote><p>Note that this output has no relation whatsoever with the package name or product. If we rename the struct to <code>FiveStars</code> for example, the same command will have different output:</p><pre><code>$ swift run git-star --help
&gt; <span class="type">USAGE</span>: five-stars &lt;rating&gt;
&gt; 
&gt; <span class="type">ARGUMENTS</span>:
&gt;   &lt;rating&gt;                <span class="type">Your</span> code rating (<span class="number">1</span>-<span class="number">5</span> only).

&gt; <span class="type">OPTIONS</span>:
&gt;   -h, --help              <span class="type">Show</span> help information.
</code></pre><blockquote><p>See the <code>USAGE</code> callout.</p></blockquote><h2>Releasing The Script</h2><p><a href="https://www.fivestars.blog/articles/ultimate-guide-swift-executables/">We've covered this before</a>:</p><pre><code>$ swift build -c release
$ cp .<span class="dotAccess">build</span>/release/git-star /usr/local/bin/git-star
</code></pre><h2>The Caveat</h2><p>When an executable placed in <code>/usr/local/bin/</code> has a dash <code>-</code> in its name, and the original command (the prefix) accepts subcommands, then we can run our command with and without the dash, which means that we can now run <code>$ git star</code> from anywhere:</p><pre><code>$ git star <span class="number">5</span>
&gt; <span class="type">Your</span> rating ★★★★★

$ git star
&gt; <span class="type">Error</span>: <span class="type">Missing</span> expected argument '&lt;rating&gt;'
&gt; <span class="type">Usage</span>: git-star &lt;rating&gt;

$ git star -h
&gt; <span class="type">USAGE</span>: five-stars &lt;rating&gt;
&gt; 
&gt; <span class="type">ARGUMENTS</span>:
&gt;   &lt;rating&gt;                <span class="type">Your</span> code rating (<span class="number">1</span>-<span class="number">5</span> only).
&gt; 
&gt; <span class="type">OPTIONS</span>:
&gt;   -h, --help              <span class="type">Show</span> help information.
</code></pre><blockquote><p>The same approach works regardless of the origin/nature of the executable: even a renamed bash file would do the trick.</p></blockquote><h2>Conclusions</h2><p>While we've built a basic example, it's easy to imagine how we can use this approach to add big, powerful features directly in <code>git</code> and any other tool.</p><p>Also please note that this tutorial doesn't work with all executables: some (👋🏻 <code>xcrun</code>) require extra work (or different paths) in order to achieve the same result.</p><p>Lastly, if you need inspiration on what you can build with this new knowledge, please have a look at <a href="https://github.com/kamranahmedse/git-standup"><code>git-standup</code></a> and <a href="https://github.com/kiliankoe/swift-outdated"><code>swift-outdated</code></a>.</p><p>Do you use any tool with this approach? Have you built or are you planning to make one yourself? <a href="https://twitter.com/zntfdr">Please let me know!</a></p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/what-s-new-in-swift-5-2</guid><title>What's New In Swift 5.2 🏎</title><description></description><link>https://www.fivestars.blog/articles/what-s-new-in-swift-5-2</link><pubDate>Wed, 25 Mar 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Just a little over 6 months after the announcement of <a href="https://www.fivestars.blog/articles/what-s-new-in-swift-5-1/">Swift 5.1</a>, we have a new Swift dot release:<br>Swift 5.2.</p><p>Despite being smaller in comparison to 5.1, Swift 5.2 brings so many new enhancements such as better diagnostics, more keypaths use, subscripts default arguments, and much more.</p><p>For the <a href="https://www.eventpop.me/e/8171-iosdevth-19#event-tickets">19th</a> <a href="https://www.facebook.com/groups/iosthailand/">iOSDevTH meetup</a> I've prepared a what's new in Swift 5.2 presentation:<br>one slide per change, a small comment, and a code example.</p><p>You can see the whole presentation slides here:</p><p><a href="https://speakerdeck.com/zntfdr/whats-new-in-swift-5-dot-2"><img src="https://www.fivestars.blog/assets/posts/what-s-new-in-swift-5-2/first-slide.jpg" alt="slides"/></a></p><blockquote><p>Check out the full slides at <a href="https://speakerdeck.com/zntfdr/whats-new-in-swift-5-dot-2">SpeakerDeck</a></p></blockquote><p>I really like this format, because it lets me quickly revisit all the changes in a simple way: if you download the slides <a href="https://github.com/zntfdr/talks/blob/master/2020%20Whats%20New%20In%20Swift%205.2/whats_new_in_swift_5_2.pdf">pdf</a>, each slide also links to the associated Swift Evolution or Swift Report.</p><p>As with my <a href="https://www.fivestars.blog/articles/what-s-new-in-swift-5-1/">previous</a> <a href="https://www.fivestars.blog/articles/the-state-of-machine-learning-in-ios-13/">talks</a>, I've made the slides, along with other materials, available to download in the <a href="https://github.com/zntfdr/talks"><code>talks</code> repository</a>.</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/executables-progress</guid><title>Swift Executables Progress State ⏳</title><description></description><link>https://www.fivestars.blog/articles/executables-progress</link><pubDate>Tue, 24 Mar 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>In the <a href="https://www.fivestars.blog/articles/ultimate-guide-swift-executables/">Swift Executables Guide</a> we've seen how, among other things, we can use <a href="https://github.com/apple/swift-tools-support-core/ "><code>TSCUtility</code></a> to show progress to our users while our script is busy.</p><p>Since the outcome of these APIs are very visual, in this new article let's cover all <code>TSCUtility</code> <a href="https://github.com/apple/swift-tools-support-core/blob/fcaa2ce5a852b5355aed5808a6610dc8b6dcf27e/Sources/TSCUtility/ProgressAnimation.swift">animations</a>!</p><h2>ProgressAnimationProtocol</h2><p>When looking at <code>TSCUtility</code>'s <a href="https://github.com/apple/swift-tools-support-core/blob/fcaa2ce5a852b5355aed5808a6610dc8b6dcf27e/Sources/TSCUtility/ProgressAnimation.swift"><code>ProgressAnimation.swift</code></a> file, the first thing that pops up is that all animations conform to the <a href="https://github.com/apple/swift-tools-support-core/blob/d57fc3cd2fab8d0c23fdd23bbc9880c6ae3b0858/Sources/TSCUtility/ProgressAnimation.swift#L14"><code>ProgressAnimationProtocol</code> protocol</a>, here's its definition:</p><pre><code><span class="comment">/// A protocol to operate on terminal based progress animations.</span>
<span class="keyword">public protocol</span> ProgressAnimationProtocol {
  <span class="comment">/// Update the animation with a new step.
  /// - Parameters:
  ///   - step: The index of the operation's current step.
  ///   - total: The total number of steps before the operation is complete.
  ///   - text: The description of the current step.</span>
  <span class="keyword">func</span> update(step: <span class="type">Int</span>, total: <span class="type">Int</span>, text: <span class="type">String</span>)

  <span class="comment">/// Complete the animation.
  /// - Parameters:
  ///   - success: Defines if the operation the animation represents was succesful.</span>
  <span class="keyword">func</span> complete(success: <span class="type">Bool</span>)

  <span class="comment">/// Clear the animation.</span>
  <span class="keyword">func</span> clear()
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-tools-support-core/ "><code>TSCUtility</code></a>’s <a href="https://github.com/apple/swift-tools-support-core/blob/fcaa2ce5a852b5355aed5808a6610dc8b6dcf27e/Sources/TSCUtility/ProgressAnimation.swift"><code>ProgressAnimation.swift</code></a>.</p></blockquote><p>Every animation is responsible to take care of any interpolation needed, and correctly display our progress (by means of progress percentage, or else).</p><p>To start an animation, we call <code>update(step:total:text:)</code>, and we keep doing so until the job has been completed, which is when we let the animation know via <code>complete(success:)</code>.</p><p>Lastly, the <code>ProgressAnimationProtocol</code> requires a <code>clear</code> function, which tells the animation to remove itself, allowing the terminal to proceed as if the animation was never shown.</p><p>Animations are <em>just</em> "print" statements: there's no requirement on the order of the <code>update</code>/<code>complete</code>/<code>clear</code> calls, it's entirely up to us.</p><h2>DynamicProgressAnimation</h2><p>Different terminals types support different <a href="https://en.wikipedia.org/wiki/Control_character">control codes</a>. Some animations are possible only if their control codes are available (e.g. to control the terminal cursor position and clear terminal lines).<br><br>In order to support all terminal types, <code>TSCUtility</code> defines <a href="https://github.com/apple/swift-tools-support-core/blob/7b1b8195cb83c20e1f24f384140be9edad523aa5/Sources/TSCUtility/ProgressAnimation.swift#L271"><code>DynamicProgressAnimation</code></a>, which selects a different animation based on the terminal capability:</p><pre><code><span class="comment">/// A progress animation that adapts to the provided output stream.</span>
<span class="keyword">public class</span> DynamicProgressAnimation: <span class="type">ProgressAnimationProtocol</span> {
  <span class="keyword">private let</span> animation: <span class="type">ProgressAnimationProtocol</span>

  <span class="keyword">public init</span>(
    stream: <span class="type">OutputByteStream</span>,
    ttyTerminalAnimationFactory: (<span class="type">TerminalController</span>) -&gt; <span class="type">ProgressAnimationProtocol</span>,
    dumbTerminalAnimationFactory: () -&gt; <span class="type">ProgressAnimationProtocol</span>,
    defaultAnimationFactory: () -&gt; <span class="type">ProgressAnimationProtocol</span>
  ) {
    <span class="keyword">if let</span> terminal = <span class="type">TerminalController</span>(stream: stream) {
      animation = <span class="call">ttyTerminalAnimationFactory</span>(terminal)
    } <span class="keyword">else if let</span> fileStream = stream <span class="keyword">as</span>? <span class="type">LocalFileOutputByteStream</span>,
      <span class="type">TerminalController</span>.<span class="call">terminalType</span>(fileStream) == .<span class="call">dumb</span> {
      animation = <span class="call">dumbTerminalAnimationFactory</span>()
    } <span class="keyword">else</span> {
      animation = <span class="call">defaultAnimationFactory</span>()
    }
  }

  <span class="keyword">public func</span> update(step: <span class="type">Int</span>, total: <span class="type">Int</span>, text: <span class="type">String</span>) {
    animation.<span class="call">update</span>(step: step, total: total, text: text)
  }

  <span class="keyword">public func</span> complete(success: <span class="type">Bool</span>) {
    animation.<span class="call">complete</span>(success: success)
  }

  <span class="keyword">public func</span> clear() {
    animation.<span class="call">clear</span>()
  }
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-tools-support-core/ "><code>TSCUtility</code></a>’s <a href="https://github.com/apple/swift-tools-support-core/blob/fcaa2ce5a852b5355aed5808a6610dc8b6dcf27e/Sources/TSCUtility/ProgressAnimation.swift"><code>ProgressAnimation.swift</code></a>.</p></blockquote><p><code>DynamicProgressAnimation</code> takes in three animation factories and uses one of them depending on the given <code>stream</code>, which is an object conforming to the <a href="https://github.com/apple/swift-tools-support-core/blob/f1162ddd0e05e47bf4ae1ae2066332e32e3521fa/Sources/TSCBasic/OutputByteStream.swift#L46"><code>OutputByteStream</code></a> protocol, used to manage different output destinations.</p><p><code>TSCUtility</code> provides two <code>DynamicProgressAnimation</code> subclasses ready for us to use, <code>NinjaProgressAnimation</code> and <code>PercentProgressAnimation</code>.</p><h2>NinjaProgressAnimation</h2><pre><code><span class="comment">/// A ninja-like progress animation that adapts to the provided output stream.</span>
<span class="keyword">public final class</span> NinjaProgressAnimation: <span class="type">DynamicProgressAnimation</span> {
  <span class="keyword">public init</span>(stream: <span class="type">OutputByteStream</span>) {
    <span class="keyword">super</span>.<span class="keyword">init</span>(
      stream: stream,
      ttyTerminalAnimationFactory: { <span class="type">RedrawingNinjaProgressAnimation</span>(terminal: $0) },
      dumbTerminalAnimationFactory: { <span class="type">SingleLinePercentProgressAnimation</span>(stream: stream, header: <span class="keyword">nil</span>) },
      defaultAnimationFactory: { <span class="type">MultiLineNinjaProgressAnimation</span>(stream: stream) }
    )
  }
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-tools-support-core/ "><code>TSCUtility</code></a>’s <a href="https://github.com/apple/swift-tools-support-core/blob/fcaa2ce5a852b5355aed5808a6610dc8b6dcf27e/Sources/TSCUtility/ProgressAnimation.swift"><code>ProgressAnimation.swift</code></a>.</p></blockquote><p><code>NinjaProgressAnimation</code> defines the three factories for the different terminal types and nothing else:</p><ul><li>in a tty terminal we have <a href="https://github.com/apple/swift-tools-support-core/blob/7b1b8195cb83c20e1f24f384140be9edad523aa5/Sources/TSCUtility/ProgressAnimation.swift#L106"><code>RedrawingNinjaProgressAnimation</code></a>:</li></ul><img src="https://www.fivestars.blog/assets/posts/executables-progress/RedrawingNinjaProgressAnimation.gif" alt="large-contents"/><p>the animation clears itself at every update and "redraws" the new state on the same line.<br> This animation commit its line if we call <code>complete(:)</code> on it, or it removes itself if we call <code>clear()</code>.</p><ul><li>in a <a href="https://en.wikipedia.org/wiki/Computer_terminal#Dumb_terminals">dumb</a> terminal we have <a href="https://github.com/apple/swift-tools-support-core/blob/7b1b8195cb83c20e1f24f384140be9edad523aa5/Sources/TSCUtility/ProgressAnimation.swift#L32"><code>SingleLinePercentProgressAnimation</code></a>:</li></ul><img src="https://www.fivestars.blog/assets/posts/executables-progress/SingleLinePercentProgressAnimation.gif" alt="large-contents"/><p>which draws the same animation without clearing the line first.<br> This animation also ignores <code>clear()</code> calls.</p><ul><li>lastly, we have a default animation when our stream doesn't fit the cases above, <a href="https://github.com/apple/swift-tools-support-core/blob/7b1b8195cb83c20e1f24f384140be9edad523aa5/Sources/TSCUtility/ProgressAnimation.swift#L73"><code>MultiLineNinjaProgressAnimation</code></a>:</li></ul><img src="https://www.fivestars.blog/assets/posts/executables-progress/MultiLineNinjaProgressAnimation.gif" alt="large-contents"/><p>This is a middle ground between the previous two animations, where this new animation has the capability to return and create a new line. However no clear capabilities are used, which means that also in this case <code>clear()</code> calls are ignored.</p><p>If this animation looks familiar, it's because it is! For example, we use it every time a package needs to resolve, download, and compile any package dependency:</p><img src="https://www.fivestars.blog/assets/posts/executables-progress/build.gif" alt="large-contents"/><blockquote><p>You can see the <code>RedrawingNinjaProgressAnimation</code> in the line before the last one in the terminal.</p></blockquote><p>Here's the code to test the animation:</p><pre><code><span class="keyword">import</span> Darwin
<span class="keyword">import</span> TSCBasic
<span class="keyword">import</span> TSCUtility

<span class="keyword">let</span> animation = <span class="type">NinjaProgressAnimation</span>(stream: stdoutStream)

<span class="keyword">for</span> i <span class="keyword">in</span> <span class="number">0</span>..&lt;<span class="number">100</span> {
  <span class="keyword">let</span> second: <span class="type">Double</span> = <span class="number">1_000_000</span>
  <span class="call">usleep</span>(<span class="type">UInt32</span>(second * <span class="number">0.05</span>))
  animation.<span class="call">update</span>(step: i, total: <span class="number">100</span>, text: <span class="string">"Loading.."</span>)
}

animation.<span class="call">complete</span>(success: <span class="keyword">true</span>) <span class="comment">// or animation.clear()</span>
</code></pre><blockquote><p>Create a new <a href="https://www.fivestars.blog/articles/ultimate-guide-swift-executables/">swift executable</a> and replace the <code>main.swift</code> with the code above.</p></blockquote><blockquote><p>Why Ninja? I guess this refers to the <code>[currentStep/totalSteps]</code> pattern... The forward slash <code>/</code> looks like a sword, right? 😅</p></blockquote><blockquote><p>Update: <a href="https://twitter.com/aciidb0mb3r">Ankit Aggarwal</a> from the Swift team <a href="https://twitter.com/aciidb0mb3r/status/1242480330805661698">pointed out</a> that this animation is based on the same animation from the <a href="https://ninja-build.org">ninja build tool</a> (you can see it mentioned <a href="https://github.com/ninja-build/ninja/blob/b50e1e3bc636abd75cb7aa444aeb397cc324e325/doc/manual.asciidoc#environment-variables">here</a>), hence the name. Thank you Ankit!</p></blockquote><h2>PercentProgressAnimation</h2><pre><code><span class="comment">/// A percent-based progress animation that adapts to the provided output stream.</span>
<span class="keyword">public final class</span> PercentProgressAnimation: <span class="type">DynamicProgressAnimation</span> {
    <span class="keyword">public init</span>(stream: <span class="type">OutputByteStream</span>, header: <span class="type">String</span>) {
        <span class="keyword">super</span>.<span class="keyword">init</span>(
            stream: stream,
            ttyTerminalAnimationFactory: { <span class="type">RedrawingLitProgressAnimation</span>(terminal: $0, header: header) },
            dumbTerminalAnimationFactory: { <span class="type">SingleLinePercentProgressAnimation</span>(stream: stream, header: header) },
            defaultAnimationFactory: { <span class="type">MultiLinePercentProgressAnimation</span>(stream: stream, header: header) })
    }
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-tools-support-core/ "><code>TSCUtility</code></a>’s <a href="https://github.com/apple/swift-tools-support-core/blob/fcaa2ce5a852b5355aed5808a6610dc8b6dcf27e/Sources/TSCUtility/ProgressAnimation.swift"><code>ProgressAnimation.swift</code></a>.</p></blockquote><p>Identically to <code>NinjaProgressAnimation</code>, <code>PercentProgressAnimation</code> defines the three factories for different terminal types:</p><ul><li>in a tty terminal we have <a href="https://github.com/apple/swift-tools-support-core/blob/7b1b8195cb83c20e1f24f384140be9edad523aa5/Sources/TSCUtility/ProgressAnimation.swift#L195"><code>RedrawingLitProgressAnimation</code></a>:</li></ul><img src="https://www.fivestars.blog/assets/posts/executables-progress/RedrawingLitProgressAnimation.gif" alt="large-contents"/><p>as before, the animation <em>clears</em> and <em>redraws</em> itself.</p><ul><li>in a <a href="https://en.wikipedia.org/wiki/Computer_terminal#Dumb_terminals">dumb</a> terminal we get <a href="https://github.com/apple/swift-tools-support-core/blob/7b1b8195cb83c20e1f24f384140be9edad523aa5/Sources/TSCUtility/ProgressAnimation.swift#L32"><code>SingleLinePercentProgressAnimation</code></a>, which is the same as we've seen in <code>NinjaProgressAnimation</code>, plus a header:</li></ul><img src="https://www.fivestars.blog/assets/posts/executables-progress/SingleLinePercentProgressAnimationHeader.gif" alt="large-contents"/><ul><li>lastly, we have <a href="https://github.com/apple/swift-tools-support-core/blob/7b1b8195cb83c20e1f24f384140be9edad523aa5/Sources/TSCUtility/ProgressAnimation.swift#L154"><code>MultiLinePercentProgressAnimation</code></a> for the default animation:</li></ul><img src="https://www.fivestars.blog/assets/posts/executables-progress/MultiLinePercentProgressAnimation.gif" alt="large-contents"/><p>A way to see this animation in the wild is by using the <code>swift test</code> tool, the progress animation will be shown when we do parallel testing and a special environment variable is set.</p><p>Here's an example of me running the <code>FunctionalTests</code> in the <code>swift-package-manager</code>:</p><pre><code><span class="type">SWIFTPM_TEST_RUNNER_PROGRESS_BAR</span>=lit swift test --filter <span class="type">FunctionalTests</span> --parallel
</code></pre><img src="https://www.fivestars.blog/assets/posts/executables-progress/test.gif" alt="large-contents"/><p>Here's the code to test this animation:</p><pre><code><span class="keyword">import</span> Darwin
<span class="keyword">import</span> TSCBasic
<span class="keyword">import</span> TSCUtility

<span class="keyword">let</span> animation = <span class="type">PercentProgressAnimation</span>(stream: stdoutStream, header: <span class="string">"Five Stars"</span>)

<span class="keyword">for</span> i <span class="keyword">in</span> <span class="number">0</span>..&lt;<span class="number">100</span> {
  <span class="keyword">let</span> second: <span class="type">Double</span> = <span class="number">1_000_000</span>
  <span class="call">usleep</span>(<span class="type">UInt32</span>(second * <span class="number">0.05</span>))
  animation.<span class="call">update</span>(step: i, total: <span class="number">100</span>, text: <span class="string">"Loading.."</span>)
}

animation.<span class="call">complete</span>(success: <span class="keyword">true</span>) <span class="comment">// or animation.clear()</span>
</code></pre><blockquote><p>Create a new <a href="https://www.fivestars.blog/articles/ultimate-guide-swift-executables/">swift executable</a> and replace the <code>main.swift</code> with the code above.</p></blockquote><h2>Conclusions</h2><p>In this article we've seen how progress animations are defined in <code>TSCUtility</code>, how different terminals require different animations, and how <code>TSCUtility</code> standardizes them by defining three types of animations:</p><ul><li><code>Redrawing</code> when the animation can redraw itself in place</li><li><code>SingleLine</code> when the whole progress is "printed" in one line</li><li><code>MultiLine</code> when we print the progress state in a new line at every update.</li></ul><p>If you need to show progress to in your scripts, you now know where to look!</p><p>As always, any <a href="https://twitter.com/zntfdr">feedback and insight</a> from your side is more than welcome.</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/a-look-into-argument-parser</guid><title>A Look Into ArgumentParser</title><description></description><link>https://www.fivestars.blog/articles/a-look-into-argument-parser</link><pubDate>Tue, 17 Mar 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>The Swift team has <a href="https://swift.org/blog/argument-parser/">recently announced</a> <a href="https://github.com/apple/swift-argument-parser"><code>ArgumentParser</code></a>, a new parse command-line argument library.</p><p>In <a href="https://www.fivestars.blog/articles/ultimate-guide-swift-executables/">previous</a> <a href="https://www.fivestars.blog/articles/executables-argument-kind/">entries</a> we've covered how we can parse command-line arguments <a href="https://www.fivestars.blog/articles/ultimate-guide-swift-executables/">manually</a>, and with <a href="https://www.fivestars.blog/articles/executables-argument-kind/"><code>TSCUtility</code></a>:</p><p><code>ArgumentParser</code> comes with an <a href="https://github.com/apple/swift-argument-parser/tree/339baea0a5d8c9b26c6e9be47df8e713f6e26234/Documentation">extensive and very well written documentation</a>, please give it a read for a complete overview of the library API.</p><p>With the <a href="https://www.fivestars.blog/articles/ultimate-guide-swift-executables/">basics covered</a>, in this article we're going to dive into how things are implemented under the hood, like we did for the <a href="https://www.fivestars.blog/articles/the-swift-behind-the-standard-library-preview-package/">Swift Preview Package</a>.</p><h2>ParsableCommand Protocol</h2><p>When following the instructions, the first task is create a type conforming to the <a href="https://github.com/apple/swift-argument-parser/blob/339baea0a5d8c9b26c6e9be47df8e713f6e26234/Sources/ArgumentParser/Parsable%20Types/ParsableCommand.swift#L13"><code>ParsableCommand</code> protocol</a>:</p><pre><code><span class="keyword">public protocol</span> ParsableCommand: <span class="type">ParsableArguments</span> {
  <span class="keyword">static var</span> configuration: <span class="type">CommandConfiguration</span> { <span class="keyword">get</span> }
  <span class="keyword">static var</span> _commandName: <span class="type">String</span> { <span class="keyword">get</span> }
  <span class="keyword">func</span> run() <span class="keyword">throws</span>
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>'s <a href="https://github.com/apple/swift-argument-parser/blob/339baea0a5d8c9b26c6e9be47df8e713f6e26234/Sources/ArgumentParser/Parsable%20Types/ParsableCommand.swift"><code>ParsableCommand.swift</code></a>.</p></blockquote><blockquote><p><code>_commandName</code>, which represents our tool name, is marked as internal: it's not for us to implement.</p></blockquote><p>All these methods are <a href="https://github.com/apple/swift-argument-parser/blob/339baea0a5d8c9b26c6e9be47df8e713f6e26234/Sources/ArgumentParser/Parsable%20Types/ParsableCommand.swift#L34">implemented for us already</a>, in our type we only need to take care of any custom logic: mainly the <code>run()</code> behavior and, if necessary, extra configurations such as abstract and subcommand declarations. Here is the default implementation:</p><pre><code><span class="keyword">extension</span> <span class="type">ParsableCommand</span> {
  <span class="keyword">public static var</span> _commandName: <span class="type">String</span> {
    configuration.<span class="property">commandName</span> ??
      <span class="type">String</span>(describing: <span class="type">Self</span>.<span class="keyword">self</span>).<span class="call">convertedToSnakeCase</span>(separator: <span class="string">"-"</span>)
  }
  
  <span class="keyword">public static var</span> configuration: <span class="type">CommandConfiguration</span> {
    <span class="type">CommandConfiguration</span>()
  }
  
  <span class="keyword">public func</span> run() <span class="keyword">throws</span> {
    <span class="keyword">throw</span> <span class="type">CleanExit</span>.<span class="call">helpRequest</span>(<span class="keyword">self</span>)
  }
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>'s <a href="https://github.com/apple/swift-argument-parser/blob/339baea0a5d8c9b26c6e9be47df8e713f6e26234/Sources/ArgumentParser/Parsable%20Types/ParsableCommand.swift"><code>ParsableCommand.swift</code></a></p></blockquote><p>In this default implementation we discover the first <em>magic trick</em> used in ArgumentParser:<br>how our type name transforms from <code>camelCase</code> into a <code>kebab-case</code> command line tool name:</p><pre><code><span class="type">String</span>(describing: <span class="type">Self</span>.<span class="keyword">self</span>).<span class="call">convertedToSnakeCase</span>(separator: <span class="string">"-"</span>)
</code></pre><p><code>Self</code> refers to the type conforming to the protocol, and using <code>String(describing: Self.self)</code> (or <code>String(describing: self)</code>, since <code>_commandName</code> is a static property) will return us our type name as a <code>String</code>.</p><p>Once we have the name, ArgumentParser has a <a href="https://github.com/apple/swift-argument-parser/blob/9ebf401d274c4ef0a634cd4c4494b4a63f12baaf/Sources/ArgumentParser/Utilities/StringExtensions.swift#L95"><code>convertedToSnakeCase(separator:)</code></a> function that takes care of the rest.</p><h2>ParsableArguments Protocol</h2><p>The <code>ParsableArguments</code> protocol requires our types to conform to the <a href="https://github.com/apple/swift-argument-parser/blob/be65f492ba72b7a08805bbb2cd1e3e064fbc3be8/Sources/ArgumentParser/Parsable%20Types/ParsableArguments.swift#L27"><code>ParsableArguments</code> protocol</a> as well, let's take a look at that next:</p><pre><code><span class="keyword">public protocol</span> ParsableArguments: <span class="type">Decodable</span> {
  <span class="keyword">init</span>()
  <span class="keyword">mutating func</span> validate() <span class="keyword">throws</span>
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>'s <a href="https://github.com/apple/swift-argument-parser/blob/f6ac7b8118ff5d1bc0faee7f37bf6f8fd8f95602/Sources/ArgumentParser/Parsable%20Types/ParsableArguments.swift"><code>ParsableArguments.swift</code></a></p></blockquote><p>Its definition is <em>A type that can be parsed from a program's command-line arguments</em>, which also explains the required <code>Decodable</code> conformation:<br>the library has its own decoders, <a href="https://github.com/apple/swift-argument-parser/blob/be65f492ba72b7a08805bbb2cd1e3e064fbc3be8/Sources/ArgumentParser/Parsing/ArgumentDecoder.swift#L13"><code>ArgumentDecoder</code></a> and <a href="https://github.com/apple/swift-argument-parser/blob/be65f492ba72b7a08805bbb2cd1e3e064fbc3be8/Sources/ArgumentParser/Parsing/ArgumentDecoder.swift#L114"><code>SingleValueDecoder</code></a>, which are used later on, to decode the command-line arguments into something meaningful.</p><p>Both the initializer and the <code>Decodable</code> conformation are synthesized, the only requirement left is the <code>validate()</code> implementation, however the library offers a <a href="https://github.com/apple/swift-argument-parser/blob/f6ac7b8118ff5d1bc0faee7f37bf6f8fd8f95602/Sources/ArgumentParser/Parsable%20Types/ParsableArguments.swift#L65">default implementation</a> for us already:</p><pre><code><span class="keyword">extension</span> <span class="type">ParsableArguments</span> {
  <span class="keyword">public mutating func</span> validate() <span class="keyword">throws</span> {}
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>'s <a href="https://github.com/apple/swift-argument-parser/blob/f6ac7b8118ff5d1bc0faee7f37bf6f8fd8f95602/Sources/ArgumentParser/Parsable%20Types/ParsableArguments.swift"><code>ParsableArguments.swift</code></a></p></blockquote><blockquote><p>As a reminder, <code>validate()</code> is here for us to make sure that the parsed arguments are valid, not type-wise, but logic-wise.</p></blockquote><h2>The Static Main</h2><p>When following the ArgumentParser instructions, the final step is to call <code>.main()</code> in our type. This static method is not required from the <code>ParsableCommand</code> (nor from <code>ParsableArguments</code> etc) and we haven't defined it, it turns out that it's implemented as a public <a href="https://github.com/apple/swift-argument-parser/blob/be65f492ba72b7a08805bbb2cd1e3e064fbc3be8/Sources/ArgumentParser/Parsable%20Types/ParsableCommand.swift#L81">extension of <code>ParsableCommand</code></a>:</p><pre><code><span class="keyword">extension</span> <span class="type">ParsableCommand</span> {
  <span class="keyword">public static func</span> main(<span class="keyword">_</span> arguments: [<span class="type">String</span>]? = <span class="keyword">nil</span>) -&gt; <span class="type">Never</span> {
    <span class="keyword">do</span> {
      <span class="keyword">let</span> command = <span class="keyword">try</span> <span class="call">parseAsRoot</span>(arguments)
      <span class="keyword">try</span> command.<span class="call">run</span>()
      <span class="call">exit</span>()
    } <span class="keyword">catch</span> {
      <span class="call">exit</span>(withError: error)
    }
  }
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>'s <a href="https://github.com/apple/swift-argument-parser/blob/339baea0a5d8c9b26c6e9be47df8e713f6e26234/Sources/ArgumentParser/Parsable%20Types/ParsableCommand.swift"><code>ParsableCommand.swift</code></a></p></blockquote><p>The method does two things:</p><ol><li>parse and initialize our command via a <a href="https://github.com/apple/swift-argument-parser/blob/9ebf401d274c4ef0a634cd4c4494b4a63f12baaf/Sources/ArgumentParser/Parsable%20Types/ParsableCommand.swift#L59"><code>parseAsRoot(_:)</code> function</a>.</li><li>run it</li></ol><p>All the magic happens in the first step, step 2 executes our <code>run()</code> implementation (or the default implementation we've seen above).</p><h2>parseAsRoot</h2><p>The purpose of this method is solely to return an instance of <code>ParsableCommand</code>, more specifically an instance of our type (conforming to <code>ParsableCommand</code>):</p><pre><code><span class="keyword">public static func</span> parseAsRoot(
  <span class="keyword">_</span> arguments: [<span class="type">String</span>]? = <span class="keyword">nil</span>
) <span class="keyword">throws</span> -&gt; <span class="type">ParsableCommand</span> {
  <span class="keyword">var</span> parser = <span class="type">CommandParser</span>(<span class="keyword">self</span>)
  <span class="keyword">let</span> arguments = arguments ?? <span class="type">Array</span>(<span class="type">CommandLine</span>.<span class="property">arguments</span>.<span class="call">dropFirst</span>())
  <span class="keyword">var</span> result = <span class="keyword">try</span> parser.<span class="call">parse</span>(arguments: arguments).<span class="call">get</span>()
  <span class="keyword">do</span> {
    <span class="keyword">try</span> result.<span class="call">validate</span>()
  } <span class="keyword">catch</span> {
    <span class="keyword">throw</span> <span class="type">CommandError</span>(
      commandStack: parser.<span class="property">commandStack</span>,
      parserError: <span class="type">ParserError</span>.<span class="call">userValidationError</span>(error))
  }
  <span class="keyword">return</span> result
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>'s <a href="https://github.com/apple/swift-argument-parser/blob/339baea0a5d8c9b26c6e9be47df8e713f6e26234/Sources/ArgumentParser/Parsable%20Types/ParsableCommand.swift"><code>ParsableCommand.swift</code></a></p></blockquote><p>We first obtain the input arguments via <a href="https://www.fivestars.blog/articles/ultimate-guide-swift-executables/"><code>CommandLine.arguments</code></a>, and then we parse them via a new <code>CommandParser</code> entity:<br>if the parse is successful, we validate the command line inputs (with <code>ParsableArguments</code>'s <code>validate()</code>) and then return the new instance (of our <code>ParsableCommand</code> type), ready to run.</p><h3>CommandParser</h3><p><code>CommandParser</code> structures our command as a tree: a command can contain zero or more subcommands, which can contain subcommands, which can contain subcommands, which... there's no limit set by ArgumentParser.</p><pre><code><span class="keyword">struct</span> CommandParser {
  <span class="keyword">let</span> commandTree: <span class="type">Tree</span>&lt;<span class="type">ParsableCommand</span>.<span class="type">Type</span>&gt;
  <span class="keyword">var</span> currentNode: <span class="type">Tree</span>&lt;<span class="type">ParsableCommand</span>.<span class="type">Type</span>&gt;
  <span class="keyword">var</span> parsedValues: [(type: <span class="type">ParsableCommand</span>.<span class="type">Type</span>, decodedResult: <span class="type">ParsableCommand</span>)] = []
  
  <span class="keyword">var</span> commandStack: [<span class="type">ParsableCommand</span>.<span class="type">Type</span>] {
    <span class="keyword">let</span> result = parsedValues.<span class="call">map</span> { $0.<span class="property">type</span> }
    <span class="keyword">if</span> currentNode.<span class="property">element</span> == result.<span class="property">last</span> {
      <span class="keyword">return</span> result
    } <span class="keyword">else</span> {
      <span class="keyword">return</span> result + [currentNode.<span class="property">element</span>]
    }
  }
  
  <span class="keyword">init</span>(<span class="keyword">_</span> rootCommand: <span class="type">ParsableCommand</span>.<span class="type">Type</span>) {
    <span class="keyword">self</span>.<span class="property">commandTree</span> = <span class="type">Tree</span>(root: rootCommand)
    <span class="keyword">self</span>.<span class="property">currentNode</span> = commandTree
    
    <span class="comment">// A command tree that has a depth greater than zero gets a `help`
    // subcommand.</span>
    <span class="keyword">if</span> !commandTree.<span class="property">isLeaf</span> {
      commandTree.<span class="call">addChild</span>(<span class="type">Tree</span>(<span class="type">HelpCommand</span>.<span class="keyword">self</span>))
    }
  }
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>'s <a href="https://github.com/apple/swift-argument-parser/blob/be65f492ba72b7a08805bbb2cd1e3e064fbc3be8/Sources/ArgumentParser/Parsing/CommandParser.swift"><code>CommandParser.swift</code></a></p></blockquote><blockquote><p>In the initializer we can see how every command line tool gets a <code>HelpCommand</code>.</p></blockquote><p>Let's look at the <code>parse(_:)</code> method, called by the <code>ParsableCommand</code>'s static <code>main()</code>:</p><pre><code><span class="keyword">mutating func</span> parse(arguments: [<span class="type">String</span>]) -&gt; <span class="type">Result</span>&lt;<span class="type">ParsableCommand</span>, <span class="type">CommandError</span>&gt; {
    <span class="keyword">var</span> split: <span class="type">SplitArguments</span>
    <span class="keyword">do</span> {
      split = <span class="keyword">try</span> <span class="type">SplitArguments</span>(arguments: arguments)
    } <span class="keyword">catch</span> {
      ...
    }
    
    <span class="keyword">do</span> {
      <span class="keyword">try</span> <span class="call">descendingParse</span>(&amp;split)
      <span class="keyword">let</span> result = <span class="keyword">try</span> <span class="call">extractLastParsedValue</span>(split)
      ...

      <span class="keyword">return</span> .<span class="call">success</span>(result)
    } <span class="keyword">catch</span> {
      ...
    }
  }
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>'s <a href="https://github.com/apple/swift-argument-parser/blob/be65f492ba72b7a08805bbb2cd1e3e064fbc3be8/Sources/ArgumentParser/Parsing/CommandParser.swift"><code>CommandParser.swift</code></a></p></blockquote><p><code>parse(arguments:)</code> can be split in three steps:</p><ol><li>Create a new instance of <code>SplitArguments</code>, a new entity, out of the input string arguments.</li><li>Translate the <code>SplitArguments</code> instance into a <code>ParsableCommand</code> instance.</li><li>Return it.</li></ol><h4>Step 1</h4><p>This step turns the input string arguments into an instance of <code>SplitArguments</code>:</p><pre><code><span class="keyword">struct</span> SplitArguments {
  <span class="keyword">var</span> elements: [(index: <span class="type">Index</span>, element: <span class="type">Element</span>)]
  <span class="keyword">var</span> originalInput: [<span class="type">String</span>]
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>'s <a href="https://github.com/apple/swift-argument-parser/blob/be65f492ba72b7a08805bbb2cd1e3e064fbc3be8/Sources/ArgumentParser/Parsing/SplitArguments.swift"><code>SplitArguments.swift</code></a></p></blockquote><blockquote><p>This step has no knowledge about our command line tool definition.</p></blockquote><p><code>SplitArguments</code> translates the input array of strings into something more meaningful for a command line tool input: options and values. Options are anything with a dash in the front, values are strings without a dash in front.</p><p>Here's the definition of <code>SplitArguments</code>'s <code>Element</code> (with comments):</p><pre><code><span class="keyword">enum</span> Element: <span class="type">Equatable</span> {
  <span class="keyword">case</span> option(<span class="type">ParsedArgument</span>) <span class="comment">// something with a dash in the front</span>
  <span class="keyword">case</span> value(<span class="type">String</span>) <span class="comment">// values</span>
  <span class="keyword">case</span> terminator <span class="comment">// --, special character</span>
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>'s <a href="https://github.com/apple/swift-argument-parser/blob/be65f492ba72b7a08805bbb2cd1e3e064fbc3be8/Sources/ArgumentParser/Parsing/SplitArguments.swift"><code>SplitArguments.swift</code></a></p></blockquote><p>Where we introduce <code>ParsedArgument</code>:</p><pre><code><span class="keyword">enum</span> ParsedArgument: <span class="type">Equatable</span>, <span class="type">CustomStringConvertible</span> {
  <span class="keyword">case</span> name(<span class="type">Name</span>) <span class="comment">/// `--foo` or `-f`</span>
  case nameWithValue(<span class="type">Name</span>, <span class="type">String</span>) <span class="comment">// `--foo=bar`</span>
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>'s <a href="https://github.com/apple/swift-argument-parser/blob/be65f492ba72b7a08805bbb2cd1e3e064fbc3be8/Sources/ArgumentParser/Parsing/SplitArguments.swift"><code>SplitArguments.swift</code></a></p></blockquote><p>Lastly, we have the <code>Index</code> definition:</p><pre><code><span class="keyword">struct</span> Index: <span class="type">Hashable</span>, <span class="type">Comparable</span> {
  <span class="keyword">var</span> inputIndex: <span class="type">InputIndex</span>
  <span class="keyword">var</span> subIndex: <span class="type">SubIndex</span>
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>'s <a href="https://github.com/apple/swift-argument-parser/blob/be65f492ba72b7a08805bbb2cd1e3e064fbc3be8/Sources/ArgumentParser/Parsing/SplitArguments.swift"><code>SplitArguments.swift</code></a></p></blockquote><p>Each Index has an <code>inputIndex</code>, which is the argument index in the original input, and a <code>subIndex</code>: for example <code>$ tool -af</code> has both <code>-a</code> and <code>-f</code> at the same index, but subindex of 0 and 1 respectively.</p><p>If you'd like to see the actual parsing, please see <a href="https://github.com/apple/swift-argument-parser/blob/be65f492ba72b7a08805bbb2cd1e3e064fbc3be8/Sources/ArgumentParser/Parsing/SplitArguments.swift#L405">here</a>.</p><h4>Step 2</h4><p>At this point we have our complete <code>SplitArguments</code> instance and its time to run <code>CommandParser</code>'s <code>descendingParse</code>:</p><pre><code><span class="keyword">internal mutating func</span> descendingParse(<span class="keyword">_</span> split: <span class="keyword">inout</span> <span class="type">SplitArguments</span>) <span class="keyword">throws</span> {
  <span class="keyword">while true</span> {
    <span class="keyword">try</span> <span class="call">parseCurrent</span>(&amp;split)
    
    <span class="comment">// Look for next command in the argument list.</span>
    <span class="keyword">if let</span> nextCommand = <span class="call">consumeNextCommand</span>(split: &amp;split) {
      currentNode = nextCommand
      <span class="keyword">continue</span>
    }
    
    <span class="comment">// Look for the help flag before falling back to a default command.</span>
    <span class="keyword">try</span> <span class="call">checkForHelpFlag</span>(split)
    
    <span class="comment">// No command was found, so fall back to the default subcommand.</span>
    <span class="keyword">if let</span> defaultSubcommand = currentNode.<span class="property">element</span>.<span class="property">configuration</span>.<span class="property">defaultSubcommand</span> {
      <span class="keyword">guard let</span> subcommandNode = currentNode.<span class="call">firstChild</span>(equalTo: defaultSubcommand) <span class="keyword">else</span> {
        <span class="keyword">throw</span> <span class="type">ParserError</span>.<span class="property">invalidState</span>
      }
      currentNode = subcommandNode
      <span class="keyword">continue</span>
    }
    
    <span class="comment">// No more subcommands to parse.</span>
    <span class="keyword">return</span>
  }
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>'s <a href="https://github.com/apple/swift-argument-parser/blob/be65f492ba72b7a08805bbb2cd1e3e064fbc3be8/Sources/ArgumentParser/Parsing/CommandParser.swift"><code>CommandParser.swift</code></a></p></blockquote><p>As a reminder, <code>CommandParser</code> thinks of our command line tool as a tree structure (where each node is a <code>ParsableCommand</code> type): in <code>descendingParse</code> we are building said tree based on the parsed <code>SplitArguments</code>.</p><p>We do not create the whole tree structure, but only the relevant branches based on the given <code>SplitArguments</code> instance.</p><p>Let's have a look at <code>parseCurrent(_:)</code> next:</p><pre><code><span class="keyword">fileprivate mutating func</span> parseCurrent(<span class="keyword">_</span> split: <span class="keyword">inout</span> <span class="type">SplitArguments</span>) <span class="keyword">throws</span> {
  <span class="comment">// Build the argument set (i.e. information on how to parse):</span>
  <span class="keyword">let</span> commandArguments = <span class="type">ArgumentSet</span>(currentNode.<span class="property">element</span>)
  
  <span class="comment">// Parse the arguments into a ParsedValues:</span>
  <span class="keyword">let</span> parsedResult = <span class="keyword">try</span> commandArguments.<span class="call">lenientParse</span>(split)
  
  <span class="keyword">let</span> values: <span class="type">ParsedValues</span>
  <span class="keyword">switch</span> parsedResult {
  <span class="keyword">case</span> .<span class="dotAccess">success</span>(<span class="keyword">let</span> v):
    values = v
  <span class="keyword">case</span> .<span class="dotAccess">partial</span>(<span class="keyword">let</span> v, <span class="keyword">let</span> e):
    values = v
    <span class="keyword">if</span> currentNode.<span class="property">isLeaf</span> {
      <span class="keyword">throw</span> e
    }
  }
  
  <span class="comment">// Decode the values from ParsedValues into the ParsableCommand:</span>
  <span class="keyword">let</span> decoder = <span class="type">ArgumentDecoder</span>(values: values, previouslyParsedValues: parsedValues)
  <span class="keyword">var</span> decodedResult: <span class="type">ParsableCommand</span>
  <span class="keyword">do</span> {
    decodedResult = <span class="keyword">try</span> currentNode.<span class="property">element</span>.<span class="keyword">init</span>(from: decoder)
  } <span class="keyword">catch</span> {
    ...
  }
  
  <span class="comment">// Decoding was successful, so remove the arguments that were used
  // by the decoder.</span>
  split.<span class="call">removeAll</span>(in: decoder.<span class="property">usedOrigins</span>)
  
  <span class="comment">// Save this decoded result to add to the next command.</span>
  parsedValues.<span class="call">append</span>((currentNode.<span class="property">element</span>, decodedResult))
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>'s <a href="https://github.com/apple/swift-argument-parser/blob/be65f492ba72b7a08805bbb2cd1e3e064fbc3be8/Sources/ArgumentParser/Parsing/CommandParser.swift"><code>CommandParser.swift</code></a></p></blockquote><p>First we create a new <code>ArgumentSet</code> instance, which is based on the <code>ParsableCommand</code> type associated with the current node in the tree (if this is the first iteration, we're still at the root):</p><pre><code><span class="keyword">struct</span> ArgumentSet {
  <span class="keyword">var</span> content: <span class="type">Content</span>
  <span class="keyword">var</span> kind: <span class="type">Kind</span> <span class="comment">// Used to generate help text.</span>
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>'s <a href="https://github.com/apple/swift-argument-parser/blob/339baea0a5d8c9b26c6e9be47df8e713f6e26234/Sources/ArgumentParser/Parsing/ArgumentSet.swift"><code>ArgumentSet.swift</code></a>:</p></blockquote><p>Where we define a <code>Content</code>:</p><pre><code><span class="keyword">enum</span> Content {
  <span class="keyword">case</span> arguments([<span class="type">ArgumentDefinition</span>]) <span class="comment">// A leaf list of arguments.</span>
  <span class="keyword">case</span> sets([<span class="type">ArgumentSet</span>]) <span class="comment">// A node with additional `[ArgumentSet]`</span>
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>'s <a href="https://github.com/apple/swift-argument-parser/blob/339baea0a5d8c9b26c6e9be47df8e713f6e26234/Sources/ArgumentParser/Parsing/ArgumentSet.swift"><code>ArgumentSet.swift</code></a>:</p></blockquote><p><code>ArgumentSet</code> helps us create yet again a tree structure of our command line tool, however the emphasis here is on the command line arguments instead of the command line sub/commands.</p><p>Going back to <code>parseCurrent(_:)</code>, here's the <code>ArgumentSet</code> initializer that we use:</p><pre><code><span class="keyword">extension</span> <span class="type">ArgumentSet</span> {
  <span class="keyword">init</span>(<span class="keyword">_</span> type: <span class="type">ParsableArguments</span>.<span class="type">Type</span>) {
    <span class="keyword">let</span> a: [<span class="type">ArgumentSet</span>] = <span class="type">Mirror</span>(reflecting: type.<span class="keyword">init</span>())
      .<span class="dotAccess">children</span>
      .<span class="call">compactMap</span> { child <span class="keyword">in
        guard
          var</span> codingKey = child.<span class="property">label</span>,
          <span class="keyword">let</span> parsed = child.<span class="property">value</span> <span class="keyword">as</span>? <span class="type">ArgumentSetProvider</span>
          <span class="keyword">else</span> { <span class="keyword">return nil</span> }
        
        <span class="comment">// Property wrappers have underscore-prefixed names</span>
        codingKey = <span class="type">String</span>(codingKey.<span class="property">first</span> == <span class="string">"_"</span> ? codingKey.<span class="call">dropFirst</span>(<span class="number">1</span>) : codingKey.<span class="call">dropFirst</span>(<span class="number">0</span>))
        
        <span class="keyword">let</span> key = <span class="type">InputKey</span>(rawValue: codingKey)
        <span class="keyword">return</span> parsed.<span class="call">argumentSet</span>(for: key)
    }
    <span class="keyword">self</span>.<span class="keyword">init</span>(additive: a)
  }
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>'s <a href="https://github.com/apple/swift-argument-parser/blob/f6ac7b8118ff5d1bc0faee7f37bf6f8fd8f95602/Sources/ArgumentParser/Parsable%20Types/ParsableArguments.swift"><code>ParsableArguments.swift</code></a></p></blockquote><p>This initializer is where a second <em>magic trick</em> happens: 1. we first initialize our <code>ParsableArguments</code> type with <code>ParsableArguments</code>'s required <code>init()</code> 2. just to get the instance <a href="https://developer.apple.com/documentation/swift/mirror">Mirror representation</a> 3. and extract its <code>children</code> 4. which are the properties defined in our type, a.k.a. the ones with one of the four ArgumentParser property wrappers (<code>@Argument</code>, <code>@Option</code>, <code>@Flag</code>, and <code>@OptionGroup</code>).</p><p>This is to say, <code>ArgumentSet</code> initialization is where our <code>ParsableCommand</code> type properties auto-magically transform into parsable input arguments that can be read from the command line input.</p><p>Once we have the <code>ArgumentSet</code> instance, we proceed with our <code>parseCurrent(_:)</code> execution by <a href="https://github.com/apple/swift-argument-parser/blob/7f5984a29bf6fa0bd158c3e7e8f39fa31a5f062e/Sources/ArgumentParser/Parsing/ArgumentSet.swift#L277">matching</a> the contents of <code>SplitArguments</code> (which uses as input the input arguments from the command line) with <code>ArgumentSet</code> (which uses as input our type definition).</p><p>This match result is then <a href="https://github.com/apple/swift-argument-parser/blob/fd5b49c8dcd5084c65f622cfb253ed50f72321d1/Sources/ArgumentParser/Parsing/CommandParser.swift#L120">used by the <code>ArgumentDecoder</code></a> to really instantiate our command line and set all the (property wrappers) values.</p><p>The <code>CommandParser</code>'s <code>descendingParse(_:)</code> continues its execution until all the arguments have been consumed:<br>once this is completed, we go back to <code>CommandParser</code>'s <code>parse(arguments:)</code>, which then <a href="https://github.com/apple/swift-argument-parser/blob/fd5b49c8dcd5084c65f622cfb253ed50f72321d1/Sources/ArgumentParser/Parsing/CommandParser.swift#L185">extracts</a> and returns the last parsed <code>ParsableCommand</code> instance. Completing the last two steps of <code>CommandParser</code>'s <code>parse(_:)</code>.</p><h2>Wrapping Up The Static Main</h2><p>At this point we are back to the <code>ParsableCommand</code>'s <code>parseAsRoot(_:)</code> method, with our <code>ParsableCommand</code>instance and all its properties set. There's one last step that we need to take before finally running: do our (custom and optional) <code>ParsableArguments</code>'s input validation.</p><p>Once the validation passes, we finally run our tool, which completes the whole journey.</p><h2>Conclusions</h2><p><em>Clarity at the point of use</em> is the first fundamental in Swift's <a href="https://swift.org/documentation/api-design-guidelines/#parameter-names">API Design Guidelines</a>, what the Swift team has achieved with ArgumentParser goes well beyond that: it's <em>ease at the point of use</em>.</p><p>The more we dig into this library the more we can appreciate how much complexity is hidden behind a protocol and four property wrappers:<br>with this article I hope to have given you a small glimpse into the tremendous work the Swift team put into the library, and hope you can now also appreciate how elegant this library API truly is.</p><p>Thank you for reading and please don't hesitate to <a href="https://twitter.com/zntfdr">let me know</a> of any other library with such powerful and elegant API 😃</p><p><a href="https://www.fivestars.blog/feed.rss">Subscribe</a> and <a href="https://twitter.com/zntfdr">follow me on Twitter</a> for more insights into Swift and all things around the language! Until next time 👋🏻</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/executables-argument-kind</guid><title>TSCUtility's ArgumentParser</title><description></description><link>https://www.fivestars.blog/articles/executables-argument-kind</link><pubDate>Tue, 10 Mar 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<blockquote><p>While we have a <a href="https://swift.org/blog/argument-parser/">brand new library</a> from the Swift team that specializes in <a href="https://github.com/apple/swift-argument-parser">argument parsing</a>, it's good to have a look at how we got there, which is the purpose of this article.</p></blockquote><p>In the <a href="https://www.fivestars.blog/articles/ultimate-guide-swift-executables/">Swift Executables Guide</a> we've seen how we can use <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a> to easily read and transform launch arguments. The <a href="https://github.com/apple/swift-argument-parser">ArgumentParser package</a> is actually an iteration over <a href="https://github.com/apple/swift-tools-support-core/blob/0.0.1/Sources/TSCUtility/ArgumentParser.swift#L526"><code>TSCUtility</code>'s <code>ArgumentParser</code></a>, which we are going to dive into next.</p><h2>An example</h2><blockquote><p>I'm going to make a small example here, however please feel free to bookmark <a href="https://twitter.com/rderik">Derik Ramirez</a>'s awesome <a href="https://rderik.com/blog/command-line-argument-parsing-using-swift-package-manager-s/">argument parsing article</a> for more.</p></blockquote><p>In this example we will require a flag <code>--name</code> with an associated value: after implementing it, the scripts will expect to be launched with something like <code>$ swift run hello --name YourName</code>:</p><ol><li>First, we will define our parser, which we can think of as our command line interface to the user:</li></ol><pre><code><span class="keyword">let</span> parser = <span class="type">ArgumentParser</span>(
  usage: <span class="string">"--name YourName"</span>, 
  overview: <span class="string">"Tell me your name 😊"</span>
)
</code></pre><ol start="2"><li>Then we declare an expected argument via the generic <code>OptionArgument</code>:</li></ol><pre><code><span class="keyword">let</span> nameArgument: <span class="type">OptionArgument</span>&lt;<span class="type">String</span>&gt; = parser.<span class="call">add</span>(
  option: <span class="string">"--name"</span>,
  kind: <span class="type">String</span>.<span class="keyword">self</span>,
  usage: <span class="string">"Specify your name"</span>
)
</code></pre><blockquote><p>Beside the flag, <code>OptionArgument</code> also knows the expected argument value type: <code>kind</code> is a metatype instance conforming to the <a href="https://github.com/apple/swift-tools-support-core/blob/05e8dd50e41731961d73b5d7b181a9a57021e5f8/Sources/TSCUtility/ArgumentParser.swift#L151"><code>ArgumentKind</code> protocol</a>.Exactly as for <code>Decodable</code>, all Swift primitives natively conform to it, and we can make our own types conform to it as well.</p></blockquote><ol start="3"><li>Lastly, we can do the actual parsing:</li></ol><pre><code><span class="keyword">let</span> parseResult = <span class="keyword">try</span>! parser.<span class="call">parse</span>(arguments)
<span class="keyword">if let</span> name: <span class="type">String</span> = parseResult.<span class="call">get</span>(nameArgument) { ... }
</code></pre><p>Here's the complete code:</p><pre><code><span class="keyword">import</span> TSCBasic
<span class="keyword">import</span> TSCUtility

<span class="comment">// Read the arguments.</span>
<span class="keyword">let</span> arguments: [<span class="type">String</span>] = <span class="type">Array</span>(
  <span class="type">CommandLine</span>.<span class="property">arguments</span>.<span class="call">dropFirst</span>()
)

<span class="comment">// Define our parser.</span>
<span class="keyword">let</span> parser = <span class="type">ArgumentParser</span>(
  usage: <span class="string">"--name YourName"</span>,
  overview: <span class="string">"Tell me your name 😊"</span>)

<span class="comment">// Declare expected launch argument(s).</span>
<span class="keyword">let</span> nameArgument: <span class="type">OptionArgument</span>&lt;<span class="type">String</span>&gt; = parser.<span class="call">add</span>(
  option: <span class="string">"--name"</span>,
  kind: <span class="type">String</span>.<span class="keyword">self</span>,
  usage: <span class="string">"Specify your name"</span>)

<span class="comment">// Do the parsing.</span>
<span class="keyword">do</span> {
  <span class="keyword">let</span> parseResult = <span class="keyword">try</span> parser.<span class="call">parse</span>(arguments)
  <span class="keyword">if let</span> name: <span class="type">String</span> = parseResult.<span class="call">get</span>(nameArgument) {
    <span class="call">print</span>(<span class="string">"Hello</span> \(name)<span class="string">"</span>)
  } <span class="keyword">else</span> {
    parser.<span class="call">printUsage</span>(on: stdoutStream)
  }
} <span class="keyword">catch</span> {
   parser.<span class="call">printUsage</span>(on: stdoutStream) 
}
</code></pre><blockquote><p>Replace the <code>main.swift</code> content with this to try it out!</p></blockquote><blockquote><p>In case of any failure, we're asking the parser to print the command line usage: this is done for free, we don't have to do anything to get this behavior! 🎉</p></blockquote><p>And here are a few examples of the script in action:</p><pre><code>$ swift run hello --name <span class="type">Federico</span>
&gt; <span class="type">Hello Federico</span>

$ swift run hello missingFlag
&gt; <span class="type">OVERVIEW</span>: <span class="type">Tell</span> me your name 😊
&gt; 
&gt; <span class="type">USAGE</span>: hello --name <span class="type">YourName</span>
&gt; 
&gt; <span class="type">OPTIONS</span>:
&gt;   --name   <span class="type">Specify</span> your name
&gt;   --help   <span class="type">Display</span> available options

$ swift run hello --help
&gt; <span class="type">OVERVIEW</span>: <span class="type">Tell</span> me your name 😊
&gt; 
&gt; <span class="type">USAGE</span>: hello --name <span class="type">YourName</span>
&gt; 
&gt; <span class="type">OPTIONS</span>:
&gt;   --name   <span class="type">Specify</span> your name
&gt;   --help   <span class="type">Display</span> available options
</code></pre><blockquote><p>This is how the <code>swift</code> command line tool works as well: if you run <code>$ swift --help</code>, or <code>$ swift run --help</code>, etc you will see exactly the same format as our new script.</p></blockquote><h2>ArgumentKind</h2><p>In the example above we declared an expected argument via the generic <code>OptionArgument</code>:</p><pre><code><span class="keyword">let</span> nameArgument: <span class="type">OptionArgument</span>&lt;<span class="type">String</span>&gt; = parser.<span class="call">add</span>(
  option: <span class="string">"--name"</span>,
  kind: <span class="type">String</span>.<span class="keyword">self</span>,
  usage: <span class="string">"Specify your name"</span>
)
</code></pre><p>The <code>kind</code> parameter expects a type conforming to a protocol called <a href="https://github.com/apple/swift-tools-support-core/blob/05e8dd50e41731961d73b5d7b181a9a57021e5f8/Sources/TSCUtility/ArgumentParser.swift#L151"><code>ArgumentKind</code></a>:</p><pre><code><span class="keyword">public protocol</span> ArgumentKind {
  <span class="comment">/// Throwable convertion initializer.</span>
  <span class="keyword">init</span>(argument: <span class="type">String</span>) <span class="keyword">throws</span>

  <span class="comment">/// Type of shell completion to provide for this argument.</span>
  <span class="keyword">static var</span> completion: <span class="type">ShellCompletion</span> { <span class="keyword">get</span> }
}
</code></pre><blockquote><p><code>ArgumentKind</code> definition: I like to think of this protocol as the <a href="https://en.wikipedia.org/wiki/Command-line_interface">CLI</a> version of Swift's <a href="https://github.com/apple/swift/blob/99e60b03b23507eef610895cb41cb28c85ec1601/stdlib/public/core/Codable.swift#L32"><code>Decodable</code></a>.</p></blockquote><p>All it is requested is an initializer <code>init(argument:)</code> that parses the given launch argument, and a static <code>completion</code> property of type <a href="https://github.com/apple/swift-tools-support-core/blob/7b1b8195cb83c20e1f24f384140be9edad523aa5/Sources/TSCUtility/ArgumentParser.swift#L130"><code>ShellCompletion</code></a>:</p><pre><code><span class="keyword">public enum</span> ShellCompletion {
  <span class="comment">/// Offers no completions at all; e.g. for a string identifier.</span>
  <span class="keyword">case</span> none
  
  <span class="comment">/// No specific completions, will offer tool's completions.</span>
  <span class="keyword">case</span> unspecified
  
  <span class="comment">/// Offers filename completions.</span>
  <span class="keyword">case</span> filename
  
  <span class="comment">/// Custom function for generating completions. Must be provided in the script's scope.</span>
  <span class="keyword">case</span> function(<span class="type">String</span>)
  
  <span class="comment">/// Offers completions from predefined list. A description can be provided which is shown in some shells, like zsh.</span>
  <span class="keyword">case</span> values([(value: <span class="type">String</span>, description: <span class="type">String</span>)])
}
</code></pre><blockquote><p><code>ShellCompletion</code> definition.</p></blockquote><p>This second requirement is to provide shell completion to our users.</p><h2>Primitives Conformation</h2><p><code>TSCUtility</code> provides <code>ArgumentKind</code> conformation to the most common primitives:</p><ul><li><a href="https://github.com/apple/swift-tools-support-core/blob/57f63b8a9ee5a18c3bf8413ec734127a74dda692/Sources/TSCUtility/ArgumentParser.swift#L169"><code>String</code></a>:</li></ul><pre><code><span class="keyword">extension</span> <span class="type">String</span>: <span class="type">ArgumentKind</span> {
  <span class="keyword">public init</span>(argument: <span class="type">String</span>) <span class="keyword">throws</span> {
    <span class="keyword">self</span> = argument
  }

  <span class="keyword">public static let</span> completion: <span class="type">ShellCompletion</span> = .<span class="dotAccess">none</span>
}
</code></pre><ul><li><a href="https://github.com/apple/swift-tools-support-core/blob/57f63b8a9ee5a18c3bf8413ec734127a74dda692/Sources/TSCUtility/ArgumentParser.swift#L177"><code>Int</code></a>:</li></ul><pre><code><span class="keyword">extension</span> <span class="type">Int</span>: <span class="type">ArgumentKind</span> {
    <span class="keyword">public init</span>(argument: <span class="type">String</span>) <span class="keyword">throws</span> {
        <span class="keyword">guard let</span> int = <span class="type">Int</span>(argument) <span class="keyword">else</span> {
            <span class="keyword">throw</span> <span class="type">ArgumentConversionError</span>.<span class="call">typeMismatch</span>(value: argument, expectedType: <span class="type">Int</span>.<span class="keyword">self</span>)
        }

        <span class="keyword">self</span> = int
    }

    <span class="keyword">public static let</span> completion: <span class="type">ShellCompletion</span> = .<span class="dotAccess">none</span>
}
</code></pre><ul><li><a href="https://github.com/apple/swift-tools-support-core/blob/57f63b8a9ee5a18c3bf8413ec734127a74dda692/Sources/TSCUtility/ArgumentParser.swift#L189"><code>Bool</code></a>:</li></ul><pre><code><span class="keyword">extension</span> <span class="type">Bool</span>: <span class="type">ArgumentKind</span> {
    <span class="keyword">public init</span>(argument: <span class="type">String</span>) <span class="keyword">throws</span> {
        <span class="keyword">guard let</span> bool = <span class="type">Bool</span>(argument) <span class="keyword">else</span> {
            <span class="keyword">throw</span> <span class="type">ArgumentConversionError</span>.<span class="call">unknown</span>(value: argument)
        }

        <span class="keyword">self</span> = bool
    }

    <span class="keyword">public static var</span> completion: <span class="type">ShellCompletion</span> = .<span class="dotAccess">unspecified</span>
}
</code></pre><blockquote><p>There's a fourth conformation in <code>TSCUtility</code> for a custom type <a href="https://github.com/apple/swift-tools-support-core/blob/7b1b8195cb83c20e1f24f384140be9edad523aa5/Sources/TSCUtility/ArgumentParser.swift#L217"><code>PathArgument</code></a>, we skip it here as it's off topic.</p></blockquote><p>Conforming to <code>ArgumentKind</code> is straightforward, let's make a new conformation next.</p><h2>Custom Conformation</h2><p>In my <a href="https://apps.apple.com/us/developer/federico-zanetello/id1053443073">app suite</a> I have several scripts where I must specify which city I want to work on, this is a good opportunity to define an enum with <code>String</code> <a href="https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html#ID149">Raw Values</a>:</p><pre><code><span class="keyword">enum</span> City: <span class="type">String</span> {
  <span class="keyword">case</span> bangkok
  <span class="keyword">case</span> chongqing
  <span class="keyword">case</span> jakarta
  <span class="keyword">case</span> kualalumpur 
}
</code></pre><p>Then we can define our <code>ArgumentKind</code> initializer:</p><pre><code><span class="keyword">public init</span>(argument: <span class="type">String</span>) <span class="keyword">throws</span> {
  <span class="keyword">guard let</span> city = <span class="type">City</span>(rawValue: argument) <span class="keyword">else</span> {
    <span class="keyword">throw</span> <span class="type">ArgumentConversionError</span>.<span class="call">unknown</span>(value: argument)
  }

  <span class="keyword">self</span> = city
}
</code></pre><blockquote><p>We throw <code>TSCUtility</code>'s <a href="https://github.com/apple/swift-tools-support-core/blob/52e6700615f398688c415560a8ccba95a5bbcc56/Sources/TSCUtility/ArgumentParser.swift#L68"><code>ArgumentConversionError</code></a>, no need to create custom error types!</p></blockquote><p>Lastly, we add the <code>ArgumentKind</code>'s static <code>completion</code> property:</p><pre><code><span class="keyword">public static var</span> completion: <span class="type">ShellCompletion</span> = .<span class="call">values</span>([
  (<span class="string">"bangkok"</span>, <span class="string">"Capital of Thailand"</span>),
  (<span class="string">"chongqing"</span>, <span class="string">"Capital of the Hot Pot!"</span>),
  (<span class="string">"jakarta"</span>, <span class="string">"Capital of Indonesia"</span>),
  (<span class="string">"kualalumpur"</span>, <span class="string">"Capital of Malaysia"</span>)
])
</code></pre><blockquote><p>Making the enum conform to <code>CaseIterable</code> would be more correct, but for brevity's sake ...</p></blockquote><p>And that's it! We can now define a new argument with associated type <code>City</code>, and use it in our script:</p><pre><code><span class="keyword">let</span> parser = <span class="type">ArgumentParser</span>(
  usage: <span class="string">"My new tool"</span>,
  overview: <span class="string">"A five stars tool."</span>
)

<span class="keyword">let</span> cityArgument: <span class="type">OptionArgument</span>&lt;<span class="type">City</span>&gt; = parser.<span class="call">add</span>(
  option: <span class="string">"--city"</span>,
  kind: <span class="type">City</span>.<span class="keyword">self</span>,
  usage: <span class="string">"The city to work on"</span>
)
</code></pre><p>Here's the complete example:</p><pre><code><span class="keyword">import</span> TSCBasic
<span class="keyword">import</span> TSCUtility

<span class="keyword">enum</span> City: <span class="type">String</span>, <span class="type">ArgumentKind</span> {
  <span class="keyword">case</span> bangkok
  <span class="keyword">case</span> chongqing
  <span class="keyword">case</span> jakarta
  <span class="keyword">case</span> kualaLumpur

  <span class="keyword">public init</span>(argument: <span class="type">String</span>) <span class="keyword">throws</span> {
    <span class="keyword">guard let</span> city = <span class="type">City</span>(rawValue: argument) <span class="keyword">else</span> {
      <span class="keyword">throw</span> <span class="type">ArgumentConversionError</span>.<span class="call">unknown</span>(value: argument)
    }

    <span class="keyword">self</span> = city
  }

  <span class="keyword">public static var</span> completion: <span class="type">ShellCompletion</span> = .<span class="call">values</span>([
    (<span class="string">"bangkok"</span>, <span class="string">"Capital of Thailand"</span>),
    (<span class="string">"chongqing"</span>, <span class="string">"Capital of the Hot Pot!"</span>),
    (<span class="string">"jakarta"</span>, <span class="string">"Capital of Indonesia"</span>),
    (<span class="string">"kualalumpur"</span>, <span class="string">"Capital of Malaysia"</span>)
  ])
}

<span class="comment">// Read the arguments.</span>
<span class="keyword">let</span> arguments: [<span class="type">String</span>] = <span class="type">Array</span>(
  <span class="type">CommandLine</span>.<span class="property">arguments</span>.<span class="call">dropFirst</span>()
)

<span class="comment">// Define our parser.</span>
<span class="keyword">let</span> parser = <span class="type">ArgumentParser</span>(
  usage: <span class="string">"My new tool"</span>,
  overview: <span class="string">"A five stars tool."</span>
)

<span class="comment">// Declare expected launch argument(s).</span>
<span class="keyword">let</span> cityArgument: <span class="type">OptionArgument</span>&lt;<span class="type">City</span>&gt; = parser.<span class="call">add</span>(
  option: <span class="string">"--city"</span>,
  kind: <span class="type">City</span>.<span class="keyword">self</span>,
  usage: <span class="string">"The city to work on"</span>,
  completion: <span class="type">City</span>.<span class="property">completion</span>
)

<span class="comment">// Do the parsing.</span>
<span class="keyword">do</span> {
  <span class="keyword">let</span> parseResult = <span class="keyword">try</span> parser.<span class="call">parse</span>(arguments)
  <span class="keyword">if let</span> city: <span class="type">City</span> = parseResult.<span class="call">get</span>(cityArgument) {
    <span class="call">print</span>(<span class="string">"Working on</span> \(city.<span class="property">rawValue</span>) <span class="string">🚀"</span>)
  } <span class="keyword">else</span> {
    <span class="call">print</span>(<span class="string">"⚠️ Seems like the city is missing!"</span>)
  }
} <span class="keyword">catch</span> {
   <span class="call">print</span>(error)
}
</code></pre><blockquote><p>Place this in a <a href="https://www.fivestars.blog/articles/ultimate-guide-swift-executables/">Swift Executable</a> <code>main.swift</code> file.</p></blockquote><p>And here we can see the script in action:</p><pre><code>$ swift run yourToolName --city bangkok
&gt; <span class="type">Working</span> on bangkok 🚀

$ swift run yourToolName 
&gt; ⚠️ <span class="type">Seems</span> like the city <span class="keyword">is</span> missing!
</code></pre><h2>Custom Conformation 2: Extending Types</h2><p>While it's awesome that we can make our own types conform to <code>ArgumentKind</code>, nobody stops us to also <em>extend</em> other types as well.<br>For example, if our script expects an <code>URL</code>, we can extend <a href="https://developer.apple.com/documentation/foundation/url">Foundation's <code>URL</code></a>:</p><pre><code><span class="keyword">import</span> Foundation
<span class="keyword">import</span> TSCUtility

<span class="keyword">extension</span> <span class="type">Foundation</span>.<span class="type">URL</span>: <span class="type">ArgumentKind</span> {

  <span class="keyword">public init</span>(argument: <span class="type">String</span>) <span class="keyword">throws</span> {
    <span class="keyword">guard let</span> url = <span class="type">URL</span>(string: argument) <span class="keyword">else</span> {
      <span class="keyword">throw</span> <span class="type">ArgumentConversionError</span>.<span class="call">unknown</span>(value: argument)
    }

    <span class="keyword">self</span> = url
  }

  <span class="keyword">public static var</span> completion: <span class="type">ShellCompletion</span> = .<span class="dotAccess">none</span>
}
</code></pre><blockquote><p><a href="https://github.com/zntfdr/Selenops/blob/74ab2b0f5afc7948d758ae60374be2e6089f6b58/Sources/selenopsCLI/URL%2BArgumentKind.swift#L11">Code snippet</a> from <a href="https://github.com/zntfdr/Selenops">Selenops, A Swift Web Crawler</a>.</p></blockquote><p>And now we can get an <code>URL</code> instance directly from our parser!</p><h2>Comparison with ArgumentParser</h2><p>As of today, the new <a href="https://github.com/apple/swift-argument-parser">ArgumentParser library</a> does <a href="https://github.com/apple/swift-argument-parser/issues/1">not support auto-completion</a>, therefore ArgumentParser's <code>ArgumentKind</code> equivalent, <a href="https://github.com/apple/swift-argument-parser/blob/f6ac7b8118ff5d1bc0faee7f37bf6f8fd8f95602/Sources/ArgumentParser/Parsable%20Types/ExpressibleByArgument.swift#L13"><code>ExpressibleByArgument</code></a>, only requires an initializer:</p><pre><code><span class="comment">/// A type that can be expressed as a command-line argument.</span>
<span class="keyword">public protocol</span> ExpressibleByArgument {
  <span class="comment">/// Creates a new instance of this type from a command-line-specified
  /// argument.</span>
  <span class="keyword">init</span>?(argument: <span class="type">String</span>)
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>'s <a href="https://github.com/apple/swift-argument-parser/blob/f6ac7b8118ff5d1bc0faee7f37bf6f8fd8f95602/Sources/ArgumentParser/Parsable%20Types/ExpressibleByArgument.swift#L13"><code>ExpressibleByArgument.swift</code></a>.</p></blockquote><p>Having to implement one initializer surely makes adopting the new library faster, however I'm hopeful that auto-completion support <a href="https://github.com/apple/swift-argument-parser/issues/1">will be added in the future</a>.</p><p>If you're interested to learn more about the new library: - have a look at the <a href="https://github.com/apple/swift-argument-parser/tree/be65f492ba72b7a08805bbb2cd1e3e064fbc3be8/Documentation">amazing official documentation</a> - maybe subscribe to <a href="https://www.fivestars.blog/feed.rss">this blog feed RSS</a> 👀</p><h2>Conclusions</h2><p>In this article we've seen how <code>TSCUtility</code>'s <code>ArgumentParser</code> and <code>ArgumentKind</code> are defined and how we can make any type conform to it, we've then compared it with the new ArgumentParser library, noting a little regression. Despite that, both libraries make our scripts argument parsing effortless 🚀.</p><p>If you're wondering which one you should pick up today: go with <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>. TSCUtility's ArgumentParser will be <a href="https://github.com/apple/swift-argument-parser/issues/5#issuecomment-592572888">completely removed in the future</a>.</p><p>As always you can <a href="https://twitter.com/zntfdr">find me on Twitter</a> for any comment and/or feedback, thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/ultimate-guide-swift-executables</guid><title>The Ultimate Guide To Swift Executables 🚀</title><description></description><link>https://www.fivestars.blog/articles/ultimate-guide-swift-executables</link><pubDate>Tue, 25 Feb 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Every macOS/iOS developer uses tools such as <a href="https://fastlane.tools">fastlane</a> and <a href="https://github.com/realm/SwiftLint">SwiftLint</a> to automate tasks as much as possible:<br>in this article we're going to dive into how we can build our own tools using Swift.</p><h2>Why Swift?</h2><p>There are multiple advantages of using Swift: - we know the language - building a script is similar to building an app or a library - everybody in the team can read, understand, and edit what any script does</p><p>In fact, everything is so familiar that we're going to build a script with the Swift Package Manager!</p><h2>Creating An Executable</h2><p>Creating a new package in Xcode defaults into library. Instead, we're going to use the command line to create an executable, please open your favorite terminal and fire the following commands:</p><pre><code>$ mkdir hello
$ cd hello
$ swift package <span class="keyword">init</span> --type executable
</code></pre><p>Once the last command completes, a bunch of new files have been created, and our new executable is ready to run! Let's take a look at what we have first.</p><h2>The Package Structure</h2><p>Before diving into the package structure, it is important to know what a Package Target is.</p><h3>Package Target(s)</h3><p>A Package Target is a basic building block of any Swift Package, you can think of a target as a module or a library, its representation is a <strong>folder</strong>:<br>all files within that folder belongs to that specific target, and every file can access to all other declarations within that folder.</p><p>A Target can depend on other targets, said targets can be within the same package or come from external packages, we will cover both cases in this article.</p><p>There are two kinds of targets: those that are compiled into a module, and those that are compiled into a test suite (more on this soon).</p><h3>The Structure</h3><p>When we run <code>$ swift package init --type executable</code>, the basic structure of our package is automatically generated:</p><pre><code>├── <span class="type">Package</span>.<span class="property">swift</span>
├── <span class="type">README</span>.<span class="property">md</span>
├── <span class="type">Sources</span>
│   └── hello
│       └── main.<span class="property">swift</span>
└── <span class="type">Tests</span>
    ├── helloTests
    │   ├── helloTests.<span class="property">swift</span>
    │   └── <span class="type">XCTestManifests</span>.<span class="property">swift</span>
    └── <span class="type">LinuxMain</span>.<span class="property">swift</span>
</code></pre><p>This structure is very similar to a <em>library</em> package:</p><ul><li>The <code>Tests</code> folder is where our tests are, we will have a folder for each <strong>test target</strong>.</li><li>The <code>Sources</code> folder is where our script code will live, we will have one folder for each <strong>module target</strong>. The compiler distinguishes <strong>executable modules</strong> from <strong>library modules</strong> thanks to the presence, or absence, of a <code>main.swift</code> file in the target folder.</li><li>The <code>README.md</code> is for us to describe the purpose of the package.</li><li>Lastly, we have the most important file: the <code>Package.swift</code> manifest, we will look at this next.</li></ul><p>From the file structure above we can see that we have two targets:<br>- a <code>helloTests</code> test target. - a <code>hello</code> module target, which we now know is an executable module, as it contains a <code>main.swift</code> file.</p><p>While this is all clear to us now, nothing really works until it's declared in the <code>Package.swift</code> manifest file.</p><h2>The Package Manifest</h2><p><code>Package.swift</code> is the entry point of every package, it showcases all there is to know about it:</p><pre><code><span class="comment">// swift-tools-version:5.1</span>

<span class="keyword">import</span> PackageDescription

<span class="keyword">let</span> package = <span class="type">Package</span>(
    name: <span class="string">"hello"</span>,
    dependencies: [
    ],
    targets: [
        .<span class="call">target</span>(
            name: <span class="string">"hello"</span>,
            dependencies: []),
        .<span class="call">testTarget</span>(
            name: <span class="string">"helloTests"</span>,
            dependencies: [<span class="string">"hello"</span>]),
    ]
)
</code></pre><blockquote><p>The generated <code>Package.swift</code> file.</p></blockquote><blockquote><p>We can double-click on this file to open the whole package in Xcode.</p></blockquote><p>The first line always declares the <em>Swift tools version</em>, which lets the compiler know the minimum version of the Swift tools (such as the <code>PackageDescription</code> module) and Swift language (compatibility) version to use in order to process the manifest file.</p><p>Without this line, the compiler can't know if a package has been written in Swift 3, 6 or else:<br>therefore this line is required, and we wouldn't be able to compile the package without it.</p><p>We then import the <a href="https://github.com/apple/swift-package-manager/tree/334217e5a0d3b8b2a305c1adbc72f81917c0c445/Sources/PackageDescription"><code>PackageDescription</code> module</a>, which defines all the APIs available for us to use when declaring a Package.</p><p>Lastly, we have our package definition, in here we find:</p><ul><li>The package name which defaults to the folder name where we ran the <code>$ swift package init</code> command.</li><li>The package external dependencies there are none at the moment.</li><li>The package targets the generated package has two targets, a module target, <code>.target</code>, and a test target, <code>.testTarget</code>.</li></ul><p>If not specified otherwise, the folder of each target has the same name as the target, and all <code>.target</code> declarations are to be found in the <code>Source</code> folder, while the <code>.testTarget</code> targets are to be found in the <code>Tests</code> folder.</p><p>Note how each target can have its own dependencies, these dependencies can come from within the package itself, like in our <code>.testTarget</code> case, or from external packages (we will see how to do so later on in this article).</p><p>Now that we understand the package structure and its declaration, it's time to look at the content of <code>hello</code> target folder: the <code>main.swift</code> file.</p><h2>main.swift</h2><pre><code><span class="call">print</span>(<span class="string">"Hello, world!"</span>)
</code></pre><p>That's it! The whole body is one print statement ready to be run, let's do so!</p><h2>Build, Run, And Test A Package</h2><pre><code>$ swift build
$ swift run
$ swift test
</code></pre><h3>Building A Package</h3><p><code>swift build</code> will resolve, download, and compile any dependency mentioned in the package manifest file, it will then build the specified target dependencies (if any) and finally the target itself.</p><p>Since our package only has one target, it is inferred, and we can omit its name in the command.<br>The complete command would be <code>$ swift build --target hello</code>.</p><h3>Running A Package</h3><p>As for the build phase, since we only have one target, we can omit its name, however the complete command is <code>$ swift run hello</code>.</p><p>With this, we should see the following in the terminal:</p><pre><code>$ swift run 
&gt; <span class="type">Hello</span>, world!
</code></pre><p>Congratulations on running your first executable!</p><blockquote><p>We don't have to build every time before running:<code>$ swift run</code> will automatically build the target when necessary.</p></blockquote><h3>Testing A Package</h3><p>We can run all the package tests via <code>$ swift test</code>.</p><p>It's best to always run all the tests. If we'd like to run only a subset, the <code>swift test</code> command offers a <code>--filter</code> flag that does exactly that.<br><br>For example, we can run all the tests in <code>helloTests</code> with <code>$ swift test --filter helloTests</code>.</p><h2>Common Patterns</h2><p>There are a few very common use cases in the scripting world, let's cover them!</p><h2>Exit Codes &amp; System Modules</h2><p>All scripts have an exit status (or code) when complete, the most common are <code>0</code> for success and <code>1</code> for failure. While those are the de facto standards, I prefer to avoid writing magic numbers in code:<br><br>this is why you'll see me importing the <code>Darwin</code> module in some examples below: <code>Darwin</code> defines the two values for a successful exit, <code>EXIT_SUCCESS</code>, and unsuccessful exit, <code>EXIT_FAILURE</code>.</p><blockquote><p><a href="https://en.wikipedia.org/wiki/Darwin_(operating_system)">Darwin</a> is Apple’s UNIX-based core of every Apple OS, I like to think of it as the foundation of the <code>Foundation</code> framework (<code>Foundation</code> imports <code>Darwin</code>) but really Darwin is much more than that.</p></blockquote><p>Here's a silly example, please replace the <code>main.swift</code> content with the following:</p><pre><code><span class="keyword">import</span> Darwin

<span class="comment">// Flip a coin.</span>
<span class="keyword">if</span> <span class="type">Bool</span>.<span class="call">random</span>() {
  <span class="call">exit</span>(<span class="type">EXIT_SUCCESS</span>) <span class="comment">// exit(0)</span>
} <span class="keyword">else</span> {
  <span class="call">exit</span>(<span class="type">EXIT_FAILURE</span>) <span class="comment">// exit(1)</span>
}
</code></pre><p>While this script does absolutely nothing, it will exit with success only 50% of the time.</p><blockquote><p>Hide this script in your company project build phases to see your colleagues lose their minds 😆</p></blockquote><p>This script showcases another important aspect of Swift Packages: we do not need to declare system dependencies.<br>All system modules (such as <code>Foundation</code> and <code>Darwin</code>) can be freely imported in our scripts without having to add such dependency in our <code>Package.swift</code> manifest.</p><p>Lastly, if the script ends because it completes its execution, it automatically ends with a success state: we don't need to call <code>exit(EXIT_SUCCESS)</code> at the bottom of every script.</p><h3>Launch Arguments</h3><p>Most scripts need some input before running, for example a path to a folder, a meaningful value, etc.</p><p>This kind of input can be read via the <a href="https://github.com/apple/swift/blob/b6d9dcca46e034fcf622f698139645916f2f4017/stdlib/public/core/CommandLine.swift#L17"><code>CommandLine</code></a> object, which holds the parameters passed to the script at launch.</p><p>To test it out, please replace the current <code>main.swift</code> content with the following:</p><pre><code><span class="keyword">import</span> Darwin

<span class="comment">// We drop the first argument, which is the script execution path.</span>
<span class="keyword">let</span> arguments: [<span class="type">String</span>] = <span class="type">Array</span>(<span class="type">CommandLine</span>.<span class="property">arguments</span>.<span class="call">dropFirst</span>())

<span class="keyword">guard let</span> name: <span class="type">String</span> = arguments.<span class="property">first</span> <span class="keyword">else</span> { 
  <span class="call">exit</span>(<span class="type">EXIT_FAILURE</span>) 
}

<span class="call">print</span>(<span class="string">"Hello</span> \(name)<span class="string">"</span>)
</code></pre><p>And here's how you can run:</p><pre><code>$ swift run hello <span class="type">Swift</span>
&gt; <span class="type">Hello Swift</span>
$ swift run hello <span class="type">Federico</span>
&gt; <span class="type">Hello Federico</span>
</code></pre><p>The first argument of <code>CommandLine.arguments</code> is always the script execution path, while the following arguments are the user inputs, for example:</p><pre><code>$ swift run hello <span class="number">1 2 3</span>
</code></pre><p>Results in a <code>CommandLine.arguments</code> <code>String</code> array of four elements:</p><pre><code>[<span class="string">"&lt;execution-path-here&gt;"</span>, <span class="string">"1"</span>, <span class="string">"2"</span>, <span class="string">"3"</span>]
</code></pre><h3>Interactive Scripts</h3><p>Sometimes scripts need more user input after launch, for such cases we can use <a href="https://developer.apple.com/documentation/swift/1641199-readline"><code>readLine()</code></a>:</p><pre><code><span class="keyword">import</span> Darwin

<span class="call">print</span>(<span class="string">"What`s your name?"</span>)

<span class="keyword">guard let</span> name = <span class="call">readLine</span>(), !name.<span class="property">isEmpty</span> <span class="keyword">else</span> {
  <span class="call">exit</span>(<span class="type">EXIT_FAILURE</span>)
}

<span class="call">print</span>(<span class="string">"Hello</span> \(name)<span class="string">"</span>)
</code></pre><p><code>readLine()</code> is a synchronous call that waits for the user to type something (until the return key is pressed) before proceeding, here's how you can run it:</p><pre><code>$ swift run hello
&gt; <span class="type">What</span>`s your name?
&gt; <span class="type">Federico</span>
&gt; <span class="type">Hello Federico</span>
</code></pre><blockquote><p>The third line, <code>Federico</code>, is something that we type in the console.</p></blockquote><h3>Environment Variables</h3><p>A fundamental aspect of many automation/CI tools is having access to the inherited shell <a href="https://en.wikipedia.org/wiki/Environment_variable">environment</a>, in Swift this couldn't be easier:</p><pre><code><span class="keyword">import</span> Foundation

<span class="keyword">let</span> environment: [<span class="type">String</span>: <span class="type">String</span>] = <span class="type">ProcessInfo</span>.<span class="property">processInfo</span>.<span class="property">environment</span>

<span class="keyword">if let</span> secret = environment[<span class="string">"MYSECRET"</span>] {
  <span class="call">print</span>(secret)
}
</code></pre><blockquote><p><a href="https://developer.apple.com/documentation/foundation/processinfo"><code>ProcessInfo</code></a> is part of <code>Foundation</code>.</p></blockquote><pre><code>$ <span class="type">MYSECRET</span>=<span class="type">FiVeStArStOkEn</span> swift run hello
&gt; <span class="type">FiVeStArStOkEn</span>
</code></pre><h3>Pipeline Messages</h3><p>One of the most powerful features of scripts is the concept of <a href="https://en.wikipedia.org/wiki/Pipeline_(Unix)">pipeline</a> messages: this concept lets us chain multiple scripts, where a script <em>input</em> is the <em>output</em> of the previous script.</p><p>In order to support that, we can use <code>Foundation</code>'s <a href="https://developer.apple.com/documentation/foundation/filehandle"><code>FileHandle</code></a>:</p><pre><code><span class="keyword">import</span> Foundation

<span class="keyword">let</span> standardInput: <span class="type">FileHandle</span> = .<span class="dotAccess">standardInput</span>

<span class="keyword">if let</span> input = <span class="type">String</span>(data: standardInput.<span class="property">availableData</span>, encoding: .<span class="dotAccess">utf8</span>) {
  <span class="call">print</span>(input)
}
</code></pre><blockquote><p>This script expects data at launch: if there's no data, it's going to wait until some is given.</p></blockquote><p><code>FileHandle</code> manages data associated with files, sockets, pipes, and devices:<br>in our case we use it to read the available data from the <a href="https://developer.apple.com/documentation/foundation/filehandle/1413686-standardinput"><code>standardInput</code></a> terminal.</p><p>Here's an example where we use our new script in a pipeline with the list command <a href="https://en.wikipedia.org/wiki/Ls"><code>ls</code></a>:</p><pre><code>$ ls -<span class="number">1</span> | swift run hello
&gt; <span class="type">Package</span>.<span class="property">swift</span>
&gt; <span class="type">README</span>.<span class="property">md</span>
&gt; <span class="type">Sources</span>
&gt; <span class="type">Tests</span>
</code></pre><blockquote><p>In this case our script reads the pipeline input and prints it out.This is a similar behavior to the concatenate command <code>cat</code>.</p></blockquote><p>Similarly, here's how we'd use it for our script to pass data to the next script in the pipeline:</p><pre><code><span class="keyword">import</span> Foundation

<span class="keyword">let</span> standardOutput: <span class="type">FileHandle</span> = .<span class="dotAccess">standardOutput</span>

<span class="keyword">if let</span> outputData = <span class="string">"Five stars"</span>.<span class="call">data</span>(using: .<span class="dotAccess">utf8</span>) {
  standardOutput.<span class="call">write</span>(outputData)
}
</code></pre><p>In this example we take our script output and pass it to <code>cat</code>:</p><pre><code>$ swift run | cat
&gt; <span class="type">Five</span> stars
</code></pre><blockquote><p><code>cat</code> reads the pipeline input and prints it out.</p></blockquote><h3>Asynchronous Calls</h3><p>In our apps it's completely fine and even encouraged to do work asynchronously: when we pass a block to a <code>DispatchQueue</code>, we know that our block will run at some point in the future.</p><p>In the executables world, our scripts life ends as soon as we reach the end of the <code>main.swift</code> file:<br>if we dispatch something, it's likely that our script ends <em>before</em> the dispatched block had a chance to execute.</p><p>Does it mean that we cannot do any asynchronous work in our scripts? Absolutely not.<br>There are multiple ways to achieve this, I suggest using either <a href="https://developer.apple.com/documentation/foundation/runloop/1412430-run"><code>RunLoop.current.run()</code></a> or <a href="https://developer.apple.com/documentation/dispatch/1452860-dispatchmain"><code>dispatchMain()</code></a>, both of them do a similar thing: - the former puts the script loop on hold and waits for input data to process. - the latter starts a loop on the main queue which waits for blocks to execute.</p><p>What matters is that both stop the script from terminating, this way: - we can do as much (asynchronous) work as we like - once the work is complete, we must send an <code>exit</code> signal to terminate our execution</p><p>As an example, here's how we can make an asynchronous fetch request:</p><pre><code><span class="keyword">import</span> Foundation

<span class="keyword">let</span> url = <span class="type">URL</span>(string: <span class="string">"https://api.github.com/users/zntfdr"</span>)!
<span class="keyword">let</span> request = <span class="type">URLRequest</span>(url: url)
<span class="type">URLSession</span>.<span class="property">shared</span>.<span class="call">dataTask</span>(with: request) { data, <span class="keyword">_</span>, error <span class="keyword">in
  if let</span> data = data {
    <span class="keyword">let</span> responseText = <span class="type">String</span>(data: data, encoding: .<span class="dotAccess">utf8</span>)!
    <span class="call">print</span>(responseText)
    <span class="call">exit</span>(<span class="type">EXIT_SUCCESS</span>)
  } <span class="keyword">else</span> {
    <span class="call">print</span>(error!.localizedDescription)
    <span class="call">exit</span>(<span class="type">EXIT_FAILURE</span>)
  }
}.<span class="call">resume</span>()

<span class="type">RunLoop</span>.<span class="property">current</span>.<span class="call">run</span>() <span class="comment">// or dispatchMain()</span>
</code></pre><blockquote><p>Replace the <code>main.swift</code> content with this to try it out!</p></blockquote><h2>Common Patterns Standards</h2><p>Certain patterns are repeated across many scripts:<br>wouldn't it be great if there was a common standard for all the scripts to conform to?<br><br>The incredible team behind the Swift Package Manager has released two packages that do just that:<br>please welcome <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a> and <a href="https://github.com/apple/swift-tools-support-core"><code>swift-tools-support-core</code></a>.</p><p>More specifically, we're going to use the <code>SwiftToolsSupport</code> product of the latter, which consists of two libraries: <code>TSCUtility</code> and <code>TSCBasic</code> (TSC = <strong>T</strong>ools <strong>S</strong>upport <strong>C</strong>ore).</p><blockquote><p>This package was previously part of the <a href="https://github.com/apple/swift-package-manager">original <code>swift-package-manager</code> package</a>, and these two libraries were previously known as <a href="https://github.com/apple/swift-package-manager/blob/9abcc2260438177cecd7cf5185b144d13e74122b/Package.swift#L69"><code>SPMUtility</code></a> and <a href="https://github.com/apple/swift-package-manager/blob/9abcc2260438177cecd7cf5185b144d13e74122b/Package.swift#L68"><code>Basic</code></a>.</p></blockquote><p>As we will see shortly, these two modules offer powerful abstractions for common operations.</p><p>Even if these packages are released and maintained by people at Apple, they're not system modules, therefore we need to add them as a dependency to our own package, let's see how to do so!</p><h3>Adding a Package Dependency</h3><p>We've seen that <code>Package.swift</code> lists the package dependencies, let's add our new one there:</p><pre><code><span class="keyword">let</span> package = <span class="type">Package</span>(
    ...
    dependencies: [
        .<span class="call">package</span>(url: <span class="string">"https://github.com/apple/swift-tools-support-core.git"</span>,
                 from: <span class="string">"0.0.1"</span>),
        .<span class="call">package</span>(url: <span class="string">"https://github.com/apple/swift-argument-parser"</span>,
                 from: <span class="string">"0.0.1"</span>),
    ],
    targets: [
        ...
    ]
)
</code></pre><blockquote><p>If you'd like to find out the latest official version of any package, use the following link: <code>https://github.com/AUTHOR/PACKAGE/releases/latest</code>, for example <a href="https://github.com/apple/swift-tools-support-core/releases/latest"><code>https://github.com/apple/swift-tools-support-core/releases/latest</code></a>.</p></blockquote><p>Our package now depends on both ArgumentParser and <code>swift-tools-support-core</code>, next we need to declare which targets want to use a product of these packages, to do so, please update the <code>hello</code> target dependencies as seen below:</p><pre><code><span class="keyword">let</span> package = <span class="type">Package</span>(
    ...
    dependencies: [
        ...
    ],
    targets: [
        .<span class="call">target</span>(
            name: <span class="string">"hello"</span>,
            dependencies: [<span class="string">"ArgumentParser"</span>, <span class="string">"SwiftToolsSupport"</span>]),
        .<span class="call">testTarget</span>(...),
    ]
)
</code></pre><p>Our <code>hello</code> target can now use the libraries offered by both <code>ArgumentParser</code> and <code>SwiftToolsSupport</code>, let's try them out!</p><h3>Parse Input Arguments</h3><blockquote><p>This section has been rewritten to use the new Argument Parser, If you'd like to see how we got there, please see my deep dive into <code>TSCUtility</code>'s <code>ArgumentParser</code> <a href="https://www.fivestars.blog/articles/executables-argument-kind/">here</a>.</p></blockquote><blockquote><p>The <code>ArgumentParser</code> library has an incredible <a href="https://github.com/apple/swift-argument-parser/tree/master/Documentation">documentation section</a>, in this article I'm going to make a small example: make sure to bookmark the official <a href="https://github.com/apple/swift-argument-parser/tree/master/Documentation">documentation</a> for many more examples and insights into the library.</p></blockquote><blockquote><p>If you'd like to know more about the inner workings of the <code>ArgumentParser</code> package, please <a href="https://www.fivestars.blog/articles/a-look-into-argument-parser/">see this deep dive</a>.</p></blockquote><p>We've already seen how we can read launch arguments via the <code>CommandLine</code>, so why would we need to use <code>ArgumentParser</code> for this? Let me give you a few reasons:</p><ul><li>Previously we were manually reading an array of strings and then parse/convert them to something meaningful: this is equivalent of fetching a JSON object and then manually parse its contents instead of using <a href="https://www.fivestars.blog/articles/swift-decodable/"><code>JSONDecoder</code></a>.</li></ul><ul><li>Scripts might be launched with more than just raw values: we might expect a combination of flags (for example <code>ls -la</code>, <code>ls -l -a</code>, <code>ls -al</code>, <code>ls -a -l</code> are all valid commands and do the same thing), or a mix of flags and values.</li></ul><ul><li>Good scripts offer auto completion for their arguments.</li></ul><ul><li>Good scripts also come with documentation: wouldn't it be great if all our scripts follow the same pattern?</li></ul><p>...and this is why we use <a href="https://github.com/apple/swift-argument-parser">ArgumentParser</a>, which ticks all the boxes (beside autocompletion, <a href="https://github.com/apple/swift-argument-parser/issues/1">for now</a>), and more.</p><p>In this example we will require a flag <code>--name</code> with an associated value: after implementing it, the scripts will expect to be launched with something like <code>$ swift run hello --name YourName</code>:</p><ol><li>ArgumentParser requires us to define a type conforming to <code>ParsableCommand</code>: <code></code>`swift struct Hello: ParableCommand { } <code></code>`</li></ol><ol start="2"><li>Then we declare one or more expected arguments via one of the four <a href="https://github.com/apple/swift-argument-parser/blob/46464fd2e225193cda964494479e76c3d2c4a842/Documentation/02%20Arguments%2C%20Options%2C%20and%20Flags.md"><code>ArgumentParser</code>'s' property wrappers</a>: <code></code>`swift struct Hello: ParableCommand { @Option(help: "Specify your name.") var name: String } <code></code>`</li></ol><ol start="3"><li>To complete the definition, we need to add our custom logic in the <code>ParsableCommand</code>'s <code>run()</code> method:</li></ol><pre><code><span class="keyword">struct</span> Hello: <span class="type">ParsableCommand</span> {
  <span class="comment">// Declare expected launch argument(s).</span>
  <span class="keyword">@Option</span>(help: <span class="string">"Specify your name."</span>)
  <span class="keyword">var</span> name: <span class="type">String</span>

  <span class="comment">// Our custom logic goes here.</span>
  <span class="keyword">func</span> run() <span class="keyword">throws</span> {
    <span class="call">print</span>(<span class="string">"Hello</span> \(name)<span class="string">"</span>)
  }
}
</code></pre><blockquote><p><code>run()</code> executes only when all the declared launch arguments have been assigned. If an argument has a mistmatch or is missing, the script will rise and error and our logic won't run.</p></blockquote><p>We then can trigger Swift to both do the parsing and execute our logic via a static <code>ParsableCommand</code> function:</p><pre><code><span class="type">Hello</span>.<span class="call">main</span>()
</code></pre><p>Here's the complete code:</p><pre><code><span class="keyword">import</span> ArgumentParser

<span class="comment">// Define our parser.</span>
<span class="keyword">struct</span> Hello: <span class="type">ParsableCommand</span> {
  <span class="comment">// Declare expected launch argument(s).</span>
  <span class="keyword">@Option</span>(help: <span class="string">"Specify your name."</span>)
  <span class="keyword">var</span> name: <span class="type">String</span>

  <span class="keyword">func</span> run() <span class="keyword">throws</span> {
    <span class="call">print</span>(<span class="string">"Hello</span> \(name)<span class="string">"</span>)
  }
}

<span class="comment">// Run the parser.</span>
<span class="type">Hello</span>.<span class="call">main</span>()
</code></pre><blockquote><p>Replace the <code>main.swift</code> content with this to try it out!</p></blockquote><p>And here are a few examples of the script in action:</p><pre><code>$ swift run hello --name <span class="type">Federico</span>
&gt; <span class="type">Hello Federico</span>

$ swift run hello <span class="type">Federico</span>
&gt; <span class="type">Error</span>: <span class="type">Unexpected</span> argument '<span class="type">Federico</span>'
&gt; <span class="type">Usage</span>: hello --name &lt;name&gt;

$ swift run hello --name
&gt; <span class="type">Error</span>: <span class="type">Missing</span> value <span class="keyword">for</span> '--name &lt;name&gt;'
&gt; <span class="type">Usage</span>: hello --name &lt;name&gt;

$ swift run hello --help
&gt; <span class="type">USAGE</span>: hello --name &lt;name&gt;
&gt;
&gt; <span class="type">OPTIONS</span>:
&gt;   --name &lt;name&gt;           <span class="type">Specify</span> your name.
&gt;   -h, --help              <span class="type">Show</span> help information.
</code></pre><blockquote><p>This is how the <code>swift</code> command line tool works as well: if you run <code>$ swift --help</code>, or <code>$ swift run --help</code>, etc you will see exactly the same format as our new script.</p></blockquote><h3>Progress State</h3><p>In our apps we never want to block the UI while we're doing some work.<br>In scripts this is not always the case, because we might need to finish the current work before the user can continue its flow.</p><p>While this is completely fine and acceptable, leaving the terminal frozen while we're doing so is not the best user experience: the user might think that the script is stuck.</p><p>To address this issue, we can use one of the <a href="https://github.com/apple/swift-tools-support-core/blob/fcaa2ce5a852b5355aed5808a6610dc8b6dcf27e/Sources/TSCUtility/ProgressAnimation.swift">progress animations</a> that <code>TSCUtility</code> gives us, here's an example:</p><img src="https://www.fivestars.blog/assets/posts/ultimate-guide-swift-executables/loading.gif"/><p>Adding an animation is pretty straight forward:</p><ol><li>First we initialize the animation itself, in this case we're using <a href="https://github.com/apple/swift-tools-support-core/blob/fcaa2ce5a852b5355aed5808a6610dc8b6dcf27e/Sources/TSCUtility/ProgressAnimation.swift#L260"><code>PercentProgressAnimation</code></a>: <code></code>`swift let animation = PercentProgressAnimation( stream: stdoutStream, header: "Loading Awesome Stuff ✨" ) <code></code>`</li></ol><ol start="2"><li>Then we need to let the animation know when to update: <code></code>`swift animation.update(step: i, total: 100, text: "Loading..") <code></code>` &gt; Note how we don't need to worry about the presentation at all: we only pass the current step, the number ot total steps, and then <code>TSCUtility</code> will take care of the rest for us.</li></ol><ol start="3"><li>Lastly, we call animation complete when the work is done: <code></code>`swift animation.complete(success: true) <code></code>`</li></ol><p>And this is the final code:</p><pre><code><span class="keyword">import</span> Darwin
<span class="keyword">import</span> TSCBasic
<span class="keyword">import</span> TSCUtility

<span class="keyword">let</span> animation = <span class="type">PercentProgressAnimation</span>(
  stream: stdoutStream,
  header: <span class="string">"Loading Awesome Stuff ✨"</span>)

<span class="keyword">for</span> i <span class="keyword">in</span> <span class="number">0</span>..&lt;<span class="number">100</span> {
  <span class="keyword">let</span> second: <span class="type">Double</span> = <span class="number">1_000_000</span>
  <span class="call">usleep</span>(<span class="type">UInt32</span>(second * <span class="number">0.05</span>))
  animation.<span class="call">update</span>(step: i, total: <span class="number">100</span>, text: <span class="string">"Loading.."</span>)
}

animation.<span class="call">complete</span>(success: <span class="keyword">true</span>)
<span class="call">print</span>(<span class="string">"Done! 🚀"</span>)
</code></pre><blockquote><p>Replace the <code>main.swift</code> content with this to try it out!</p></blockquote><h3>Colors</h3><img src="https://www.fivestars.blog/assets/posts/ultimate-guide-swift-executables/colors.png"/><p>The progress state that we just talked about had something new that we haven't touched before: colors! Let's see how we can add colors to our scripts.</p><ol><li>First of all, we need to create a <a href="https://github.com/apple/swift-tools-support-core/blob/de90da12a45fe57991916836b7655128ae987871/Sources/TSCBasic/TerminalController.swift#L18"><code>TerminalController</code></a>, this object helps us control a terminal: for example it allows operations such as cursor movement and colored text output: <code></code>`swift let terminalController = TerminalController(stream: stdoutStream) <code></code>`</li></ol><ol start="2"><li>Then we must choose which <a href="https://github.com/apple/swift-tools-support-core/blob/de90da12a45fe57991916836b7655128ae987871/Sources/TSCBasic/TerminalController.swift#L33"><code>TerminalController.Color</code> variation</a> we would like to use, as of the current release, these are the options: <code>noColor</code>, <code>red</code>, <code>green</code>, <code>yellow</code>, <code>cyan</code>, <code>white</code>, <code>black</code>, and <code>grey</code>.</li></ol><ol start="3"><li>Lastly, we print our message in the command line: <code></code>`swift terminalController?.write("Hello World", inColor: .yellow, bold: true) <code></code>`</li></ol><ol start="4"><li>With the <code>TerminalController</code> we have more control over a simple <code>print</code> command, therefore the command <code>write</code> alone doesn't end the line: we must end the line ourselves by calling <code>endLine()</code>. <code></code>`swift terminalController?.endLine() <code></code>`</li></ol><p>Here's the example:</p><pre><code><span class="keyword">import</span> TSCBasic

<span class="keyword">let</span> terminalController = <span class="type">TerminalController</span>(stream: stdoutStream)

<span class="keyword">let</span> colors: [<span class="type">TerminalController</span>.<span class="type">Color</span>] = [
  .<span class="dotAccess">noColor</span>, .<span class="dotAccess">red</span>, .<span class="dotAccess">green</span>, .<span class="dotAccess">yellow</span>, .<span class="dotAccess">cyan</span>, .<span class="dotAccess">white</span>, .<span class="dotAccess">black</span>, .<span class="dotAccess">grey</span>
]

<span class="keyword">for</span> color <span class="keyword">in</span> colors {
  terminalController?.<span class="call">write</span>(<span class="string">"Hello World"</span>, inColor: color, bold: <span class="keyword">true</span>)
  terminalController?.<span class="call">endLine</span>()
}
</code></pre><blockquote><p>Replace the <code>main.swift</code> content with this to try it out!</p></blockquote><h2>Releasing A Script</h2><p>We now have everything we need to build a great script. There's a final step that we must take in order to complete our journey: ship it!</p><p>So far we've ran the script by using <code>$ swift run hello</code>, but this works only if we are in the <code>hello/</code> folder: how can we run our script from anywhere?</p><p>Two steps:</p><ol><li>Generate our script binary in release mode. <code></code>`shell $ swift build --configuration release <code></code>` Until now we've used the default <code>debug</code> build strategy when building our script, this works great while developing it, however it's no longer necessary when we want to release it. This configuration flag will create a new executable in the <code>.build/release/</code> folder.<ul></ul></li></ol><ol start="2"><li>Copy the new binary to the user binary folder. <code></code>`shell $ cp .build/release/hello /usr/local/bin/hello <code></code>` <code>/usr/local/bin/</code> is a folder know as <em>user binary folder</em> which contains plenty of executable binaries: anything placed there can be launched from the terminal by using its name, which means that we can now run our script with: <code></code>`shell $ hello <code></code>`</li></ol><p>Here are the two command lines for easy copy-pasting:</p><pre><code>$ swift build -c release
$ cp .<span class="dotAccess">build</span>/release/hello /usr/local/bin/hello
</code></pre><p>That's it! We can now run our script from anywhere!</p><h2>Conclusions</h2><p>In this article we've started from exploring how a Swift Executable is structured, how we can read its contents from the package manifest file, and then we've moved into common script patterns and how we can use Apple's <code>swift-tools-support-core</code> in order to achieve high quality scripts without having to do all the work ourselves.</p><p>I hope this reference guide helps you kick-start your Swift scripting journey, and I would love to know what scripts you're going to build! Please let me know <a href="https://twitter.com/zntfdr">on Twitter</a>!</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles! 🚀</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/the-swift-behind-the-standard-library-preview-package</guid><title>The Swift Behind The Standard Library Preview Package ✨</title><description></description><link>https://www.fivestars.blog/articles/the-swift-behind-the-standard-library-preview-package</link><pubDate>Thu, 20 Feb 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>If you're like me, you cannot wait to put your hands on the latest and greatest Swift features.</p><p>The most obvious example is probably Swift 5's <a href="https://github.com/apple/swift-evolution/blob/master/proposals/0235-add-result.md">introduction of <code>Result</code></a>: by the time it came out, tons of codebases had their own <code>Result</code> implementation already, all mine did.</p><p>There are multiple ways to test and use upcoming features without waiting for new Xcode (beta) releases: until recently the easiest one was via <a href="https://swift.org/download/">Swift</a> <a href="https://developer.apple.com/library/archive/documentation/ToolsLanguages/Conceptual/Xcode_Overview/AlternativeToolchains.html">Toolchains</a>, however, while toolchains are good for experimenting, they cannot be used to release apps to the store.</p><p>Thanks to the Swift team <a href="https://swift.org/blog/preview-package/">announcement</a> and <a href="https://github.com/apple/swift-standard-library-preview">release</a> of the <a href="https://github.com/apple/swift-standard-library-preview">Standard Library Preview Package</a>, this has now changed: with the Preview Package not only we can use and experiment new Swift features before any official release, but we can also ship apps with it!</p><blockquote><p>As the Preview Package is like any other Swift Package, only Swift features that are purely additive and do not access to internal Swift APIs can be previewed this way:other Swift changes (behavioral, breaking, etc) cannot be previewed with this package.</p></blockquote><p>Since this new package is <a href="https://github.com/apple/swift-standard-library-preview">open source</a>, let's have a look at it!</p><h2>Package Manifest</h2><p>Let's understand its structure first, which we do from its <a href="https://github.com/apple/swift-standard-library-preview/blob/833ea174a771bdc98edfa3529ae05439564e3902/Package.swift"><code>Package.swift</code> manifest</a>:</p><pre><code><span class="keyword">let</span> package = <span class="type">Package</span>(
  name: <span class="string">"swift-standard-library-preview"</span>,
  products: [
    .<span class="call">library</span>(
      name: <span class="string">"StandardLibraryPreview"</span>,
      targets: [<span class="string">"StandardLibraryPreview"</span>]),
  ],
  dependencies: exports.<span class="call">map</span> { $0.<span class="property">packageDependency</span> },
  targets: [
    .<span class="call">target</span>(
      name: <span class="string">"StandardLibraryPreview"</span>,
      dependencies: exports.<span class="call">map</span> { $0.<span class="property">targetDependency</span> }),
    
    .<span class="call">testTarget</span>(
      name: <span class="string">"ExportTests"</span>,
      dependencies: [<span class="string">"StandardLibraryPreview"</span>]),
  ]
)
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-standard-library-preview">Standard Library Preview Package</a>'s <a href="https://github.com/apple/swift-standard-library-preview/blob/833ea174a771bdc98edfa3529ae05439564e3902/Package.swift">Package.swift</a></p></blockquote><p>From this definition we can see that the Preview Package offers one library product named <code>StandardLibraryPreview</code>, which exposes the only target in the package, also named <code>StandardLibraryPreview</code>, which has the same dependencies as the whole package.</p><p>These dependencies are declared in <code>exports</code>, an array of <code>Export</code> elements:</p><pre><code><span class="keyword">struct</span> Export {
  <span class="keyword">var</span> package: <span class="type">String</span>
  <span class="keyword">var</span> requirement: <span class="type">Package</span>.<span class="type">Dependency</span>.<span class="type">Requirement</span>
  
  <span class="keyword">var</span> name: <span class="type">String</span> {
    <span class="keyword">let</span> parts = package.<span class="call">split</span>(separator: <span class="string">"-"</span>)
    <span class="keyword">let</span> <span class="type">SE</span> = parts[<span class="number">1</span>].<span class="call">uppercased</span>()
    <span class="keyword">let</span> name = parts[<span class="number">2</span>...].<span class="call">map</span> { $0.<span class="property">capitalized</span> }.<span class="call">joined</span>()
    <span class="keyword">return</span> <span class="string">"</span>\(<span class="type">SE</span>)<span class="string">_</span>\(name)<span class="string">"</span>
  }
  
  <span class="keyword">var</span> packageDependency: <span class="type">PackageDescription</span>.<span class="type">Package</span>.<span class="type">Dependency</span> {
    .<span class="call">package</span>(url: <span class="string">"https://github.com/apple/</span>\(package)<span class="string">"</span>, requirement)
  }
  
  <span class="keyword">var</span> targetDependency: <span class="type">Target</span>.<span class="type">Dependency</span> {
    .<span class="call">product</span>(name: name, package: package)
  }
}
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-standard-library-preview">Standard Library Preview Package</a>'s <a href="https://github.com/apple/swift-standard-library-preview/blob/833ea174a771bdc98edfa3529ae05439564e3902/Package.swift">Package.swift</a></p></blockquote><p><code>Export</code> tells us that every single Swift Evolution implementation that can be added to the Preview Package needs to:</p><ul><li>be its own Swift Package</li><li>be hosted under Apple's Github organization (<code>https://github.com/apple/...</code>)</li><li>have a <code>package</code> name with at least two dashes</li><li>offer a <code>library</code> product with a name based on the package name</li></ul><p>The last two requirements seems weird at first, but all becomes clear after looking at the actual <code>exports</code> array definition:</p><pre><code><span class="keyword">let</span> exports = [
  <span class="type">Export</span>(package: <span class="string">"swift-se0270-range-set"</span>, requirement: .<span class="call">upToNextMajor</span>(from: <span class="string">"1.0.0"</span>)),
]
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-standard-library-preview">Standard Library Preview Package</a>'s <a href="https://github.com/apple/swift-standard-library-preview/blob/833ea174a771bdc98edfa3529ae05439564e3902/Package.swift">Package.swift</a>.</p></blockquote><blockquote><p>At the moment there's just one Swift Evolution that can be previewed.</p></blockquote><p><a href="https://github.com/apple/swift-se0270-range-set"><code>swift-se0270-range-set</code></a> is the package <a href="https://github.com/apple/swift-se0270-range-set/blob/120fc7efe17b11f7eedd94e7a5e1885fc69efb49/Package.swift#L16">name</a>, while its vended library product name is <a href="https://github.com/apple/swift-se0270-range-set/blob/120fc7efe17b11f7eedd94e7a5e1885fc69efb49/Package.swift#L19"><code>SE0270_RangeSet</code></a>:<br>the two dashes requirement is for Apple to prefix all the packages with <code>swift-</code>, followed by the Swift Evolution code, <code>SE0270</code> in the example above, followed by another dash and then the name of the new swift feature, <code>range-set</code> in this case.</p><p>This naming convention makes it easy to manage all Preview Packages within the GitHub organization.</p><p>The manifest declaration is clever:<br>from now on only the <code>exports</code> definition needs to be updated, either when feature previews are added or removed, everything else will automatically work.</p><p>Every package target has its own folder, let's look at the <code>StandardLibraryPreview</code> folder next.</p><h2>StandardLibraryPreview.swift</h2><p>The only file in the this folder is <code>StandardLibraryPreview.swift</code>, and here it is in its entirety:</p><pre><code><span class="keyword">@_exported import</span> SE0270_RangeSet
</code></pre><blockquote><p>Snippet from <a href="https://github.com/apple/swift-standard-library-preview">Standard Library Preview Package</a>'s <a href="https://github.com/apple/swift-standard-library-preview/blob/833ea174a771bdc98edfa3529ae05439564e3902/Sources/StandardLibraryPreview/StandardLibraryPreview.swift">StandardLibraryPreview.swift</a>.</p></blockquote><p>From the manifest we already knew that <code>SE0270_RangeSet</code> is a library that we could import within the package, but what's this <code>@_exported</code> keyword?</p><h2>@_exported</h2><blockquote><p><code>@_exported</code> hasn't gone through Swift Evolution yet, as we can tell from the underscore prefix, it is possible that its behavior will change in the future.</p></blockquote><p>The short answer stands within the <a href="https://github.com/apple/swift-standard-library-preview/blob/833ea174a771bdc98edfa3529ae05439564e3902/README.md"><code>README</code> of the Preview Package</a>: the Preview Package acts as an umbrella library, re-exporting each of the individual (Swift Evolution Implementations) packages.</p><p>The <code>@_exported</code> attribute lets us export symbols from another module as if they were from ours.</p><p>Let's imagine that we would like to expand a library with new functionalities:<br>we've always been able to can add and use <code>extension</code>s within our module, but what if we would like to wrap up everything in a new module/package?</p><p>At that point we would discover that the users of our new module will have access to all our <code>public</code> types declarations, but won't have access to anything from the original package, not even the <code>public</code> extensions that we've made to elements of the original package.</p><p>To fix this we have two solutions:</p><ul><li>Ask our users to also <code>import</code> the original package, this way everything <code>public</code>, both from the original package and ours, is available to the users.</li></ul><ul><li>Use <code>@_exported</code> in our package: this way our users can <code>import</code> just our module, and they will automatically have access to both our <code>public</code> declarations and also to the ones from the original package. No further <code>import</code> necessary.</li></ul><p>Pretty powerful and elegant, don't you think?</p><h3>Overlay Libraries</h3><p>If we create a package that extends another as described above, we've created what is know as an <a href="https://github.com/apple/swift/blob/d842fa3763e171998ee3c583b4d7894843747e26/docs/Lexicon.rst">Overlay Package</a> (or library, module, etc).</p><p>Apple uses such libraries within its SDKs for example to bring Swift-specific functionality to C-family libraries or frameworks.</p><h3>Umbrella Libraries</h3><p>The Standard Library Preview Package is not an overlay package, instead, it is what is know as an umbrella library, which only exports a subset of packages, without adding any other functionality.</p><p>MacOS's <code>Cocoa</code> framework is another example of umbrella framework, which is an umbrella of three other frameworks: <code>AppKit</code>, <code>Foundation</code>, and <code>CoreData</code>.</p><h2>Conclusions</h2><p>That's it! The whole Standard Library Preview Package contains only two swift files (excluding tests):</p><ul><li>the <code>Package.swift</code> manifest</li><li>the <code>StandardLibraryPreview.swift</code> export file</li></ul><p>I always appreciate when something complicated can be solved in such a simple way: - from the Preview Package user point of view (us!), we need to <code>import StandardLibraryPreview</code>, and we automatically get all the new Swift features - from the Preview Package maintainer point of view, adding/removing new Swift implementations is a matter of removing an entry from the <code>Package.swift</code> <code>exports</code> array, and then remove the associated <code>@_exported import</code> in the <code>StandardLibraryPreview.swift</code> (excluding tests)</p><p>Lastly, we've seen how the Preview Package uses <code>@_exported</code>, a powerful implementation detail that has not yet gone through Swift Evolution, but that plays a very important role in this package.<br><br>If you'd like to know more about <code>@_exported</code> and its possible future evolution, please see <a href="https://forums.swift.org/t/exported-and-fixing-import-visibility/9415">this discussion</a> from the <a href="https://forums.swift.org">Swift Forums</a>.</p><p>Have you ever seen <code>@_export</code> (or <a href="https://forums.swift.org/t/update-on-implementation-only-imports/26996"><code>@_implementationOnly</code></a> or ...) out in the wild? Do you or are you going to use them yourself? If so please <a href="https://twitter.com/zntfdr">let me know on Twitter</a>! I'd love to see more of them 😃</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/the-state-of-machine-learning-in-ios-13</guid><title>The State of Machine Learning in iOS 13 🦠</title><description></description><link>https://www.fivestars.blog/articles/the-state-of-machine-learning-in-ios-13</link><pubDate>Tue, 4 Feb 2020 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Did you know that the second most discussed topic at WWDC19 was machine learning?</p><p>With 15 videos and about 400 minutes of content, machine learning comes second only to SwiftUI (11 videos and 450 minutes), everything else, including ARKit and Combine, don't come even close.</p><p>2019 was also the year where I've decided to dive deeper in this topic, and the <a href="https://www.facebook.com/groups/iosthailand/">Thai local iOS meetup</a> was kind enough to host my presentation on the matter: on this talk you can find an overview of the current state of machine learning in iOS 13, how to get started, and more.</p><p>You can view the whole presentation here:</p><p>{% include videoThumb.html videoUrl="https://www.youtube.com/watch?v=DUqScctukMo" imageSrc="/assets/posts/the-state-of-machine-learning-in-ios-13/yt.jpeg" %}</p><p>And here are the slides:</p><p><a href="https://speakerdeck.com/zntfdr/the-state-of-machine-learning-in-ios-13"><img src="https://www.fivestars.blog/assets/posts/the-state-of-machine-learning-in-ios-13/first-slide.jpg" alt="slides"/></a></p><blockquote><p>Check out the full slides at <a href="https://speakerdeck.com/zntfdr/the-state-of-machine-learning-in-ios-13">SpeakerDeck</a></p></blockquote><p>As always, you can download slides and other materials in my <a href="https://github.com/zntfdr/talks"><code>talks</code> repository</a> (more talks soon!).</p><p>If you have any feedback, please <a href="https://twitter.com/zntfdr">let me know</a> 😃</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/swift-5-1-collection-diffing</guid><title>Swift 5.1 Collection Diffing</title><description></description><link>https://www.fivestars.blog/articles/swift-5-1-collection-diffing</link><pubDate>Tue, 23 Jul 2019 00:00:00 +0000</pubDate><content:encoded><![CDATA[<blockquote><p>Disclaimer: this article refers to the <a href="https://github.com/apple/swift/pull/21845 ">first Swift implementation</a> of collection diffing. The implementation itself <a href="https://github.com/apple/swift/pull/25808">has already been improved</a>, however the concepts behind this article are exactly the same.</p></blockquote><p>Until recently, dealing with dynamic tables/collection views meant one of the following:</p><ul><li>Call <code>reloadData()</code> at every change</li><li>Use a dependency like <a href="https://github.com/Instagram/IGListKit">IGListKit</a> or <a href="https://github.com/ra1028/DifferenceKit">DifferenceKit</a></li><li>Manually deal with every possible change (while praying to not miss anything 🤞🏻)</li></ul><p>Swift 5.1 introduces a new alternative: Use the new <code>Collection</code>’s <code>difference(from:)</code>.</p><blockquote><p>Want to know about what else is new Swift 5.1? Check out my <a href="https://www.fivestars.blog/articles/what-s-new-in-swift-5-1/">lightning talk here</a>.</p></blockquote><p>Unlike other features, Swift collection diffing is entirely additive and it is completely written in Swift:<br>this presents us an unique opportunity to learn how the collection diffing is actually implemented without digging into any other lower level language.</p><p>In this article I'm going to do exactly that: at over 5000 words with a lot of technicalities and code, you might want to find a comfortable place before digging in.</p><p>Ready? Let's go!</p><h2>CollectionDifference&lt;ChangeElement&gt;</h2><p>Before talking about the function itself, I want to focus on what it returns:<br>an instance of <code>CollectionDifference&lt;ChangeElement&gt;</code>.</p><p><a href="https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/CollectionDifference.swift#L16 ">CollectionDifference</a> is a generic <code>Collection</code> defined as "<em>A collection of insertions and removals that describe the difference between two ordered collection states</em>".</p><p>The generic type <code>ChangeElement</code> is the element type of the two collections that we are diffing, note that, if we look solely at the <code>CollectionDifference</code> definition, <code>ChangeElement</code> is completely unconstrained: it doesn’t even need to conform to <code>Equatable</code>!</p><p>The elements of this collection are of type <a href="https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/CollectionDifference.swift#L227 "><code>CollectionDifference.Change</code></a>, which is a new enum with two cases: <code>.insert</code> and <code>.remove</code>. Before moving into this enum, there are a couple of points that must be disclosed about <code>CollectionDifference</code>:</p><ol><li>The collection <a href="https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/CollectionDifference.swift#L253">returns</a> first all the removals (all the <code>.remove</code> cases), and then all the insertions (all the <code>.insert</code> cases). This is assured by <a href="https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/CollectionDifference.swift#L168 ">an internal initializer</a>: even if we try to initialize this collection with changes in random order, the initialized collection will be always ordered properly.</li><li>The collection insertions, deletions, and association between the two must be unique (more on this later). This is assured by an <a href="https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/CollectionDifference.swift#L89">internal validator</a>: while trying to initialize an unsorted <code>CollectionDifference</code> is allowed, failing this uniqueness validation will fail the collection initialization.</li></ol><h3>Insertions and Removals</h3><p>If we’re interested exclusively on the insertions <strong>or</strong> the removals of a <code>CollectionDifference</code>, instead of traversing the collection itself, we can use its public properties <a href="https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/CollectionDifference.swift#L70 "><code>insertions</code></a> and <a href="https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/CollectionDifference.swift#L70 "><code>removals</code></a>, which are two arrays (with elements of type <code>CollectionDifference.Change</code>).</p><p>These arrays are actually where the elements of the main collection are stored, and accessing them directly is more performant the traversing the collection itself.</p><h2>CollectionDifference.Change</h2><p>When comparing two collections, you can think of any change between the collections elements as either:</p><ul><li>an insertion (a new element has been added)</li><li>a removal (an element has been removed)</li><li>or both (when an element changes position, it is <em>removed</em> from the current position and <em>added</em> to the new position).</li></ul><p>With that being said, it comes as no surprise that <code>CollectionDifference.Change</code> is an <code>Enum</code> with two cases: <code>insert</code> and <code>remove</code>.</p><p>Both cases come with three associated values: an offset, an element, and an optional association.</p><h3>Change Offset</h3><p>The offset is the position of the change.</p><p>In case of a removal, it reflects the position where the element <a href="https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/CollectionDifference.swift#L30 ">used to be</a>:</p><pre><code><span class="keyword">let</span> oldArray = [<span class="string">"a"</span>, <span class="string">"b"</span>, <span class="string">"c"</span>, <span class="string">"d"</span>]
<span class="keyword">let</span> newArray = [<span class="string">"a"</span>, <span class="string">"b"</span>, <span class="string">"d"</span>]

<span class="comment">// The element "c" at index 2 has been removed</span>

<span class="keyword">let</span> difference = newArray.<span class="call">difference</span>(from: oldArray) 
<span class="comment">// difference is a one-element collection
// where the element is of type `.remove` with `offset` 2</span>
</code></pre><p>In the case of an insertion, it reflects the position of the element in the <a href="https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/CollectionDifference.swift#L22">final</a> collection:</p><pre><code><span class="keyword">let</span> oldArray = [<span class="string">"a"</span>, <span class="string">"b"</span>, <span class="string">"c"</span>, <span class="string">"d"</span>]
<span class="keyword">let</span> newArray = [<span class="string">"a"</span>, <span class="string">"b"</span>, <span class="string">"c"</span>, <span class="string">"d"</span>, <span class="string">"e"</span>]

<span class="comment">// A new element "e" has been added at index 4</span>

<span class="keyword">let</span> difference = newArray.<span class="call">difference</span>(from: oldArray) 
<span class="comment">// difference is a one-element collection
// where the element is of type `.insert` with `offset` 4</span>
</code></pre><h3>Change Element</h3><p>This element is the whole reason why <code>CollectionDifference</code> is declared generic:<br>the change element is the actual collection element that has been removed or inserted.</p><pre><code><span class="keyword">let</span> oldArray = [<span class="string">"a"</span>, <span class="string">"b"</span>, <span class="string">"c"</span>, <span class="string">"d"</span>]
<span class="keyword">let</span> newArray = [<span class="string">"a"</span>, <span class="string">"b"</span>, <span class="string">"d"</span>, <span class="string">"e"</span>]

<span class="comment">// The element "c" at index 2 has been removed
// A new element "e" has been added at index 3</span>

<span class="keyword">let</span> difference = newArray.<span class="call">difference</span>(from: oldArray)
<span class="comment">// difference is a two elements collection:
// - the first element is of type `.remove` (with offset 2) with element "c"
// - the second element is of type `.insert` (with offset 3) with element "e"</span>
</code></pre><h3>Change Association</h3><p>Again, when an element moves position between two collection states, the change can be considered as a deletion (from the old position) and as an insertion (to the new position).</p><p>This is what the <code>Change</code> association is for: to link that removal and that insertion.<br>The association, called <code>associatedWith</code>, is an index representing <em>the other</em> change offset:<br>if we’re looking at a <code>.insert</code> change, the <code>associatedWith</code> index will be equal to the associated <code>.remove</code> <code>offset</code> index, and vice versa.</p><p>As change associations add an extra cost on the diffing, they’re not computed by default:<br>the <code>difference(from:)</code> result is a collection of <code>.remove</code>/<code>.insert</code> elements all with <code>associatedWith</code> value set to <code>nil</code>. If we’re also interested in this association, we need to call <a href="https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/CollectionDifference.swift#L323"><code>.inferringMoves()</code></a> in our difference collection:</p><pre><code><span class="keyword">let</span> oldArray = [<span class="string">"a"</span>, <span class="string">"b"</span>, <span class="string">"d"</span>, <span class="string">"e"</span>, <span class="string">"c"</span>]
<span class="keyword">let</span> newArray = [<span class="string">"a"</span>, <span class="string">"b"</span>, <span class="string">"c"</span>, <span class="string">"d"</span>, <span class="string">"e"</span>]

<span class="comment">// the element "c" has moved from index 4 to 2
// therefore we can think it as:
// - a removal from position 4
// - and an insertion at position 2</span>

<span class="keyword">let</span> difference = newArray.<span class="call">difference</span>(from: oldArray)
<span class="comment">// difference is a two elements collection 
// - the first element is a `.remove` with offset 4 
//   and associated offset of `nil`
// - the second element is an `.insert` with offset 2
//   and associated offset of `nil`</span>

let differenceWithMoves = difference.<span class="call">inferringMoves</span>()
<span class="comment">// differenceWithMoves is a two elements collection 
// - the first element is a `.remove` with offset 4 
//   and associated offset 2
// - the second element is an `.insert` with offset 2 
//   and associated offset 4</span>
</code></pre><h4>InferringMoves</h4><p><a href="https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/CollectionDifference.swift#L323">This function</a> is straightforward: it scans the whole <code>CollectionDifference</code> instance and adds an association when an element change matches both among the removals and the insertions, otherwise it just returns the Change <em>unchanged</em>.</p><h3>A Look Back to CollectionDifference’s Rules</h3><p>Now that we have a clear definition of what the <code>Change</code> type is, we can look back to the <code>CollectionDifference</code> definition and have a clearer understanding of some of its rules:</p><ul><li>Having non-unique removals would mean having multiple removals at the same index: it's impossible.</li><li>Same with additions: having multiple additions at the same offset would mean having multiple elements at the same index in the final collection state.</li><li>Lastly, associations must always come in pairs (an insertion linked to a removal and vice versa): there can’t be a one way association.</li></ul><h3>A Second CollectionDifference Order</h3><p>If we look at the how the collection internal <a href="https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/CollectionDifference.swift#L168">init</a> is defined, we can see that <code>CollectionDifference</code> has a clear order that goes beyond "<em>removals first and insertions second</em>":<br>all the insertions are stored in the <code>insertions</code> array in order from lowest to highest <code>offset</code>, and same goes for the deletions with the <code>removals</code> array.</p><p>However, if we look at the <code>CollectionDifference</code> <a href="https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/CollectionDifference.swift#L253"><code>subscript</code></a>, we can see that the collection is exposed in, again, a different order:<br>the removals are exposed from highest to lowest <code>offset</code>, while the insertions from lowest to highest <code>offset</code>.</p><p>This exposed order allows us to use the returned <code>CollectionDifference</code> instance to transform a collection from the old state to the new state by applying, one by one, the collection <code>Change</code>s:</p><pre><code><span class="keyword">let</span> oldArray = [<span class="string">"a"</span>, <span class="string">"b"</span>, <span class="string">"c"</span>, <span class="string">"d"</span>]
<span class="keyword">let</span> newArray = [<span class="string">"x"</span>, <span class="string">"a"</span>, <span class="string">"e"</span>, <span class="string">"c"</span>]

<span class="keyword">var</span> anotherArray = oldArray

<span class="keyword">let</span> difference = newArray.<span class="call">difference</span>(from: oldArray)
<span class="comment">// difference is a four elements collection:
// 1. `.remove` at offset 3
// 2. `.remove` at offset 1 
// 3. `.insert` at offset 0
// 4. `.insert` at offset 2</span>

<span class="keyword">for</span> change <span class="keyword">in</span> difference {
  <span class="keyword">switch</span> change {
  <span class="keyword">case let</span> .<span class="call">remove</span>(offset, <span class="keyword">_</span>, <span class="keyword">_</span>):
    anotherArray.<span class="call">remove</span>(at: offset)
  <span class="keyword">case let</span> .<span class="call">insert</span>(offset, newElement, <span class="keyword">_</span>):
    anotherArray.<span class="call">insert</span>(newElement, at: offset)
  }
}
<span class="comment">// at this point `anotherArray` is equal to `newArray`</span> 
</code></pre><p><code>anotherArray</code> starts in the same state of <code>oldArray</code> and ends up like <code>newArray</code> by following the <code>difference</code> collection order:</p><pre><code>[<span class="string">"a"</span>, <span class="string">"b"</span>, <span class="string">"c"</span>, <span class="string">"d"</span>] <span class="comment">// initial state</span>
[<span class="string">"a"</span>, <span class="string">"b"</span>, <span class="string">"c"</span>] <span class="comment">// removal at index 3</span>
[<span class="string">"a"</span>, <span class="string">"c"</span>] <span class="comment">// removal at index 1</span>
[<span class="string">"x"</span>, <span class="string">"a"</span>, <span class="string">"c"</span>] <span class="comment">// insertion at index 0</span>
[<span class="string">"x"</span>, <span class="string">"a"</span>, <span class="string">"e"</span>, <span class="string">"c"</span>] <span class="comment">// insertion at index 2</span>
</code></pre><p>If the collection was ordered in any another way, we would have obtained a different outcome (or even a crash!).</p><h3>Applying</h3><p>If we need to apply the <code>difference</code> result to a collection, we don’t have to do it ourselves (like in the example above):<br>Swift offers a new method, <a href="https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/Diffing.swift#L68"><code>applying(_:)</code></a>, that does it for us.</p><p>Therefore the example above could have been written entirely as:</p><pre><code><span class="keyword">let</span> oldArray = [<span class="string">"a"</span>, <span class="string">"b"</span>, <span class="string">"c"</span>, <span class="string">"d"</span>]
<span class="keyword">let</span> newArray = [<span class="string">"x"</span>, <span class="string">"a"</span>, <span class="string">"e"</span>, <span class="string">"c"</span>]

<span class="keyword">var</span> anotherArray = oldArray

<span class="keyword">let</span> difference = newArray.<span class="call">difference</span>(from: oldArray)

<span class="comment">// applying the difference here 👇🏻</span> 
anotherArray = anotherArray.<span class="call">applying</span>(difference)!

<span class="comment">// at this point `anotherArray` is equal to `newArray`</span> 
</code></pre><p>Since this method can be called on <em>any</em> collection (as long the collection elements are compatible), instead of applying the <code>difference</code> in place, it returns a new optional collection with the difference applied.</p><p>The reason why the return type is optional is clear as soon as we imagine doing a <code>remove</code> or an <code>insert</code> to an index out of range:<br>instead of crashing, the function will return <code>nil</code> when the difference is <em>applied</em> to an incompatible collection.</p><h2>difference(from:)</h2><p>Now that we’ve covered the basics, it’s time to uncover what this method actually does, it might take you by surprise (it did to me!) but the whole implementation is a one liner:</p><pre><code><span class="keyword">extension</span> <span class="type">BidirectionalCollection</span> <span class="keyword">where</span> <span class="type">Element</span> : <span class="type">Equatable</span> {
  <span class="keyword">public func</span> difference&lt;C: <span class="type">BidirectionalCollection</span>&gt;(
    from other: <span class="type">C</span>
  ) -&gt; <span class="type">CollectionDifference</span>&lt;<span class="type">Element</span>&gt; <span class="keyword">where</span> <span class="type">C</span>.<span class="type">Element</span> == <span class="type">Self</span>.<span class="type">Element</span> {
    <span class="keyword">return</span> <span class="call">difference</span>(from: other, by: ==)
  }
}
</code></pre><p>It turns out that we were using a convenience method all along!</p><p>Before digging into the real <code>difference(from:by:)</code> method, it's important to note how it's <code>difference(from:)</code> that requires the elements of the collections to conform to <code>Equatable</code>, this requirement is nowhere to be seen in <code>difference(from:by:)</code> 👍🏻.</p><h2>difference(from:by:)</h2><p>So far we’ve used <code>CollectionDifference</code> to compute the difference in the <em>equality</em> of two states of a collection, however the truth is that this comparison could be used for many more use cases:<br>in fact, the <a href="https://forums.swift.org/t/ordered-collection-diffing/18933 ">original pitch</a> for collection diffing describes the feature as a "<em>diffing functionality […] necessary to provide easy creation, representation, and application of ordered collection state transitions</em>".<br>A <em>state transition</em> can have different meanings based on the context, therefore it makes a lot of sense for Swift to set no limits on what this <em>transition</em> can be.</p><p>With that being said, it should come with no surprise that the real <code>difference</code> function signature is:</p><pre><code>   <span class="keyword">public func</span> difference&lt;C: <span class="type">BidirectionalCollection</span>&gt;(
    from other: <span class="type">C</span>,
    by areEquivalent: (<span class="type">Element</span>, <span class="type">C</span>.<span class="type">Element</span>) -&gt; <span class="type">Bool</span>
  ) -&gt; <span class="type">CollectionDifference</span>&lt;<span class="type">Element</span>&gt;
  <span class="keyword">where</span> <span class="type">C</span>.<span class="type">Element</span> == <span class="type">Self</span>.<span class="type">Element</span> 
</code></pre><p>Note how non-constraining this function is: all it requires is two <a href="https://developer.apple.com/documentation/swift/bidirectionalcollection "><code>BidirectionalCollection</code></a>s (two collections that can be traversed both by moving backward and/or forward) with the same associated types.</p><p>The associated types don’t need to conform to any specific protocol, all it matters is that we pass a method that takes two elements of this type and returns a boolean:<br>what this <em>comparison</em> method does, and what it means, is entirely up to the developer.</p><h3>An Example</h3><p>As a fun example, let’s run <code>difference(from:by:)</code> with two identical collections and a comparison method that returns always <code>false</code>:</p><pre><code><span class="keyword">let</span> oldArray = [<span class="string">"a"</span>, <span class="string">"b"</span>, <span class="string">"c"</span>]
<span class="keyword">let</span> newArray = [<span class="string">"a"</span>, <span class="string">"b"</span>, <span class="string">"c"</span>] <span class="comment">// same as `oldArray`</span>

let difference = newArray.<span class="call">difference</span>(from: oldArray, 
                                     by: { <span class="keyword">_</span>, <span class="keyword">_  in false</span> })
<span class="comment">// `difference` is a 6 elements collection with:
// - three removals
// - three insertions</span>
</code></pre><p>The result is a collection with three removals and three insertions:<br>this is because our comparison method (which, again, returns always <code>false</code> in this example) removes any connection between any element in either state, therefore the <code>difference</code> algorithm will see this transition as a complete rewrite of the array, regardless of the contents of those arrays!</p><h3>A Note on difference(from:by:) Return Type</h3><p><code>difference</code> returns a non-optional <code>CollectionDifference</code>.<br>Previously I've mentioned that a <code>CollectionDifference</code> initialization fails when we try to initialize it with elements that do not comply to its rules. How does Swift guarantees a non-optional <code>CollectionDifference</code> instance?</p><p>While Swift exposes only one failable initializer for <code>CollectionDifference</code>, it turns out that there's also a non-failable initializer:<br>this second initializer is marked as <code>internal</code>, therefore it can only be used within the Swift repository, and it is not exposed when we use Swift from a toolchain in Xcode.</p><p>This is because the internal initializer is used only with algorithms that have been mathematically proven to instantiate a correct <code>CollectionDifference</code>, therefore it is ok for Swift to return a non-optional collection.</p><h3>Inside difference(from:by:)</h3><p>The first thing that <a href="https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/Diffing.swift#L134">this method</a> does is creating two collections, <code>source</code> and <code>target</code>, of internal type <code>_CountingIndexCollection</code>:<br>the <code>source</code> reflects the <em>old</em> state of the original collection, while <code>target</code> reflects the <em>new</em> state of the original collection.</p><p>If we call <code>["a","b","c"].difference(from: ["e", "f", "g"])</code> for example, <code>source</code> mirrors <code>["e", "f", "g"]</code> while <code>target</code> mirrors <code>["a", "b", "c"]</code>.</p><p><code>_CountingIndexCollection</code> is a wrapper of the real collection, with an easy way to get the offset of its indices from its start index.</p><pre><code><span class="keyword">let</span> originalCollection = [<span class="string">"a"</span>, <span class="string">"b"</span>, <span class="string">"c"</span>, <span class="string">"d"</span>]
<span class="type">Let</span> offsetCollection = <span class="type">_CountingIndexCollection</span>(originalCollection)
<span class="keyword">if let</span> index = offsetCollection.<span class="call">index</span>(of: <span class="string">"d"</span>) {
  <span class="call">print</span>(index.<span class="property">offset</span>) <span class="comment">// prints "3"</span>
  <span class="call">print</span>(offsetCollection[index]) <span class="comment">// prints "d"</span>
}
</code></pre><p>Lastly, the method creates an instance of internal type <code>_CollectionChanges</code>, which takes the newly created <code>source</code> and <code>target</code> collections along with our injected comparison block.</p><p>After the <code>_CollectionChanges</code> initialization is complete, the <code>difference</code> method maps back the <code>_CollectionChanges</code> instance into an array of <code>Change</code> instances, and returns a <code>CollectionDifference</code> initialized with these changes.</p><p>Therefore, the real diffing doesn’t happen in this method, but inside the <code>_CollectionChanges</code> initialization:<br>we need to explore what this <code>_CollectionChanges</code> is all about.</p><h3>_CollectionChanges</h3><p><a href="https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/Diffing.swift#L247"><code>_CollectionChanges</code></a> is a generic collection of changes (<em>duh!</em>) between a source and target collection. In the documentation it is stated that an instance of this type can be used to:</p><ul><li>Traverse the <a href="http://en.wikipedia.org/wiki/Longest_common_subsequence_problem">longest common subsequence</a> of <code>source</code> and <code>target</code>, which means the longest subsequence common between the two collections. For example, the longest common sequence between X<strong>MJ</strong>Y<strong>AU</strong>Z and <strong>M</strong>Z<strong>JA</strong>WX<strong>U</strong> is MJAU: the subsequence, as long as it is found while traversing both collections in the same direction, doesn’t have to occupy consecutive positions within the original collections.</li></ul><ul><li>Traverse the <a href="http://en.wikipedia.org/wiki/Edit_distance">shortest edit script</a> of remove and insert operations, which means finding the minimum number of operations needed to transform the <code>source</code> collection into the <code>target</code>.</li></ul><p>These definitions/challenges are actually dual:<br>if we solve one, we've also found a way to solve the other as well!</p><p>Both challenges are very valuable to our diffing: since additions and removals are costly, and no change is free, solving those problems means finding the cheapest (read: fastest) way to turn our old collection state into the new collection.</p><p>Let's take two collections for example, <code>ABCABBA</code> and <code>CBABAC</code>: how many ways there are to transform one into the other?<br>The answer is several: <code>_CollectionChanges</code> promises to find the most efficient one.</p><h3>Endpoint</h3><p>The first declaration that we met inside the <code>_CollectionChanges</code> body is a new <code>typealias</code> that forms a tuple by taking one index of the <code>source</code> collection and one from the <code>target</code> collection:</p><pre><code><span class="keyword">typealias</span> Endpoint = (x: <span class="type">SourceIndex</span>, y: <span class="type">TargetIndex</span>)
</code></pre><p>This is what we will use when associating two locations of the two collections:<br>For example, if we want to associate the first element of the <code>source</code> with the second element of the <code>target</code>, we will define the tuple (nee, the <code>Endpoint</code>) as <code>(1, 2)</code>.</p><h3>PathStorage: An Hidden Swift Game 👾</h3><p>Let’s assume to have a 2D chart in front of us. The X-axis is our <code>source</code> collection and our Y-axis is our <code>target</code> collection.</p><pre><code>   | • | <span class="type">X</span> | <span class="type">A</span> | <span class="type">B</span> | <span class="type">C</span> | <span class="type">D</span> |
 • |   |   |   |   |   |   |
 <span class="type">X</span> |   |   |   |   |   |   |
 <span class="type">Y</span> |   |   |   |   |   |   |
 <span class="type">C</span> |   |   |   |   |   |   |
 <span class="type">D</span> |   |   |   |   |   |   |
</code></pre><p>You’re now tasked to draw a path that goes:</p><ul><li>from <code>(0,0)</code></li><li>to <code>(lastIndexOfTarget, lastIndexOfSource)</code></li></ul><pre><code>   | • | <span class="type">X</span> | <span class="type">A</span> | <span class="type">B</span> | <span class="type">C</span> | <span class="type">D</span> |
 • | <span class="type">S</span> |   |   |   |   |   | 
 <span class="type">X</span> |   |   |   |   |   |   | <span class="comment">// S: Start Here</span> 
 <span class="type">Y</span> |   |   |   |   |   |   | <span class="comment">// T: Target Destination</span>
 <span class="type">C</span> |   |   |   |   |   |   |
 <span class="type">D</span> |   |   |   |   |   | <span class="type">T</span> |

<span class="comment">// Start Position S: (0,0)
// Target Position T: (4,5)</span>
</code></pre><blockquote><p>Fill the gaps</p></blockquote><p>Rules:</p><ul><li>You can only advance left to right (no going back)</li><li>You can only advance top to bottom (no going back)</li><li>You can advance left to right and top to bottom at the same time (diagonal move) only if you advance equally in both directions</li><li>You can advance left to right and top to bottom at the same time (diagonal move) only when the X-axis and Y-axis elements are the same</li></ul><blockquote><p>Note: I’m using the equality as a comparison for simplicity sake, however the rules above are still applied in general sense.</p></blockquote><p>The game is to find the allowed path with the most diagonal moves.</p><p>Sounds familiar? Because it is:<br>this game is exactly what we introduced before in <code>_CollectionChanges</code> as the <em>longest common subsequence</em> challenge.</p><p>Finding the path with the most diagonal moves is the same as finding the <em>longest common subsequence</em> of our two collections, and finding the path with the most diagonal moves means finding the path with as little non-diagonal moves as possible. Non-diagonal moves correspond to a change:</p><ul><li>a vertical move (top to bottom) is an insertion</li><li>while an horizontal move correspond to a deletion.</li></ul><p>Here’s the solution of the game above:</p><pre><code>   | • | <span class="type">X</span> | <span class="type">A</span> | <span class="type">B</span> | <span class="type">C</span> | <span class="type">D</span> |
 • | <span class="type">S</span> |   |   |   |   |   | 
 <span class="type">X</span> |   | x | x | x |   |   | <span class="comment">// S: Start Here</span> 
 <span class="type">Y</span> |   |   |   | x |   |   | <span class="comment">// T: Target Destination</span>
 <span class="type">C</span> |   |   |   |   | x |   |
 <span class="type">D</span> |   |   |   |   |   | <span class="type">T</span> |

<span class="comment">// Final path:</span>
(<span class="number">0</span>,<span class="number">0</span>) → (<span class="number">1</span>,<span class="number">1</span>) → (<span class="number">1</span>,<span class="number">2</span>) → (<span class="number">1</span>,<span class="number">3</span>) → (<span class="number">2</span>,<span class="number">3</span>) → (<span class="number">3</span>,<span class="number">4</span>) → (<span class="number">4</span>,<span class="number">5</span>)
</code></pre><p>Let's look at the moves, one by one:</p><ul><li>we start at <code>(0,0)</code>, nothing to see here</li><li>we first move diagonally to <code>(1,1)</code>, as both axis have the value <code>X</code> at that position</li><li>then we move horizontally to <code>(1,2)</code>, which is equivalent to removing <code>A</code></li><li>then we move horizontally to <code>(1,3)</code>, which is equivalent to removing <code>B</code></li><li>then we move vertically to <code>(2,3)</code>, which is equal to inserting <code>Y</code> to our collection</li><li>the we move diagonally to <code>(3,4)</code> which is allowed because we have <code>C</code> in both axis</li><li>lastly we move diagonally to <code>(4,5)</code>, which is allowed because we have <code>D</code> in both axis, arriving to our target destination.</li></ul><p>In other words, we can use this path to find the difference operations necessary to go from <code>target</code> to <code>destination</code>, looking back again to the path: - the two horizontal moves, <code>(1,1) → (1,2)</code> and <code>(1,2) → (1,3)</code>, correspond to two deletions - the one vertical move, <code>(1,3) → (2,3)</code>, correspond to an insertion</p><blockquote><p>Before translating those moves to an <code>offset</code> of <code>CollectionDifference.Change</code> we must remember that the indexes of this path are shifted by one, as <code>(0,0)</code> is the initial position in the chart, and not the start index of each collection.</p></blockquote><p>Remember our <code>Endpoint</code> definition?</p><pre><code><span class="keyword">typealias</span> Endpoint = (x: <span class="type">SourceIndex</span>, y: <span class="type">TargetIndex</span>)
</code></pre><p>The path that we have just found is just an array of endpoints:<br>the second declaration that we find in <code>_CollectionChanges</code> is <code>PathStorage</code>: which is where we store this path.</p><h3>pathStartIndex</h3><p>Our final property found inside <code>_CollectionChanges</code> is <code>pathStartIndex</code>, which is the index in <code>pathStorage</code> of the first segment in the <em>difference path</em>.</p><p>This is an internal optimization:<br>if two collections have the same prefix, like <strong>AE</strong>OS and <strong>AE</strong>FT, it’s no use to start doing our diffing in the common prefix AE, therefore we skip it and do all our diffing operations starting from the <code>pathStartIndex</code> instead.</p><h3>enum Element</h3><p>This is an <code>internal</code> enum declaration that will help us when we need to transform the path found above into a <code>CollectionDifference.Change</code> instance:</p><pre><code>   <span class="keyword">enum</span> Element {
    <span class="keyword">case</span> removed(<span class="type">Range</span>&lt;<span class="type">SourceIndex</span>&gt;)
    <span class="keyword">case</span> inserted(<span class="type">Range</span>&lt;<span class="type">TargetIndex</span>&gt;)
    <span class="keyword">case</span> matched(<span class="type">Range</span>&lt;<span class="type">SourceIndex</span>&gt;, <span class="type">Range</span>&lt;<span class="type">TargetIndex</span>&gt;)
  }
</code></pre><p>Note how the associated values of each case is a range instead of a simple index:<br>while in the example above we've separated each move to one step, multiple <em>consecutive</em> moves in the same direction can be grouped together into one move, therefore moves like our two horizontal moves <code>(1,1) → (1,2) → (1,3)</code> can be grouped into one as <code>(1,1) → (1,3)</code>, therefore we can describe our path succinctly, and this contraction allows us to use ranges.</p><p>If you recall were we started, we've said that our diffing method initializes a <code>_CollectionChanges</code> instance: &gt; After the <code>_CollectionChanges</code> initialization is complete, the <code>difference</code> method maps back the <code>_CollectionChanges</code> instance into an array of <code>Change</code> instances, and returns a <code>CollectionDifference</code> initialized with these changes.</p><blockquote><p>an excerpt of the chapter "Inside difference(from:by:)"</p></blockquote><p>This <code>Element</code> definition is exactly what <code>_CollectionChanges</code> is a collection of, therefore what our original <code>difference</code> method does is take these <code>Element</code> instances and, one by one, turn them into <code>Change</code>s (while ignoring all the matches).</p><p>We've covered the basics: it's time to look at the <code>_CollectionChanges</code> initialization.</p><h3>_CollectionChanges Main init</h3><blockquote><p>As a reminder, this <code>init</code> has three parameters: our two collection states and the comparison method.</p></blockquote><p>This init does two things: - initialize the <code>pathStartIndex</code> with value <code>0</code> and the <code>PathStorage</code> array to an empty array - call a <code>private</code> method <code>formChanges</code> while passing to it all the <code>init</code> parameters.</p><h3>_CollectionChanges formChanges</h3><blockquote><p>As a reminder, what we want to do now is filling the <code>_CollectionChanges</code>'s <code>PathStorage</code> (which is an array of <code>Endpoint</code>s, which correspond to our path as we've seen above).</p></blockquote><p>Once called, this method does some initial checks.</p><p>If both collection states are empty (e.g. we're comparing two empty arrays), this method stops immediately: the difference is empty as there's nothing to compare.</p><p>If we continue, it means that at least one of the collection states is not empty.</p><p>This is the point where our method finds the common prefix among the collection states (according to our comparison method):</p><ul><li>if the common prefix is equal to at least one of the two collections, then the change between the two collections is trivial: going back to our path game, having a collection equal to the prefix of the other means that we can use diagonal moves until we hit one of the edges. Once we hit the edge, we are only allowed to move either by going down (insertions) or right (deletions). Here are three examples where the collections are one the prefix of the other.</li></ul><pre><code>   <span class="comment">// source = ASDFO 
  // target = ASD
  // the target is a prefix of the source:</span>

     | • | <span class="type">A</span> | <span class="type">S</span> | <span class="type">D</span> | <span class="type">F</span> | <span class="type">O</span> |
   • | <span class="type">S</span> |   |   |   |   |   | 
   <span class="type">A</span> |   | x |   |   |   |   | <span class="comment">// S: Start Here</span> 
   <span class="type">S</span> |   |   | x |   |   |   | <span class="comment">// T: Target Destination</span>
   <span class="type">D</span> |   |   |   | x | x | <span class="type">T</span> |
                   ↑
          we hit the edge here

  <span class="comment">// Final path:</span>
  (<span class="number">0</span>,<span class="number">0</span>) → (<span class="number">3</span>,<span class="number">3</span>) → (<span class="number">3</span>,<span class="number">5</span>)

  <span class="comment">// Which translates into:
  // - three matches (0,0) → (3,3)
  // - two deletions (of letter F, and O), (3,3) → (3,5)

  // source = ASD
  // target = ASDFO
  // the source is a prefix of the target:</span>

     | • | <span class="type">A</span> | <span class="type">S</span> | <span class="type">D</span> |
   • | <span class="type">S</span> |   |   |   | <span class="comment">// S: Start Here</span> 
   <span class="type">A</span> |   | x |   |   | <span class="comment">// T: Target Destination</span>
   <span class="type">S</span> |   |   | x |   | 
   <span class="type">D</span> |   |   |   | x | ← we hit the edge here
   <span class="type">F</span> |   |   |   | x |
   <span class="type">O</span> |   |   |   | x |

  <span class="comment">// Final path:</span>
  (<span class="number">0</span>,<span class="number">0</span>) → (<span class="number">3</span>,<span class="number">3</span>) → (<span class="number">5</span>,<span class="number">3</span>)

  <span class="comment">// Which translates into:
  // - three matches (0,0) → (3,3)
  // - two insertions (of letter F, and O), (3,3) → (5,3)

  // source = ASD
  // target = ASD
  // both collections are equal, therefore they are each other's prefixes</span>

     | • | <span class="type">A</span> | <span class="type">S</span> | <span class="type">D</span> |
   • | <span class="type">S</span> |   |   |   | <span class="comment">// S: Start Here</span> 
   <span class="type">A</span> |   | x |   |   | <span class="comment">// T: Target Destination</span>
   <span class="type">S</span> |   |   | x |   | 
   <span class="type">D</span> |   |   |   | <span class="type">T</span> | ← we hit the edge here

  <span class="comment">// Final path:</span>
  (<span class="number">0</span>,<span class="number">0</span>) → (<span class="number">3</span>,<span class="number">3</span>)

  <span class="comment">// Which translates into:
  // - three matches (0,0) → (3,3)</span>
</code></pre><blockquote><p>Once again I’m using the equality as a comparison for simplicity sake.</p></blockquote><p>Based on which of the three cases we fall into, our final <code>pathStorage</code> will have two or three elements (endpoints) and the method returns.</p><ul><li>Lastly, if we arrive here, it means that our collection states are:<ul><li>Not the same (according to our comparison method)</li><li>Not empty</li><li>Might have a common prefix</li></ul></li></ul><p>This is when our method <code>formChanges</code> calls another method, <code>formChangesCore</code>.</p><h3>_CollectionChanges formChangesCore</h3><blockquote><p>Remember what we are accomplishing:we are trying to find the most efficient way to go from one collection state to another, by using as little insertions/deletions as possible, while reusing the <code>source</code> collection elements as much as possible (a.k.a. finding the path with the most diagonal moves in our path game).</p></blockquote><p>This method is invoked with a few parameters: the usual original collections and comparison method, plus the end indexes on each collection of their common prefix (that we have just computed above).</p><p>Since we've already got rid of the base cases, this method is (finally!) where the magic happens.</p><p>To put it simply, this method implements the "<a href="http://www.xmailserver.org/diff2.pdf">Greedy LCS/SES Algorithm</a>" published in 1986 (!) by <a href="https://en.wikipedia.org/wiki/Eugene_Myers">Eugene W. Myers</a>.</p><h4>The Greedy LCS/SES Algorithm</h4><blockquote><p>Curious on which algorithm IGListKit uses? Check out <a href="http://documents.scribd.com/docs/10ro9oowpo1h81pgh1as.pdf">Paul Heckel's paper here</a>. <a href="https://github.com/lxcid/ListDiff">Here</a>'s a Swift implementation of said algorithm.</p></blockquote><p>Without going too deeply on the theory behind the algorithm, which is explained and demonstrated very clearly in the <a href="http://www.xmailserver.org/diff2.pdf">original paper</a> (approachable by everyone), the algorithm is based on three steps:</p><ol><li>Find the furthest valid path that starts at <code>(0,0)</code> and has up to <code>D</code> non-diagonal moves.</li></ol><ol start="2"><li>If we've reached the Target Destination, end the algorithm.</li></ol><ol start="3"><li>If not, increase <code>D</code> by one (initially set to <code>0</code>) and start again.</li></ol><p>In order to get to the Target Destination, instead of computing over and over all the possible valid paths with <code>D</code> non-diagonal moves, the algorithm stores the previous explored paths.</p><p>However, not all possible paths are stored, nor all the possible paths are explored.</p><p>Instead, the algorithm bases its research for the best path on the <em>diagonals</em> of our 2D chart game, these diagonals are defined as <code>k = x - y</code>. Here are a few examples:</p><pre><code>   diagonal with k = <span class="number">0</span>       diagonal with k = <span class="number">1</span>       diagonal with k = <span class="number">2</span>

     | • | <span class="type">A</span> | <span class="type">S</span> | <span class="type">D</span> |         | • | <span class="type">A</span> | <span class="type">S</span> | <span class="type">D</span> |         | • | <span class="type">A</span> | <span class="type">S</span> | <span class="type">D</span> |    
   • | k |   |   |   |       • |   | k |   |   |       • |   |   | k |   |
   <span class="type">A</span> |   | k |   |   |       <span class="type">A</span> |   |   | k |   |       <span class="type">A</span> |   |   |   | k |
   <span class="type">S</span> |   |   | k |   |       <span class="type">S</span> |   |   |   | k |       <span class="type">S</span> |   |   |   |   | 
   <span class="type">D</span> |   |   |   | k |       <span class="type">D</span> |   |   |   |   |       <span class="type">D</span> |   |   |   |   |
   <span class="type">F</span> |   |   |   |   |       <span class="type">F</span> |   |   |   |   |       <span class="type">F</span> |   |   |   |   |
   <span class="type">O</span> |   |   |   |   |       <span class="type">O</span> |   |   |   |   |       <span class="type">O</span> |   |   |   |   |

  diagonal with k = -<span class="number">1</span>      diagonal with k = -<span class="number">2</span>      diagonal with k = -<span class="number">3</span>

     | • | <span class="type">A</span> | <span class="type">S</span> | <span class="type">D</span> |         | • | <span class="type">A</span> | <span class="type">S</span> | <span class="type">D</span> |         | • | <span class="type">A</span> | <span class="type">S</span> | <span class="type">D</span> |    
   • |   |   |   |   |       • |   |   |   |   |       • |   |   |   |   |
   <span class="type">A</span> | k |   |   |   |       <span class="type">A</span> |   |   |   |   |       <span class="type">A</span> |   |   |   |   |
   <span class="type">S</span> |   | k |   |   |       <span class="type">S</span> | k |   |   |   |       <span class="type">S</span> |   |   |   |   |
   <span class="type">D</span> |   |   | k |   |       <span class="type">D</span> |   | k |   |   |       <span class="type">D</span> | k |   |   |   |
   <span class="type">F</span> |   |   |   | k |       <span class="type">F</span> |   |   | k |   |       <span class="type">F</span> |   | k |   |   |
   <span class="type">O</span> |   |   |   |   |       <span class="type">O</span> |   |   |   | k |       <span class="type">O</span> |   |   | k |   |
</code></pre><p>Here are the first few iterations of the algorithm:</p><ul><li>Initially, the algorithm starts at <code>(0,0)</code> and finds the furthest path with <code>0</code> non diagonal moves, which will necessarily end its path in the 0th diagonal (<code>k = 0</code>).</li></ul><ul><li>On the first iteration (if we haven't reached the Target Destination yet), we try to find the furthest paths that have <code>1</code> non diagonal moves, <strong>starting from where we left in the previous found path</strong>. We will necessarily end up on a diagonal with k equal to 1 or -1 (remember, by moving to the diagonal with <code>k = 1</code> it means that we do an insertion, dually, we perform a removal by moving into the diagonal with <code>k = -1</code>). Assuming that we haven't arrived to the final destination yet, we have 2 potential paths now: one that ends in diagonal 1 and one in diagonal -1. We keep both and keep iterating.</li></ul><ul><li>On the next iteration we will try to find the furthest path with <code>2</code> non diagonal moves, instead of starting from scratch, again, we use the two potential paths from the previous iteration. Starting from the previous two potential paths that ended in diagonal 1 and -1, this iteration paths will necessarily end in either diagonal -2, 0, or 2. If we haven't found the Target Destination yet, we keep the furthest path on each diagonal (three in total) and keep iterating.</li></ul><blockquote><p>Would you like to see this process in action? Robert Elder has an interactive Visualization <a href="http://blog.robertelder.org/diff-algorithm/">here</a> (jump to step 5!)</p></blockquote><p>This iteration keeps going until we finally reach the Target Point.</p><p>A few important observations:</p><ul><li>a path at a given iteration with "x" non-diagonal moves is just a path from the previous iteration, with "x-1" moves, and an extra non diagonal move (and potentially multiple diagonal moves).</li></ul><ul><li>we grow the number of potential paths by one at every iteration:<ul><li>we start with one (that lies at diagonal 0)</li><li>then we grow to 2 (one that lies at diagonal 1, one at diagonal -1)</li><li>then 3 (diagonals -2, 0, 2)</li><li>etcetera</li></ul></li></ul><ul><li>at a given iteration D, all the recorded path will end in one of the following diagonals: { −D, −D+2, ... D−2, D }.<ul><li>we start with one path that lays at diagonal 0 (iteration D = 0 -&gt; diagonals {0})</li><li>at the first iteration we have two paths, one at diagonal -1, one at diagonal 1 (iteration D = 1 -&gt; diagonals {-1, 1})</li><li>at the next iteration we have three paths, one at diagonal -2, one at diagonal 0, one at diagonal 2 (iteration D = 2 -&gt; diagonals {-2, 0, 2})</li><li>etcetera</li></ul></li></ul><p>We're basically alternating diagonals (while also expanding at each iteration).</p><blockquote><p>All these observations are demonstrated in the <a href="http://www.xmailserver.org/diff2.pdf">original paper</a>.</p></blockquote><p>That's the whole algorithm! Let's see how to implement it in Swift.</p><h4>The Greedy LCS/SES Algorithm in Swift</h4><p>Since <code>formChanges</code> passes the computed end indexes of the common prefix of our two collection states, the first iteration has been done already:<br>computing the common prefix of the two collections is equivalent to finding the furthest path with <code>0</code> non diagonal moves.</p><p>All we have to do now is to store this first path somewhere.</p><h5>_SearchState</h5><p>As we've observed during the explanation of the algorithm, at every iteration we increase the potential paths by one, and one potential path at a given iteration is exactly a potential path from the previous iteration with one extra non-diagonal move.<br>Therefore we can describe each potential path by pointing at its last move, which is an <code>Endpoint</code> instance as we've defined before, and then refer to its previous path from the previous iteration.<br>This is exactly what the private <code>_SearchState</code> generic struct does.</p><p>This struct relies on an internal generic storage called <code>_LowerTriangularMatrix</code>:<br>by definition, a square matrix is called lower triangular if all the entries above the main diagonal are zero. Example:</p><pre><code>| * | <span class="number">0</span> | <span class="number">0</span> | <span class="number">0</span> |
 | * | * | <span class="number">0</span> | <span class="number">0</span> | 
 | * | * | * | <span class="number">0</span> | 
 | * | * | * | * |  
</code></pre><p>In order to optimize performance and allocation, this <code>_LowerTriangularMatrix</code> externally behaves like a real lower triangular matrix (with subscript and all), however internally is just an array of elements along with a <code>dimension</code> property (this is all this type needs to mirror a real lower triangular matrix).</p><p>Going back to <code>_SearchState</code>, the struct considers every row of its storage (which, again, is a lower triangular matrix) as the <em>search frontier</em> of each iteration, while the columns represent the furthest path on the <em>x</em>th diagonal for that search frontier.</p><p>In other words:</p><ul><li>the position <code>(0,0)</code> in the storage contains the final position of our furthest path with <code>0</code> non-diagonal moves starting at <code>(0,0)</code> in our game chart.</li><li>the positions <code>(1,0)</code> and <code>(1,1)</code> in the storage contain the furthest paths with <code>1</code> non-diagonal moves, the former contains the one that ends in diagonal -1, while the latter contains the one that ends in diagonal 1.</li><li>etcetera</li></ul><h5>The Algorithm</h5><p>Finally let's take a look at the algorithm.</p><p>Each line comes with extended explanation, to associate everything with what we've discussed so far.</p><pre><code><span class="keyword">private mutating func</span> formChangesCore&lt;Source: <span class="type">BidirectionalCollection</span>, 
                                      Target: <span class="type">BidirectionalCollection</span>&gt;(
  from a: <span class="type">Source</span>, to b: <span class="type">Target</span>,
  x: <span class="type">Source</span>.<span class="type">Index</span>, y: <span class="type">Target</span>.<span class="type">Index</span>,
  by areEquivalent: (<span class="type">Source</span>.<span class="type">Element</span>, <span class="type">Target</span>.<span class="type">Element</span>) -&gt; <span class="type">Bool</span>
) <span class="keyword">where</span>
  <span class="type">Source</span>.<span class="type">Element</span> == <span class="type">Target</span>.<span class="type">Element</span>, <span class="type">Source</span>.<span class="type">Index</span> == <span class="type">SourceIndex</span>, 
  <span class="type">Target</span>.<span class="type">Index</span> == <span class="type">TargetIndex</span>
{
  <span class="comment">// current furthest position coordinate, 
  // according to our collection common prefix.
  // a.k.a. the furthest path with `0` non-diagonal moves</span>
  <span class="keyword">var</span> (x, y) = (x, y)

  <span class="comment">// our Target Destination coordinates</span>
  <span class="keyword">let</span> (n, m) = (a.<span class="property">endIndex</span>, b.<span class="property">endIndex</span>)

  <span class="comment">// initialize the storage</span>
  <span class="keyword">var</span> v = <span class="type">_SearchState</span>&lt;<span class="type">Source</span>.<span class="type">Index</span>, <span class="type">Target</span>.<span class="type">Index</span>&gt;(consuming: &amp;pathStorage)

  <span class="comment">// put the end of the common prefix endpoint 
  // at position `(0,0)` of our storage</span>
  v.<span class="call">appendFrontier</span>(repeating: (x, y))

  <span class="comment">// set the iteration number</span>
  <span class="keyword">var</span> d = <span class="number">1</span>
  
  <span class="comment">// used later</span>
  <span class="keyword">var</span> delta = <span class="number">0</span>

  <span class="comment">// iteration body</span>
  outer: <span class="keyword">while true</span> {
    <span class="comment">// expand our storage to contain a new row of _search frontier_
    // a.k.a. the furthest paths endpoints of the `d`th iteration</span>
    v.<span class="call">appendFrontier</span>(repeating: (n, m))

    <span class="comment">// one of the observations of the algorithm was about the fact
    // that at a given iteration D, all the furthest paths would
    // lie at the diagonals { -D, -D + 2, ... D -2, D }
    // This observation explains the following stride</span>
    <span class="keyword">for</span> k <span class="keyword">in</span> <span class="call">stride</span>(from: -d, through: d, by: <span class="number">2</span>) {

      <span class="comment">// if we're finding the leftmost new path, a.k.a. the one with
      // diagonal -d, or if we're finding the new furthest path at a
      // diagonal kth, which is not the right most new diagonal,
      // a.k.a. k == t, and if the furthest path in diagonal k-1 has
      // traveled our `target` collection less than or furthest path
      // in diagonal k+1 in our previous iteration...</span>
      <span class="keyword">if</span> k == -d || (k != d &amp;&amp; v[d - <span class="number">1</span>, k - <span class="number">1</span>].x &lt; v[d - <span class="number">1</span>, k + <span class="number">1</span>].x) {

        <span class="comment">// ...then we start computing our new furthest path by
        // reading the previous iteration furthest path at diagonal
        // k+1</span>
        (x, y) = v[d - <span class="number">1</span>, k + <span class="number">1</span>]

        <span class="comment">// if we are not at the edge, move down by one</span>
        <span class="keyword">if</span> y != m { b.<span class="call">formIndex</span>(after: &amp;y) }
      } <span class="keyword">else</span> {

        <span class="comment">// like above, but this time we use the previous iteration
        // furthest path at diagonal k-1</span>
        (x, y) = v[d - <span class="number">1</span>, k - <span class="number">1</span>]

        <span class="comment">// if we are not at the edge, move right by one</span>
        <span class="keyword">if</span> x != n { a.<span class="call">formIndex</span>(after: &amp;x) }
      }

      <span class="comment">// with the if/else above we have just "used" our non-diagonal
      // move of this iteration

      // now that we've moved either down or right by one, check if
      // we can move diagonally, and do it as long as you can.

      // find the common prefix, according to our comparison method, 
      // from the current position in the two collection states</span>
      <span class="keyword">let</span> matches = a[x..&lt;n].<span class="call">_commonPrefix</span>(with: b[y..&lt;m], 
                                           by: areEquivalent)

      <span class="comment">// our new furthest path at the *d*th iteration for diagonal 
      // *k*th is equivalent to the end of the prefix that we've
      // just found</span>
      (x, y) = (matches.<span class="number">0</span>.<span class="property">endIndex</span>, matches.<span class="number">1</span>.<span class="property">endIndex</span>)

      <span class="comment">// store the new endpoint</span>
      v[d, k] = (x, y)

      <span class="comment">// if this new endpoint matches the target destination...</span>
      <span class="keyword">if</span> x == n &amp;&amp; y == m {
        <span class="comment">// ...note down at which diagonal we've stopped at..</span>
        delta = k

        <span class="comment">// ...and end the algorithm.</span>
        <span class="keyword">break</span> outer
      }
    }
    <span class="comment">// if we haven't found found the target destination yet, 
    // increase the depth by one and iterate again.</span>
    d += <span class="number">1</span>
  }

  <span class="comment">// after finding the cheapest path at the *d*th iteration, on 
  // the *delta*th diagonal, convert our `_SearchState` instance
  // storage into the final `_CollectionChanges` instance.</span>
  <span class="keyword">self</span> = v.<span class="call">removeCollectionChanges</span>(a: a, b: b, d: d, delta: delta)
}
</code></pre><h3>Conclusions</h3><p>There you have it: if you've come so far, congratulations! You now know exactly how Swift implements the diffing between two collections.</p><p>It took us a long journey to arrive here, however, for something non-trivial and error-prone like collection diffing, it's very good to have a native, efficient solution implemented right into the language itself.</p><p>Lastly, if you are an iOS or mac developer, I would like to remind you that you shouldn't use this in collection and table views:<br>with the upcoming iOS and macOS releases, we will have an even better API that takes care of diffing and more for us. You can watch and hear all about Collection and Table Diffable Data Sources in the WWDC19 session <a href="https://developer.apple.com/videos/play/wwdc2019/220/">here</a>, or, if you prefer an article form, you can read about them in <a href="https://twitter.com/johnsundell">John Sundell</a>'s article <a href="https://wwdcbysundell.com/2019/diffable-data-sources-first-look/">here</a>.</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p><h3>References</h3><ul><li><a href="https://forums.swift.org/t/ordered-collection-diffing/18933 ">The original pitch</a>.</li><li><a href="https://github.com/apple/swift-evolution/blob/master/proposals/0240-ordered-collection-diffing.md ">The Swift Evolution proposal</a>.</li><li>The proposal <a href="https://forums.swift.org/t/se-0240-ordered-collection-diffing/19514">review thread</a>.</li><li>The Swift implementation <a href="https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/Diffing.swift">here</a> and <a href="https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/CollectionDifference.swift">here</a>.</li></ul>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/large-content-viewer</guid><title>iOS 13 Large Content Viewer 🔎</title><description></description><link>https://www.fivestars.blog/articles/large-content-viewer</link><pubDate>Tue, 9 Jul 2019 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>When a user uses one of the Larger Text Accessibility Sizes, everything on the screen is huge.</p><p>Some UI elements, however, must stay small, even in these cases:<br>every inch of screen estate is incredibly precious.</p><p>Among such UI elements, for example, we have navigation bars, tab bars, toolbars, and the status bar:</p><img src="https://www.fivestars.blog/assets/posts/large-content-viewer/bars.png" alt="bars"/><blockquote><p>From left to right: Safari.app's Toolbar, Contacts.app's Navigation Bar, iOS Springboard's Status Bar, and News.app's Tab Bar.</p></blockquote><p>Still, the problem remains:<br>users with large text sizes aren't able to read any of those elements. To overcome this, in iOS 11 Apple has introduced the Large Content Viewer:</p><img src="https://www.fivestars.blog/assets/posts/large-content-viewer/large-contents.png" alt="large-contents"/><blockquote><p>The same apps as in the image above, now with Large Content Viewer: imagine a finger on the bars.</p></blockquote><h2>The Large Content Viewer</h2><p>Tap and hold your finger on any bar element and, as long as you're using an accessibility text size, you'll get this big HUD, named Large Content Viewer, showing you both the element icon and title.</p><p>While all of this is awesome, any other UI element was left behind...until now.</p><h3>UILargeContentViewerItem</h3><p>Coming with iOS 13 is a new UIKit protocol named <code>UILargeContentViewerItem</code>, which lets <strong>any</strong> UI element describe what its Large Content Viewer should be.</p><p>The best part is that <a href="https://developer.apple.com/documentation/uikit/uilargecontentvieweritem#conforming-types">most of native UIKit elements</a> (read: any <code>UIView</code> subclass) already conform to this protocol. Let's have a look at its requirements:</p><pre><code><span class="keyword">@available</span>(iOS <span class="number">13.0</span>, *)
<span class="keyword">public protocol</span> UILargeContentViewerItem: <span class="type">NSObjectProtocol</span> {

    <span class="keyword">var</span> showsLargeContentViewer: <span class="type">Bool</span> { <span class="keyword">get</span> }

    <span class="keyword">var</span> largeContentTitle: <span class="type">String</span>? { <span class="keyword">get</span> }

    <span class="keyword">var</span> largeContentImage: <span class="type">UIImage</span>? { <span class="keyword">get</span> }

    <span class="keyword">var</span> scalesLargeContentImage: <span class="type">Bool</span> { <span class="keyword">get</span> }

    <span class="keyword">var</span> largeContentImageInsets: <span class="type">UIEdgeInsets</span> { <span class="keyword">get</span> }
}
</code></pre><ul><li><strong>showsLargeContentViewer</strong> Whether the UI element shows a Large Content Viewer. Return <code>false</code> to have the default behavior (and not show anything).</li><li><strong>largeContentTitle</strong> This is the title shown in the large content viewer, optional.</li><li><strong>largeContentImage</strong> This is the image shown in the large content viewer, optional.</li><li><strong>scalesLargeContentImage</strong> Let iOS know whether the image should be scaled to a size appropriate for the Large Content Viewer, or show the image at its intrinsic size.</li><li><strong>largeContentImageInsets</strong> In case you need to use insets to center the image in the HUD, you can declare them with this property.</li></ul><h3>Disclaimer</h3><p>As stated above, most of your UI already conforms to this protocol, however this doesn't mean that everything in our UI should have its own Large Content Viewer:<br>Apple really wants us to use Dynamic Type as much as possible, and only resort to the Large Content Viewer in very special UI elements (like the UIKit bars we've seen above).</p><p>With the disclaimer out of the way, let's see how we can finally display this HUD.</p><h3>Adoption</h3><p>First of all, we need to let UIKit know that our elements support Large Content Viewer:<br>we do so by setting our views <code>showsLargeContentViewer</code> property to <code>true</code>. While most of UIKit elements conform to the <code>UILargeContentViewerItem</code> protocol, most of them have this option turned off by default.</p><pre><code><span class="keyword">let</span> myView = <span class="type">UIView</span>()
<span class="comment">// ... setup view here</span>
myView.<span class="property">showsLargeContentViewer</span> = <span class="keyword">true</span>
</code></pre><blockquote><p>There's no way to currently do this via storyboard. If you, too, feel this should be added, please write Apple a <a href="https://feedbackassistant.apple.com/feedback/6438145">feedback</a>.</p></blockquote><p>Most views, like <code>UILabel</code>s, <code>UIImage</code>s, <code>UIButton</code>s, and more, already know what they should provide, therefore there's no need to set their <code>largeContentTitle</code> and <code>largeContentImage</code> properties.<br>Regardless, in case you do:</p><pre><code><span class="keyword">let</span> myView = <span class="type">UIView</span>()
<span class="comment">// ... setup view here</span>
myView.<span class="property">largeContentTitle</span> = <span class="string">"Five Stars"</span>
myView.<span class="property">largeContentImage</span> = <span class="type">UIImage</span>(systemName: <span class="string">"star.fill"</span>)
</code></pre><blockquote><p>If you've never seen a <code>UIImage(systemName:)</code> before, have a look at the <a href="https://www.fivestars.blog/articles/ios-dark-mode-how-to/">Dark Mode article</a> where I also talk about Apple's brand new System Symbols.</p></blockquote><p>Lastly, we must add a new <code>UILargeContentViewerInteraction</code> to our views:</p><pre><code><span class="keyword">let</span> myView = <span class="type">UIView</span>()
<span class="comment">// ... setup view here</span>
myView.<span class="call">addInteraction</span>(<span class="type">UILargeContentViewerInteraction</span>())
</code></pre><blockquote><p>Like we've seen in the <a href="https://www.fivestars.blog/articles/uicontextmenuinteraction/">Context Menu article</a>, we don't need to retain these new interactions.</p></blockquote><p>And that's it! Now we're fully supporting the new Large Content Viewer 🚀.</p><h3>Debugging</h3><p>There are essentially two ways in order to try out this new <em>custom</em> HUD: 1. run your app in a device with one of the accessibility sizes active 2. use the simulator and change the size with the new Environment Overrides button (more on this in the <a href="https://www.fivestars.blog/articles/ios-dark-mode-how-to/">Dark Mode article</a>).</p><p>I like the latter, especially since you can also change the simulator size preference by going in the settings.app and change the accessibility size there (no need to change it at every app launch via Environment Overrides).</p><p>And here's the result in <a href="https://apps.apple.com/us/developer/federico-zanetello/id1053443073">my own apps</a>:</p><img src="https://www.fivestars.blog/assets/posts/large-content-viewer/metro-large-content-viewer.gif" alt="metro-large-content-viewer"/><blockquote><p>Cool, isn't it? #A11y</p></blockquote><h2>Conclusions</h2><p>Apple is putting an incredible amount of effort to make it easy for developers to make their app more accessible: this new Large Content Viewer is one of many examples of that.</p><p>For more information, watch the WWDC19 session <a href="https://developer.apple.com/videos/play/wwdc2019/261/">Large Content Viewer- Ensuring Readability for Everyone</a> and feel free to watch any other wwdc accessibility session available as well.</p><p>Lastly, if you, too, believe it would be very useful to let developers use this native HUD in other scenarios as well, <a href="https://github.com/SVProgressHUD/SVProgressHUD">without</a> <a href="https://github.com/JonasGessner/JGProgressHUD">the</a> <a href="https://github.com/relatedcode/ProgressHUD">need</a> <a href="https://github.com/johnlui/SwiftNotice">to</a> <a href="https://github.com/pkluz/PKHUD">use</a> <a href="https://github.com/krimpedance/KRProgressHUD">an</a> <a href="https://github.com/harikrishnant1991/JHProgressHUD">external</a> <a href="https://github.com/fifyrio/XYProgressHUD">dependecy</a>, I encourage you to write Apple a <a href="https://feedbackassistant.apple.com/feedback/6438145">feedback</a> like I did.</p><p>As always, if you're going to implement this accessibility feature, I'd love to see it!<br><a href="https://twitter.com/zntfdr">Hit me up on Twitter</a> with your work 🤗</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/uicontextmenuinteraction</guid><title>Behind The Scenes Of Context Menus</title><description></description><link>https://www.fivestars.blog/articles/uicontextmenuinteraction</link><pubDate>Mon, 24 Jun 2019 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>iOS 13 comes with a fresh interpretation of Peek and Pop called <a href="https://developer.apple.com/design/human-interface-guidelines/ios/controls/context-menus/">Context Menus</a>.</p><img src="https://www.fivestars.blog/assets/posts/uicontextmenuinteraction/splashscreen.gif" alt="contextMenus"/><blockquote><p>The new menus, in both light and dark mode.</p></blockquote><p>What’s super cool about Context Menus is that they’re available on <strong>all</strong> iOS 13 and iPadOS devices, regardless of the 3D Touch capability.</p><p>Another key difference with Peek &amp; Pop is in the action menu: the menu is now displayed immediately, we don't have to swipe up anymore (as we had to previously).</p><h3>How It Works</h3><p>Context Menus can be triggered in two ways: via long press and, if the device supports 3D Touch, also via force touch.</p><p>Note that I’ve said <em>and</em>, not <em>or</em>:<br>If a device supports 3D Touch, then it can quickly trigger the Context Menu via force touch, however, the long press option is still available 👍🏻</p><blockquote><p>One of the main reasons why Peek and Pop didn’t make a huge splash was discoverability.3D Touch features are available exclusively on selected iPhones, Context Menus are everywhere, <em>including</em> iPads.While this doesn’t make the feature more discoverable by itself, more and more people will "stumble" upon it and the start using it throughout the system: they’ll expect your app to support it, too!</p></blockquote><h2>How It Really Works: UIContextMenuInteraction</h2><p>As for Peek and Pop, this new interaction comes with, well, a new interaction: please welcome <a href="https://developer.apple.com/documentation/uikit/uicontextmenuinteraction"><code>UIContextMenuInteraction</code></a>.</p><p><code>UIContextMenuInteraction</code> is very similar to the old (Peek and Pop’s) <a href="https://developer.apple.com/documentation/uikit/uipreviewinteraction "><code>UIPreviewInteraction</code></a>:<br>all it needs is a <code>UIContextMenuInteractionDelegate</code> and a view.</p><pre><code><span class="keyword">class</span> MyViewController: <span class="type">UIViewController</span>, 
                        <span class="type">UIContextMenuInteractionDelegate</span> {
  <span class="keyword">override func</span> viewDidLoad() {
    <span class="keyword">super</span>.<span class="call">viewDidLoad</span>()

    <span class="comment">// 👇🏻 no need to retain this like we had to with Peek and Pop’s UIPreviewInteraction! 🤩</span>
    <span class="keyword">let</span> interaction = <span class="type">UIContextMenuInteraction</span>(delegate: <span class="keyword">self</span>)
    view.<span class="call">addInteraction</span>(interaction)
  }
}
</code></pre><p>How the menu is triggered (via force touch, long press, or else), is completely abstracted away:<br>all we get, via the delegate, are interaction events. Let’s take a look at them.</p><h3>UIContextMenuInteractionDelegate</h3><p>Similar to the old <code>UIPreviewInteractionDelegate</code> for Peek and Pop, <code>UIContextMenuInteractionDelegate</code> comes with both optional and required methods:</p><pre><code><span class="keyword">@available</span>(iOS <span class="number">13.0</span>, *)
<span class="keyword">public protocol</span> UIContextMenuInteractionDelegate : <span class="type">NSObjectProtocol</span> {
  
  <span class="keyword">func</span> contextMenuInteraction(<span class="keyword">_</span> interaction: <span class="type">UIContextMenuInteraction</span>, configurationForMenuAtLocation location: <span class="type">CGPoint</span>) -&gt; <span class="type">UIContextMenuConfiguration</span>?

  optional <span class="keyword">func</span> contextMenuInteraction(<span class="keyword">_</span> interaction: <span class="type">UIContextMenuInteraction</span>, previewForHighlightingMenuWith configuration: <span class="type">UIContextMenuConfiguration</span>) -&gt; <span class="type">UITargetedPreview</span>?

  optional <span class="keyword">func</span> contextMenuInteraction(<span class="keyword">_</span> interaction: <span class="type">UIContextMenuInteraction</span>, previewForDismissingMenuWith configuration: <span class="type">UIContextMenuConfiguration</span>) -&gt; <span class="type">UITargetedPreview</span>?

  optional <span class="keyword">func</span> contextMenuInteraction(<span class="keyword">_</span> interaction: <span class="type">UIContextMenuInteraction</span>, willCommitWithAnimator animator: <span class="type">UIContextMenuInteractionCommitAnimating</span>)

  optional <span class="keyword">func</span> contextMenuInteractionWillPresent(<span class="keyword">_</span> interaction: <span class="type">UIContextMenuInteraction</span>)

  optional <span class="keyword">func</span> contextMenuInteractionDidEnd(<span class="keyword">_</span> interaction: <span class="type">UIContextMenuInteraction</span>)
}
</code></pre><ul><li><strong>configurationForMenuAtLocation</strong><ul></ul></li></ul><p>This is the only <em>required</em> method:<br> this is also the first method that is called when a new Context Menu Interactions starts.<br><br>As the name says, the method is asking us to provide a Context Menu for the given view (you can access to the view directly from the passed <code>interaction</code>).<br><br> Returning <code>nil</code> is equivalent to tell the user that, at this time, there’s no menu to display for that view. Doing so will stop the interaction immediately (no more delegate methods will be called).<br><br><br>Returning a Context Menu Configuration (please refer to <a href="https://kylebashour.com/posts/ios-13-context-menus">this great article</a> by fellow developer <a href="https://twitter.com/kylebshr ">Kyle Bashour</a> on how), will let the system know that we have a menu to display, and the interaction can continue.</p><ul><li><strong>previewForHighlightingMenuWith</strong><ul></ul></li></ul><p>This is the first <em>optional</em> method:<br><br>once we’ve returned a menu, the system gives us a chance to choose which view to focus on.<br><br> This view will be put on focus (along with the context menu), while everything else on the screen will be blurred out (like in the screenshots at the beginning of this article).<br><br><br>By default (without implementing this method, or when returning <code>nil</code>), the focused view will be the one where the <code>UIContextMenuInteraction</code> has been triggered to.</p><p>I n case you’d like to have a completely new view displayed instead, <a href="https://twitter.com/kylebshr ">Kyle</a> shows how to do so in the “<em>Providing a custom preview</em>“ chapter <a href="https://kylebashour.com/posts/ios-13-context-menus">of his article</a>.</p><ul><li><strong>contextMenuInteractionWillPresent </strong> This is the second <em>optional</em> method: it’s here to let us know that the menu is about to be presented.</li></ul><p>We can use this method call as we please: for example, we might want to track a context menu “state” or similar. It’s entirely up to you. There’s a high chance that you don't need this method, but it’s nice to have it in case you do.</p><p>Once we arrive here, the menu is displayed.</p><ul><li><strong>previewForDismissingMenuWith</strong><ul></ul></li></ul><p>This is the third <em>optional</em> method:<br> it is called when the Context Menu is being dismissed.</p><p>Exactly like for <code>previewForHighlightingMenuWith</code>, this method is asking us to provide which view the menu is going to dismiss into.</p><ul><li><strong>willCommitWithAnimator</strong><ul></ul></li></ul><p>This is the fourth <em>optional</em> method:<br><br>UIKit calls this method when the user taps in the focused view.</p><p>Consider this as an equivalent to the Pop interaction in Peek and Pop.</p><p>In this method we’re passed an <code>animator</code> of type <code>UIContextMenuInteractionCommitAnimating</code>, which we can use to inject any custom animations that we want to run alongside the context menu dismissal animations.</p><p>Note that this method is called before <code>previewForDismissingMenuWith</code>.</p><ul><li><strong>contextMenuInteractionDidEnd </strong> This is last <em>optional</em> method:<ul></ul></li></ul><p>it lets us know when a Context Menu interaction has ended, dual to <code>contextMenuInteractionWillPresent</code>.</p><p>Again, UIKit doesn't expect us to do anything at this point, it’s entirely up to us to give meaning to this call.</p><h3>UIContextMenuInteraction Playground</h3><p>While it’s very nice to have all of the above documented, we all know the saying “<em>a playground is worth a thousand words</em>”:</p><img src="https://www.fivestars.blog/assets/posts/uicontextmenuinteraction/simple-playground.png" alt="playground screenshots"/><blockquote><p>The Playground.</p></blockquote><p>In this playground you'll find a view controller with one subview offering the new Context Menu interaction:<br>the view controller adopts all the methods described above and logs each one of them when called.</p><p>You can run it in order to get familiar with all the possible flows, for example:</p><ul><li>trigger the context menu and tap on a menu action</li><li>trigger the context menu and tap on the focused view</li><li>trigger the context menu and tap on the background</li></ul><p>Your app is expected to behave differently in each of these scenarios.</p><pre><code><span class="keyword">import</span> UIKit
<span class="keyword">import</span> PlaygroundSupport

<span class="keyword">final class</span> MyViewController: <span class="type">UIViewController</span>,
                               <span class="type">UIContextMenuInteractionDelegate</span> {
  <span class="keyword">override func</span> loadView() {
    <span class="keyword">let</span> view = <span class="type">UIView</span>()
    view.<span class="property">backgroundColor</span> = .<span class="dotAccess">white</span>

    <span class="keyword">let</span> interactionView = <span class="type">UIView</span>()
    interactionView.<span class="property">backgroundColor</span> = .<span class="dotAccess">systemYellow</span>
    interactionView.<span class="property">translatesAutoresizingMaskIntoConstraints</span> = <span class="keyword">false

    let</span> interaction = <span class="type">UIContextMenuInteraction</span>(delegate: <span class="keyword">self</span>)
    interactionView.<span class="call">addInteraction</span>(interaction)
    <span class="call">print</span>(<span class="string">"interaction added"</span>)

    view.<span class="call">addSubview</span>(interactionView)

    <span class="type">NSLayoutConstraint</span>.<span class="call">activate</span>([
      interactionView.<span class="property">centerXAnchor</span>.<span class="call">constraint</span>(equalTo: view.<span class="property">centerXAnchor</span>),
      interactionView.<span class="property">centerYAnchor</span>.<span class="call">constraint</span>(equalTo: view.<span class="property">centerYAnchor</span>),
      interactionView.<span class="property">heightAnchor</span>.<span class="call">constraint</span>(equalTo: interactionView.<span class="property">widthAnchor</span>),
      interactionView.<span class="property">heightAnchor</span>.<span class="call">constraint</span>(equalToConstant: <span class="number">150</span>)
      ])

    <span class="keyword">self</span>.<span class="property">view</span> = view
  }

  <span class="comment">// MARK: UIContextMenuInteractionDelegate</span>

  <span class="keyword">func</span> contextMenuInteraction(
    <span class="keyword">_</span> interaction: <span class="type">UIContextMenuInteraction</span>,
    configurationForMenuAtLocation location: <span class="type">CGPoint</span>) -&gt; <span class="type">UIContextMenuConfiguration</span>? {
    <span class="call">print</span>(<span class="string">"configurationForMenuAtLocation"</span>)

    <span class="keyword">return</span> <span class="type">UIContextMenuConfiguration</span>(identifier: <span class="keyword">nil</span>,
                                      previewProvider: <span class="keyword">nil</span>,
                                      actionProvider: { [<span class="keyword">weak self</span>] <span class="keyword">_ in
      return self</span>?.<span class="call">makeContextMenu</span>()
    })
  }

  <span class="keyword">private func</span> makeContextMenu() -&gt; <span class="type">UIMenu</span> {
    <span class="keyword">let</span> blog = <span class="type">UIAction</span>(__title: <span class="string">"fivestars.blog"</span>,
                         image: <span class="type">UIImage</span>(systemName: <span class="string">"star.fill"</span>)) { <span class="keyword">_ in</span>
                          <span class="call">print</span>(<span class="string">"https://fivestars.blog"</span>)
    }

    <span class="keyword">let</span> twitter = <span class="type">UIAction</span>(__title: <span class="string">"Federico Zanetello"</span>,
                           image: <span class="type">UIImage</span>(systemName: <span class="string">"at"</span>)) { <span class="keyword">_ in</span>
                            <span class="call">print</span>(<span class="string">"https://twitter.com/zntfdr"</span>)
    }

    <span class="keyword">return</span> <span class="type">UIMenu</span>(__title: <span class="string">""</span>,
                  image: <span class="keyword">nil</span>,
                  identifier: <span class="keyword">nil</span>,
                  children: [blog, twitter])
  }

  <span class="keyword">func</span> contextMenuInteraction(
    <span class="keyword">_</span> interaction: <span class="type">UIContextMenuInteraction</span>,
    previewForHighlightingMenuWith configuration: <span class="type">UIContextMenuConfiguration</span>)
    -&gt; <span class="type">UITargetedPreview</span>? {
    <span class="call">print</span>(<span class="string">"previewForHighlightingMenuWith"</span>)
    <span class="keyword">return nil</span>
  }

  <span class="keyword">func</span> contextMenuInteraction(
    <span class="keyword">_</span> interaction: <span class="type">UIContextMenuInteraction</span>,
    previewForDismissingMenuWith configuration: <span class="type">UIContextMenuConfiguration</span>)
    -&gt; <span class="type">UITargetedPreview</span>? {
    <span class="call">print</span>(<span class="string">"previewForDismissingMenuWith"</span>)
    <span class="keyword">return nil</span>
  }

  <span class="keyword">func</span> contextMenuInteraction(
    <span class="keyword">_</span> interaction: <span class="type">UIContextMenuInteraction</span>,
    willCommitWithAnimator animator: <span class="type">UIContextMenuInteractionCommitAnimating</span>) {
    <span class="call">print</span>(<span class="string">"willCommitWithAnimator"</span>)
  }

  <span class="keyword">func</span> contextMenuInteractionWillPresent(
    <span class="keyword">_</span> interaction: <span class="type">UIContextMenuInteraction</span>) {
    <span class="call">print</span>(<span class="string">"willPresent"</span>)
  }

  <span class="keyword">func</span> contextMenuInteractionDidEnd(
    <span class="keyword">_</span> interaction: <span class="type">UIContextMenuInteraction</span>) {
    <span class="call">print</span>(<span class="string">"didEnd"</span>)
  }
}

<span class="type">PlaygroundPage</span>.<span class="property">current</span>.<span class="property">liveView</span> = <span class="type">MyViewController</span>()
<span class="type">PlaygroundPage</span>.<span class="property">current</span>.<span class="property">needsIndefiniteExecution</span> = <span class="keyword">true</span>
</code></pre><blockquote><p>Find the gist <a href="https://gist.github.com/zntfdr/9bdbe0f7918cb1e63e816d3ff0b50191">here</a>.</p></blockquote><h2>UIContextMenuInteraction Custom Behavior</h2><p>Now that we have a firm grasp on how Context Menus work, let me ask you: is it possible to use this new interaction for something entirely custom?</p><p>The answer is yes, and it’s very simple to do so.</p><p>As you might recall, the only required delegate method is <code>configurationForMenuAtLocation</code>, which is asking us to provide a menu for a given view.<br><br>What is important to pay attention to is the parameters passed in the method:<br>an interaction (<code>UIContextMenuInteraction</code>) and a location (<code>CGPoint</code>).</p><p>Thanks to the former we obtain the view where the interaction has been triggered from, meanwhile, with the latter, we get the exact interaction touch point on that view.</p><p>Once we have these two pieces of information, we can return <code>nil</code> to the method, which stops the context menu interaction, and then we can freely carry on with our own custom interaction instead.</p><h3>Custom Interaction Playground</h3><img src="https://www.fivestars.blog/assets/posts/uicontextmenuinteraction/stars-playground.png" alt="playground screenshots"/><blockquote><p>Stars, stars everywhere!</p></blockquote><p>As an example of custom behavior, in this playground I use the new interaction to draw stars in the main view: long press or force touch anywhere and a star is drawn at that exact position.</p><p>The possibilities of what you can do with it are truly limitless.</p><pre><code><span class="keyword">import</span> UIKit
<span class="keyword">import</span> PlaygroundSupport

<span class="keyword">final class</span> MyViewController: <span class="type">UIViewController</span>,
                               <span class="type">UIContextMenuInteractionDelegate</span> {
  <span class="keyword">override func</span> loadView() {
    <span class="keyword">let</span> view = <span class="type">UIView</span>()
    view.<span class="property">backgroundColor</span> = .<span class="dotAccess">white</span>

    <span class="keyword">let</span> contextMenuInteraction = <span class="type">UIContextMenuInteraction</span>(delegate: <span class="keyword">self</span>)
    view.<span class="call">addInteraction</span>(contextMenuInteraction)

    <span class="keyword">self</span>.<span class="property">view</span> = view
  }

  <span class="comment">// MARK: UIContextMenuInteractionDelegate</span>

  <span class="keyword">func</span> contextMenuInteraction(
    <span class="keyword">_</span> interaction: <span class="type">UIContextMenuInteraction</span>,
    configurationForMenuAtLocation location: <span class="type">CGPoint</span>) -&gt; <span class="type">UIContextMenuConfiguration</span>? {
    <span class="call">addLabel</span>(text: <span class="string">"⭐️"</span>, at: location)
    <span class="keyword">return nil</span>
  }

  <span class="keyword">private func</span> addLabel(text: <span class="type">String</span>,
                        at location: <span class="type">CGPoint</span>) {
    <span class="keyword">let</span> label = <span class="type">UILabel</span>()
    label.<span class="property">text</span> = text
    label.<span class="property">textAlignment</span> = .<span class="dotAccess">center</span>
    label.<span class="property">font</span> = .<span class="call">preferredFont</span>(forTextStyle: .<span class="dotAccess">largeTitle</span>)
    label.<span class="property">frame</span>.<span class="property">size</span> = <span class="type">CGSize</span>(width: <span class="number">40</span>, height: <span class="number">25</span>)

    view.<span class="call">addSubview</span>(label)
    label.<span class="property">center</span> = location
  }
}

<span class="type">PlaygroundPage</span>.<span class="property">current</span>.<span class="property">liveView</span> = <span class="type">MyViewController</span>()
<span class="type">PlaygroundPage</span>.<span class="property">current</span>.<span class="property">needsIndefiniteExecution</span> = <span class="keyword">true</span>
</code></pre><blockquote><p>Find the gist <a href="https://gist.github.com/zntfdr/c5da8dd09513d383606f9c7d0c78a44c">here</a>.</p></blockquote><blockquote><p>A real application example can be found in <a href="https://apps.apple.com/us/developer/federico-zanetello/id1053443073">my metro apps</a>:by customizing this interaction, users can quickly start a journey from wherever they are, known via GPS location, to the metro station they tap into with the new gesture.</p></blockquote><p>This gesture enables users to skip several steps at once:<br>by adding something alike in your app, your users, too, can get exactly what they need from your app within <strong>seconds</strong> from the app launch! 🚀</p><h3>Conclusions</h3><p><code>UIContextMenuInteraction</code> is a new, welcome take on Peek and Pop that will surely see more widespread adoption and use across many apps.<br><br>Even with the default behavior, this is a clear step in the right direction: I look forward to try it in your apps!</p><p>Lastly, if you're going to implement a custom interaction, <a href="https://twitter.com/zntfdr ">please let me know on Twitter</a>! I would love to see how you're using it 🤩</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p><p>PS<br>If this article feels strangely familiar, it's because it is:<br>this article is the 2019 edition of the great <a href="https://krakendev.io/uipreviewinteraction">UIPreviewInteraction article</a> by <a href="https://twitter.com/krakendev ">Hector Matos</a>, back from 2016(!). Time flies.</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/what-s-new-in-swift-5-1</guid><title>What's New In Swift 5.1 🏎</title><description></description><link>https://www.fivestars.blog/articles/what-s-new-in-swift-5-1</link><pubDate>Wed, 12 Jun 2019 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Among tons of new shiny things announced last week at WWDC19 (🤯), the first beta of Xcode 11 with Swift 5.1 was delivered.</p><p>Even though this is a dot release, Swift 5.1 brings exciting new features and enhancements, affecting every single Swift codebase out there.</p><p>For my company monthly talk I've decided to present what's new in Swift 5.1 in a simple and concise format, with actual example code.</p><p>You can see the whole presentation slides here:</p><p><a href="https://speakerdeck.com/zntfdr/whats-new-in-swift-5-dot-1"><img src="https://www.fivestars.blog/assets/posts/what-s-new-in-swift-5-1/first-slide.jpg" alt="slides"/></a></p><blockquote><p>Check out the full slides at <a href="https://speakerdeck.com/zntfdr/whats-new-in-swift-5-dot-1">SpeakerDeck</a></p></blockquote><blockquote><p>Be aware that I did <em>not</em> talk about the new features that Apple has sneakily shipped with Xcode 11, as those did not go throughout the normal evolution process and can still change.If you're interested in those, as you should, since they're a fundamental part to understand how SwiftUI works, you can check <a href="https://www.swiftbysundell.com/posts/the-swift-51-features-that-power-swiftuis-api">this</a> awesome article by <a href="https://www.swiftbysundell.com/posts/the-swift-51-features-that-power-swiftuis-api">John Sundell</a>.</p></blockquote><p>You can also download the slides, along with other materials, in this new <a href="https://github.com/zntfdr/talks"><code>talks</code> repository</a> that I've just created (hope to add more and more talks soon!).</p><p>Obviously the slides alone is not like being there during the presentation, still, if you have any feedback, please <a href="https://twitter.com/zntfdr">let me know</a>!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/ios-dark-mode-how-to</guid><title>How To Adopt Dark Mode In Your iOS App 🌙</title><description></description><link>https://www.fivestars.blog/articles/ios-dark-mode-how-to</link><pubDate>Tue, 11 Jun 2019 00:00:00 +0000</pubDate><content:encoded><![CDATA[<img src="https://www.fivestars.blog/assets/posts/ios-dark-mode-how-to/metroapps.png" alt="metroapps"/><blockquote><p>My <a href="https://itunes.apple.com/app/bangkok-metro/id1095112370">metro app</a>, both in dark and light mode.</p></blockquote><blockquote><p>This article is also available in <a href="https://qiita.com/tamappe/items/3bf89a55ab85d0488b8a">Japanese 🇯🇵</a>.</p></blockquote><p>Rejoice! iOS 13 comes with Dark Mode! 🤩</p><p>I've just finished adopting the new appearance in my own apps, and I've learned a few things along the way: let’s see how you can do it, too!</p><h2>Two Steps Back First</h2><p>Before diving in, we need to be aware of a few things:<br>In order to give our users the best experience, it is really important that you don’t skip this part. Ready? Let’s go!</p><h3>Requirements</h3><p>For your app to support Dark Mode, it must: 1. <strong>Have been built and released with Xcode 11 </strong><br> If you use Xcode 10 (or earlier), the app will always display its normal appearance, even on devices with Dark Mode enabled. 2. <strong>Run on an iOS 13 device</strong><br> Only devices running iOS 13 (or later) support Dark Mode, previous versions of iOS will resort to the default appearance.<br> You cannot enforce Dark Mode on an older iOS over the default appearance (actually, <a href="https://rambo.codes/ios/2018/10/03/unleashing-the-power-of-asset-catalogs-and-bundles-on-ios.html">you can</a>, however it involves creating a lot of custom logic to do so, it’s not worth it).</p><blockquote><p>⚠️ Here's another way to look at it: if your app has not been updated with Dark Mode support by the time iOS 13 launches, the app will always display its default appearance, regardless of the iOS system settings. If your users have enabled Dark Mode and your app doesn’t support it yet, your app will flash your users with that bright default interface! 🙈</p></blockquote><blockquote><p>Using Apple words: “<em>You really don't want to be that one light appearance that's stuck in dark appearance</em>”.</p></blockquote><h3>Your App Supports Dark Mode Already</h3><p>This is both the cool and scary part:<br>Once the (two!) requirements above have been fulfilled, whenever Dark Mode is enabled, your UI will automatically switch appearance.</p><p>This is awesome, because it means that we get a lot of work done for free:<br>however, if we don’t make sure that our app looks good in dark mode, some of our UI will certainly look weird, probably with bright shiny colors, even in this new appearance. This would be a really bad experience for your users, read on to avoid this.</p><h3>You Can Force One Appearance Over The Other</h3><p>If you really wish to avoid adopting dark mode in your app, drop the “light mode” indefinitely in favor of dark mode, or simply postpone your dark mode adoption to another time, add a new key <code>UIUserInterfaceStyle</code> in your app <code>info.plist</code> and set its value to <code>Light</code> or <code>Dark</code>.</p><blockquote><p>Another way to do so would be to keep shipping your app with Xcode 10 😆</p></blockquote><h3>Don’t Force One Appearance Over The Other</h3><p>Using Apple words: “<em>Only a small subset of apps really should be dark all the time, and those are media-centric or content-creation apps</em>“. Unless you have a very good reason to offer just one interface appearance, make sure to always respect the system preference. It doesn’t matter how much you prefer one over the other.</p><blockquote><p>Apple allows you to have an in-app setting where the user can choose which mode to use in your app, regardless of the system preference: again, unless your app has a very good reason to do so, the best experience is to respect the system mode. Don’t offer unnecessary, redundant settings.</p></blockquote><h3>Dark Mode Is Not The Only New Mode</h3><p>This might have not hit the press as much as Dark Mode, however iOS has always had two modes: default and high contrast (you can enable this in any iOS device by going to <code>Settings &gt; Accessibility &gt; Increase Contrast</code>, tap also on <code>General</code> if you're still running iOS 12).</p><img src="https://www.fivestars.blog/assets/posts/ios-dark-mode-how-to/high-contrast.png" alt="high-contrast"/><blockquote><p>Left: iMessage in high contrast. Right: iMessage in the default appearance.</p></blockquote><p>While you might be able to read with ease both left and right images, for someone else having some help (read: high contrast) would be great, for others it is the only way possible to read anything at all.</p><p>Now that we are aware of this, it’s clear that we have to take care of four appearances:</p><ol><li>Default</li><li>Default High Contrast</li><li>Dark Mode</li><li>Dark Mode High Contrast</li></ol><p>This is another reason why it is really important to stick with default UIKit (or SwiftUI) elements as much as possible: - if we do so, we get support for all of these appearances for free. - If we create our own colors and UI components, we must take care of each mode, element, and element state ourselves. This work grows exponentially.</p><h2>Let’s Get Started!</h2><p>If you’ve made it so far, congratulations! 🎉 From now on we will talk about what you can do, right now, to support the new interface appearances.</p><h3>Step 1: Colors</h3><p>At the end of the day, all our app do is throwing colors at the screen: getting colors right means having your app 99% ready for Dark Mode.</p><h4>(Dynamic) System Colors</h4><p>Until iOS 12, <code>UIColor</code> has offered us a few simple colors like <code>.red</code>, <code>.yellow</code> etcetera: you don’t want to use these colors any longer. These colors are <em>static</em>, which means that their tint never changes.</p><p>With iOS 13 and Xcode 11, Apple is introducing System Colors:</p><img src="https://www.fivestars.blog/assets/posts/ios-dark-mode-how-to/system-colors-rainbow.png" alt="rainbow"/><blockquote><p>Top: System Colors in the default interface. Bottom: System Colors in dark mode.Xcode Playground <a href="https://gist.github.com/zntfdr/bb8173970414d4b4e90d3a6b18f01e4b">here</a>.</p></blockquote><p>From left to right: <code>.systemBlue</code>, <code>.systemGray</code>, <code>.systemGreen</code>, <code>.systemIndigo</code>, <code>.systemOrange</code>, <code>.systemPink</code>, <code>.systemPurple</code>, <code>.systemRed</code>, <code>.systemTeal</code>, <code>.systemYellow</code>.</p><p>As you can see from the picture above, contrary to the old static colors, system colors are dynamic: their tint will adapt to the current system interface.</p><p>But those are not the only new colors! We also have a full range of grayscale colors, where the differente between dark and light appearance is even more obvious:</p><img src="https://www.fivestars.blog/assets/posts/ios-dark-mode-how-to/system-colors-grayscale.png" alt="grayscale"/><blockquote><p>Top: System Colors Grayscale in the default interface. Bottom: System Colors Grayscale in dark mode.Xcode Playground <a href="https://gist.github.com/zntfdr/c21da4fb2bc60c27a6c7cd0227543f8a">here</a>.</p></blockquote><p>From left to right: <code>.systemGray</code>, <code>.systemGray2</code>, <code>.systemGray3</code>, <code>.systemGray4</code>, <code>.systemGray5</code>, <code>.systemGray6</code>.</p><blockquote><p>All dynamic system colors also come with a special tint for high contrast. #a11y</p></blockquote><p>By using these dynamic colors (e.g. <a href="https://developer.apple.com/documentation/uikit/uicolor/3173141-systemblue"><code>UIColor.systemBlue</code></a> instead of <code>UIColor.blue</code>), your interface will automatically pick the right tint for the current system preference, no further work required! 🎉</p><h4>(Dynamic) Semantic Colors</h4><p>Xcode 10 and earlier offered two colors named <code>.lightText</code> and <code>.darkText</code>.<br>The use of these is now discuraged, as they're static, and will not adapt to any interface. Instead, from Xcode 11 we a full new suite of semantic colors such as <a href="https://developer.apple.com/documentation/uikit/uicolor/3173131-label"><code>UIColor.label</code></a>, <a href="https://developer.apple.com/documentation/uikit/uicolor/3173134-placeholdertext"><code>UIColor.placeholderText</code></a>, <a href="https://developer.apple.com/documentation/uikit/uicolor/3173140-systembackground"><code>UIColor.systemBackground</code></a> etc.</p><p>Instead of describing a shade, these colors names are based on their intended usage: most of the time you want to use these, as, like system colors, they’re dynamic.</p><p>Most importantly, semantic colors ensure that your app has a similar appearance to the rest of the system. By using these, your app will feel native, which is always the best experience for the user.</p><p>The more you use these dynamic colors, the faster you’ll properly adapt to Dark Mode.</p><blockquote><p>Still supporting iOS 12 and earlier? Worry not: fellow developer <a href="https://twitter.com/noahsark769">Noah Gilmore</a> shows you how to use the new colors while also maintaining backward compability in his brand new article "<a href="https://noahgilmore.com/blog/dark-mode-uicolor-compatibility/">Backwards compatibility for iOS 13 system colors</a>" 💯.</p></blockquote><h4>(Dynamic) Custom Colors: The Assets Catalog</h4><p>This should always be your very last option to look at.</p><p>This is the only option that requires a lot of work, trial, and error, for both you and the design team:<br>let Apple do the work for you, they’ve invested an unbelievable amount of time from their super talented teams on this, trust and use their work, your app will be fine.</p><p>With the disclaimer out of the way, how do you define and use your own dynamic colors?<br>Since iOS 11 and Xcode 9 we can add colors into assets catalogs.</p><p>Now you can also define a dark variant for each color in there as well:<br>to do so, select a color in your assets catalog, open the Attribute Inspector (shortcut: <code>⌘⌥4</code>), and set its appearance from <code>None</code> to <code>Any, Dark</code>:</p><img src="https://www.fivestars.blog/assets/posts/ios-dark-mode-how-to/assets-dark-mode.png" alt="assets-dark-mode"/><blockquote><p>Our own dynamic color!</p></blockquote><p>At this point a new color box for the Dark appearance will appear: congratulations! You have just created your first dynamic color! Set each variant to its appropriate tint and you're good to go.</p><p>Don't forget to enable high contrast as well:</p><img src="https://www.fivestars.blog/assets/posts/ios-dark-mode-how-to/assets-high-contrast.png" alt="assets-high-contrast"/><blockquote><p>Four variants for the same color 😅</p></blockquote><p>Again, I strongly suggest to use System and Semantic Colors as much as possible: in case of a button for example, using custom colors means defining one color <a href="https://developer.apple.com/documentation/uikit/uibutton#1656634">for each button state</a>: <code>.normal</code>, <code>.highlighted</code>, <code>.focused</code>, <code>.selected</code>, and <code>.disabled</code>.<br>This means requiring a different color for each state, for each appearance. If you do the math, one button now requires (5 states * 4 appearances =) 20, t-w-e-n-t-y, different tints! 😱</p><blockquote><p>A heads-up in case you still want to go this route:for pressed button colors, using a darkened version of the default color doesn’t work any longer, as the effect is correct for light interfaces, however dark interfaces require a lighter color instead. 👍🏻</p></blockquote><h4>(Dynamic) Custom Colors In Code 🙈</h4><p>If you’re still targeting iOS 10 (which doesn’t support colors declarations in the asset catalog) or don’t want or can’t use the options above, then the very last option available is to define colors in code:</p><pre><code><span class="keyword">let</span> dynamicColor = <span class="type">UIColor</span> { (traitCollection: <span class="type">UITraitCollection</span>) -&gt; <span class="type">UIColor</span> <span class="keyword">in
    switch</span> traitCollection.<span class="property">userInterfaceStyle</span> {
    <span class="keyword">case</span>
      .<span class="dotAccess">unspecified</span>,
      .<span class="dotAccess">light</span>: <span class="keyword">return</span> .<span class="dotAccess">white</span>
    <span class="keyword">case</span> .<span class="dotAccess">dark</span>: <span class="keyword">return</span> .<span class="dotAccess">black</span>
    }
}
</code></pre><p>Don’t do this, drop iOS 10 and use assets catalogs.<br>### Step 2: Images</p><p>Almost every app displays images and/or symbols in the UI. Unless those images display user content (like a profile picture), then it’s very good design to have one image variant for each mode. Let’s see how.</p><h4>SF Symbols</h4><p>Apple introduced <a href="https://developer.apple.com/design/human-interface-guidelines/sf-symbols/overview/">SF Symbols</a> at WWDC19. SF Symbols is a huge collection of glyphs (over 1500!) that are available for developers to use in their own apps.<br>Apple itself uses SF Symbols in every stock app like Reminders, News, Maps and more:</p><img src="https://www.fivestars.blog/assets/posts/ios-dark-mode-how-to/stock-apps.png" alt="stock-apps-sfsymbols"/><blockquote><p>Some examples of iOS stock apps using SF Symbols</p></blockquote><img src="https://www.fivestars.blog/assets/posts/ios-dark-mode-how-to/stock-apps-sf-symbols.jpg" alt="stock-apps-sfsymbols-2"/><blockquote><p>Some of the SF Symbols used in the screenshots above: can you spot them?</p></blockquote><p>Not only Apple uses them, but it also encurage us to do so, too. In Apple Words:<br>"<em>The system uses SF Symbols, which automatically look great in Dark Mode, and full-color images that are optimized for both light and dark appearances. Use SF Symbols wherever possible.</em>"</p><p>To recap, SF Symbols are a bunch of glyphs ready to be used in our apps:<br>wherever there's a glyph in your UI, see if you can replace it with one of these 1500+ glyphs that we get for free. Beside being vector images, which look perfect at any size, they also come built-in with the system: no need to package them up in our app! Yay for smaller app sizes!</p><p>If you're using storyboards, you can tell Xcode which symbol to use by typing the correct glyph name in the image name field (find the name in Apple's SF Symbols app available in Apple <a href="https://developer.apple.com/design/human-interface-guidelines/sf-symbols/overview/">SF Symbols guideline</a>).</p><img src="https://www.fivestars.blog/assets/posts/ios-dark-mode-how-to/storyboard-example.png" alt="storyboard-example"/><blockquote><p>Note how the Attribute Inspector confirms that we're using a system symbol with the "System" callout</p></blockquote><p>Alternatively, you can fetch any of them by using the new api <code>UIImage(systemName:)</code>:</p><pre><code><span class="type">UIImage</span>(systemName: <span class="string">"star.fill"</span>)
</code></pre><p>Cannot find something that you fancy? You can always export any of the available glyphs (as .svg) from the official <a href="https://developer.apple.com/design/human-interface-guidelines/sf-symbols/overview/">SF Symbols</a> app and change them to perfectly meet your needs 👍🏻</p><p>For more information about SF Symbols, watch the WWDC19 session <a href="https://developer.apple.com/videos/play/wwdc2019/206/ ">206: Introducing SF Symbols</a>.</p><p>If you would like to access to the whole list of symbols from anywhere, fellow developer <a href="https://twitter.com/noahsark769">Noah Gilmore</a> (again!) has your back with his new website <a href="https://sfsymbols.com">sfsymbols.com</a>, very handy if you're on the go 😎.</p><blockquote><p>Since SF Symbols can be exported from the SF Symbols app, you can always package them in your app bundle: this way you can use the new symbols while still maintaining retrocompability.</p></blockquote><h4>Custom Template Images (Glyphs)</h4><p>Like SF Symbols, template images are monocrome images that are defined in our Xcode assets catalog by selecting "render as" <code>Template Image</code> in the Attribute Inspector.</p><p>By using them, you get several advantages. To name one, you gain dark mode for free.</p><p>When using template images, remember to set the UIImageView tint to one of the dynamic colors described above:</p><pre><code><span class="keyword">let</span> glyphImage = <span class="type">UIImage</span>(named: <span class="string">"myGlyph"</span>)
<span class="keyword">let</span> glyphImageView = <span class="type">UIImageView</span>(image: glyphImage)
glyphImageView.<span class="property">tintColor</span> = .<span class="dotAccess">systemYellow</span>
</code></pre><h4>Other Images</h4><p>For all other kind of images that are not template images or symbols such as photos and more, we can follow the same steps as for custom colors: set their appearance to <code>Any, Dark</code> in the assets catalog and drop a new variant for each appearance.</p><img src="https://www.fivestars.blog/assets/posts/ios-dark-mode-how-to/assets-example.png" alt="storyboard-example"/><blockquote><p>Example stolen from Apple's <a href="https://developer.apple.com/documentation/uikit/appearance_customization/adopting_ios_dark_mode">sample app</a>.</p></blockquote><p>For more tips around images, use <a href="https://developer.apple.com/documentation/appkit/images_and_pdf/providing_images_for_different_appearances">this</a> Apple guide.</p><blockquote><p>Note that adding a dark variant to any assets in the catalog doesn't break retrocompatibility: older iOS versions will always choose the asset tagged with <code>Any Appearance</code>.</p></blockquote><h3>Step 3: UIVisualEffectViews - Semantic Materials</h3><blockquote><p>In case you need a quick catch-up with what you can do with these fantastic views, here’s <a href="http://nikolakirev.com/blog/uivisualeffect-swift-tutorial">a very clear article</a> by <a href="https://twitter.com/NikolaKirev">Nikola Kirev</a>.</p></blockquote><p>Like for <code>UIColor</code>s, until iOS 12 we had static <code>UIBlurEffect.Style</code>s (namely <code>.dark</code>, <code>.light</code> and <code>.extraLight</code>).<br>Xcode 11 has revamped these styles with brand new Semantic Materials:<br>we go from a delicate, semitransparent <code>.systemUltraThinMaterial</code>, to <code>.systemThinMaterial</code>, to <code>.systemMaterial</code>, until we arrive to a heavy <code>.systemThickMaterial</code>.</p><p>You can see them all here (grab the playground under the image):</p><img src="https://www.fivestars.blog/assets/posts/ios-dark-mode-how-to/system-materials.jpg" alt="materials"/><blockquote><p>Top: System Materials with vibrant label in the default interface. Bottom: System Materials with vibrant label in dark mode.Xcode Playground <a href="https://gist.github.com/zntfdr/70cf54afdb204278ca4947f23cae2ce8">here</a>.</p></blockquote><p>Note how the text vibrancy dynamically adapts based on both the interface appearance and the <code>UIVisualEffectView</code> effect it sits on: we don't need to worry about readability, because iOS takes care of it for us 💯.</p><h3>Step 4: Drawing Attributed Text</h3><p>When drawing attributed text, if not specified, the <code>.foregroundColor</code> property is set to <code>.black</code>:<br>set it to a proper color instead (e.g. <code>UIColor.label</code>).</p><pre><code><span class="keyword">let</span> textToDraw = <span class="string">"FiveStars.blog"</span>
<span class="keyword">let</span> attributes: [<span class="type">NSAttributedString</span>.<span class="type">Key</span>: <span class="type">AnyObject</span>] = 
  [.<span class="dotAccess">font</span>: <span class="type">UIFont</span>.<span class="call">preferredFont</span>(forTextStyle: .<span class="dotAccess">title3</span>),
   .<span class="dotAccess">foregroundColor</span>: .<span class="dotAccess">label</span>]
textToDraw.<span class="call">draw</span>(at: <span class="type">CGPoint</span>.<span class="property">zero</span>, withAttributes: attributes)
</code></pre><h3>Step 5: Dark Mode in CALayers</h3><p><code>CALayer</code>s don’t understand dynamic colors.</p><p>When working with dynamic colors at this level, you can get the current <code>UITraitCollection</code> (which contains the active mode in <code>.userInterfaceStyle</code>) from the layer's view by calling <code>view.traitCollection</code>.<br>Once obtained, you can resolve any dynamic color for example with:</p><pre><code><span class="keyword">let</span> resolvedColor = <span class="type">UIColor</span>.<span class="property">label</span>.<span class="call">resolvedColor</span>(with: traitCollection) 
layer.<span class="property">borderColor</span> = resolvedColor.<span class="property">cgColor</span> 
</code></pre><p>In case where we need to resolve multiple colors, there's a <code>UITraitCollection</code> has a handy <a href="https://developer.apple.com/documentation/uikit/uitraitcollection/3238082-performascurrent"><code>performAsCurrent()</code></a> method that lets us do just that:</p><pre><code><span class="keyword">let</span> manyDynamicColors = ...

traitCollection.<span class="call">performAsCurrent</span> {
 <span class="keyword">for</span> resolvedColor <span class="keyword">in</span> manyDynamicColors {
   <span class="keyword">let</span> resolvedCGColor = resolvedColor.<span class="property">cgColor</span>
   ...
 }
}
</code></pre><h3>Step 6: Overriding The System Appearance</h3><p>Sometimes it make sense for a view to stick with one appearance, regardless of the system preference.<br>With iOS 13, <code>UIView</code>, <code>UIViewController</code>, and <code>UIWindow</code> have gained a new <code>overrideUserInterfaceStyle</code> property that lets us override the system appearance:</p><pre><code><span class="keyword">let</span> view = <span class="type">UIView</span>()
<span class="comment">// this view will inherit the appearance of its superview</span>

<span class="keyword">let</span> darkView = <span class="type">UIView</span>()
darkView.<span class="property">overrideUserInterfaceStyle</span> = .<span class="dotAccess">dark</span>
<span class="comment">// this view (and its subviews) will always be in dark mode</span>
</code></pre><p><code>overrideUserInterfaceStyle</code> is an enum instance of type <code>UIUserInterfaceStyle</code>. This enum cases are either <code>.dark</code>, <code>.light</code>, or <code>.unspecified</code>, which is used to say that the view/viewController/window will inherit the interface from its superview/parentController/system.</p><p>Setting this property effects that specific view/viewController/window and anything that is "below" it.</p><p>By doing so, its view and <em>all its sub views</em> will adopt your preference instead of the system one.</p><p>In case you’d like a view to go back to listen to the system preference, set back the <code>overrideUserInterfaceStyle</code> property to <code>.unspecified</code>.</p><pre><code><span class="keyword">let</span> viewWithCustomAppearance = <span class="type">UIView</span>()
viewWithCustomAppearance.<span class="property">overrideUserInterfaceStyle</span> = .<span class="dotAccess">dark</span>
<span class="comment">// this view (and its subviews) are now in dark mode</span>

viewWithCustomAppearance.<span class="property">overrideUserInterfaceStyle</span> = .<span class="dotAccess">unspecified</span>
<span class="comment">// this view (and its subviews) follow the appearance of this view superview</span>
</code></pre><h2>A Deeper Look</h2><p>If your app completely relies on storyboards for the UI, then congratulations! You’re now set to fully support Dark Mode. Not all of us are this lucky, if you’re not among these people (🙋🏻‍♂️), read on.</p><h3>Behind The Scenes: Draw Time</h3><p>iOS picks the right tint/image of our dynamic colors/images at draw time: but when is “draw time” exactly?</p><p>As you know, our views can become invalid at some point in their lifetime:<br>maybe the user has rotated the screen, maybe a UIView needs to add a new element in the interface, etc.</p><p>From now on, our views will become invalid also every time the interface appearance changes.</p><p>You're always guaranteed to have iOS pick the right tint/material/image when you're inside any of the following methods:</p><table><thead><tr><th><code>UIView</code></th><th><code>UIViewController</code></th><th><code>UIPresentationController</code></th></tr></thead><tbody><tr><td><code>draw()</code></td><td><code>viewWillLayoutSubviews()</code></td><td><code>containerViewWillLayoutSubviews()</code></td></tr><tr><td><code>layoutSubviews()</code></td><td><code>viewDidLayoutSubviews()</code></td><td><code>containerViewDidLayoutSubviews()</code></td></tr><tr><td><code>traitCollectionDidChange()</code></td><td><code>traitCollectionDidChange()</code></td><td><code>traitCollectionDidChange()</code></td></tr><tr><td><code>tintColorDidChange()</code></td><td></td><td></td></tr></tbody></table><p>Therefore, put your appearance-specific logic in any of them (make sure to not do unnecessary work though!).</p><blockquote><p>If you're unfamiliar with these methods, I suggest you to have a look at <a href="http://tech.gc.com/demystifying-ios-layout/">this great article</a> about Auto Layout life cycle.For even more insights, you can watch WWDC 2015 sessions <a href="https://developer.apple.com/videos/play/wwdc2015/218">218</a> and <a href="https://developer.apple.com/videos/play/wwdc2015/218">219</a>, which are part 1 and 2 of "Mysteries of Auto Layout" (part 2 is the most important one).</p></blockquote><blockquote><p>A small reminder: these methods are exposed to developers to be overridden, not to be called.Call their complementary methods like <code>setNeedsUpdateConstraints()</code>, <code>setNeedsLayout()</code>, etc. in order to trigger them.</p></blockquote><p>Do not put appearance-specific code in <code>init</code>, <code>viewDidLoad</code> or other places:<br>these methods will be not be triggered again when/if the interface appearance changes.</p><h3>Debugging Dark Mode</h3><p>You're almost set to start adopting Dark Mode! There are just a couple of things that you should know first.</p><h4>Storyboards</h4><p>In scoreboards and <code>.xib</code> files, beside choosing the device and orientation to preview your screens in, you can now also toggle the interface preference between light and dark:</p><img src="https://www.fivestars.blog/assets/posts/ios-dark-mode-how-to/interface-storyboard.png" alt="interface-storyboard"/><blockquote><p>One of my storyboards, fully supporting Dark Mode.</p></blockquote><p>I found it much faster to adopt dark mode by using this toggle rather than changing something and then testing it by building and running the app, I suggest you to do the same, especially if the screen you're working on is not easy to get into.</p><h4>Simulator</h4><p>In Xcode 11 there’s a new button in the debugging toolbar: this button lets you access to an Environment Overrides popup which lets you, among other things, change font size and switch interface mode, you can even enable high contrast!</p><img src="https://www.fivestars.blog/assets/posts/ios-dark-mode-how-to/environment-overrides.png" alt="overrides"/><blockquote><p>No need to use the Accessibility Inspector anymore, hurray!</p></blockquote><p>Use it to test both interfaces on the simulator, this is the only way to change the interface appearance in there!</p><p>Obviously don't forget to test each screen on a real device before shipping 😉.</p><h2>Where To start</h2><p>Now that you're finally ready to adopt dark mode, here’s a roadmap for you to follow:</p><ol><li>Download and install Xcode 11 beta (duh!)</li><li>Build and Run your app with dark mode enabled</li><li>Fix the obvious "mistakes" spotted</li><li>Add dark variants to all your assets</li><li>Adapt Dark Mode one screen at a time:<ul><li>Start from the <code>.xib</code>s files</li><li>Move to storyboards</li><li>Move to code</li><li>Repeat for all screens 6. Make sure to set the foreground key when drawing attributed text 7. Move all your appearance logic in the “Draw time” functions</li></ul></li></ol><p>Some heads up:</p><ul><li>don’t forget to test your app in light mode, too 😄</li><li>don't forget the LaunchScreen storyboard!</li></ul><h2>Tips</h2><h3>Design System</h3><p>If your app uses a Design System, then congratulations! Adopting Dark Mode will take you literally 5 minutes (assuming you've read this whole article).</p><p>If your app doesn't have one yet, now it's a great time to start building one.<br>In case you're unfamiliar with this concept, I suggest you to watch the awesome talk "<em>Building a Mobile Design System</em>" by fellow developer <a href="https://twitter.com/krstnfx">Kristina Fox</a> from <a href="https://www.tryswift.co">Try! Swift Tokyo</a> (<a href="https://www.youtube.com/watch?v=Fvq8PQKJj_k">video</a>, <a href="https://speakerdeck.com/krstnfx/building-a-mobile-design-system">slides</a>).</p><h3>Assets Catalog: bid farewell to iOS 10</h3><p>With iOS 12 adoption being over 85% as of June 2019, it's a no brainer to drop iOS 10 support. With that we can finally declare and use custom colors directly from the assets catalog, which will make your dark mode adoption quick and easy.</p><h3>Use Storyboards and Xibs</h3><p>Use your storyboards/xibs with the interface appearance toggle to preview each screen in both appearances, no need to build and run your app over and over to test every change.</p><h3>Use SwiftUI</h3><p>Anything built with SwiftUI automatically supports dark/light mode and their high contrast variants.</p><h2>Dark Mode Resources</h2><p>Apple has multiple great resources around adopting Dark Mode: - I suggest you to start from the <a href="https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/dark-mode/">Dark Mode section</a> of Apple Human Interface Guidelines and choose where to go from there (plenty of links in that chapter!).<br>- another interesting resource from Apple is <a href="https://developer.apple.com/documentation/appkit/supporting_dark_mode_in_your_interface">Supporting Dark Mode in Your Interface</a>, which was written last year for macOS but still very valid for iOS.</p><p>Obviously there are also a few WWDC19 sessions that are a must watch:</p><ul><li><a href="https://developer.apple.com/videos/play/wwdc2019/214/">Implementing Dark Mode on iOS</a></li><li><a href="https://developer.apple.com/videos/play/wwdc2019/808/">What's New in iOS Design</a></li></ul><p>It's also very good to watch the previous year sessions where we were first introduced to Dark Mode (in macOS):</p><ul><li><a href="https://developer.apple.com/videos/play/wwdc2018/210/">Introducing Dark Mode</a></li><li><a href="https://developer.apple.com/videos/play/wwdc2018/218/">Advanced Dark Mode sessions</a></li></ul><h2>Conclusions</h2><p>Dark Mode has been in the works for years and we finally have it in our hands:<br>I'm using it everyday on my macs and I’m very excited to finally be able to use it on iOS, too.<br><br>What do you think? Will you use Dark Mode right away? Do you think adopting it in your apps is going to take a lot of effort?<br>I personally can’t wait to see it released! Feel free to <a href="https://twitter.com/zntfdr">reach me out on Twitter</a> and share screenshots of your apps in Dark Mode! 🤩</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/wwdc19-wishlist</guid><title>My WWDC19 Wishlist 🧞‍♂️</title><description></description><link>https://www.fivestars.blog/articles/wwdc19-wishlist</link><pubDate>Tue, 7 May 2019 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>Every year, Mac and iOS Developers all around the world welcome WWDC with excitement, high hopes, and a long list of wishes that we would love Apple to announce.</p><p>Some of those wishes are very humble, like a fix for a radar filed 5 years ago, while others are very ambitious, like the opening of a private framework and much, much more.</p><p>I thought it would be fun to make my #wwdc19 wishlist public, without further ado:</p><h2>Dark Mode</h2><img src="https://www.fivestars.blog/assets/posts/wwdc19-wishlist/dark-mode.gif" alt="dark-mode"/><blockquote><p>Photo credit: <a href="https://www.angelakismax.com/personal#/ios/">Angela Kismax</a>.</p></blockquote><p>This is a given: macOS gained Dark Mode in 2018 and, with Apple releasing Marzipan this year, UIKit apps must gain Dark Mode support. I’m really excited for this new feature and can’t wait to try it out myself.</p><blockquote><p><a href="https://openradar.appspot.com/26852871">Open Radar</a>, <a href="rdar://26852871">rdar://26852871</a></p></blockquote><h2>ARKit Occlusion</h2><table><tbody><tr><td><img src="https://www.fivestars.blog/assets/posts/wwdc19-wishlist/niantic-ar-standard.gif" alt="standard AR"/></td><td><img src="https://www.fivestars.blog/assets/posts/wwdc19-wishlist/niantic-ar-occlusion.gif" alt="enhanced AR"/></td></tr></tbody></table><blockquote><p>Left: AR without occlusion. Right: AR with occlusion. Video credit: <a href="https://www.youtube.com/watch?v=7ZrmPTPgY3I ">Niantic</a>. Gif credit: <a href="https://www.theverge.com/2018/6/28/17514340/niantic-labs-pokemon-go-ar-demo-pikachu-occlusion ">The Verge</a></p></blockquote><p>Apple has done a tremendous job with ARKit. This is probably the framework that Apple is pushing the most, and I don’t expect that to change anytime soon, especially since there are rumors of an Apple AR headset in the works.</p><p>One of the main limitations of this framework has been the lack of a native way to enable obstacle occlusion. As seen in the gifs above, this addition is key to maintain the illusion of an immersive AR experience: having this feature offered in ARKit would improve greatly the AR experience for millions of users.</p><blockquote><p><a href="https://openradar.appspot.com/50478914">Open Radar</a>, <a href="rdar://50478914">rdar://50478914</a></p></blockquote><h2>End of Subscriptions Madness</h2><img src="https://www.fivestars.blog/assets/posts/wwdc19-wishlist/subscription-1.jpg" alt="subscription-hig"/><blockquote><p>The unofficial App Review HIG addendum showing acceptable renewable subscriptions pitch screens, by <a href="https://twitter.com/agiletortoise/status/1075042403751477249">Greg Pierce</a></p></blockquote><p>If you <a href="https://twitter.com/zntfdr">follow me</a>, <a href="https://twitter.com/drbarnard">David Barnard</a>, <a href="https://twitter.com/agiletortoise">Greg Pierce</a>, or other developers that offer auto-renewable subscriptions in their app, you already know <a href="https://twitter.com/agiletortoise/status/1075042403751477249">the pain</a> <a href="https://medium.com/revenuecat-blog/apple-will-reject-your-subscription-app-if-you-dont-include-this-disclosure-bba95244405d">we have to go through</a>: the rules around such subscriptions, particularly regarding the subscription details disclosure, are ever-changing, subject to personal interpretation (inconsistent even within the App Review Team), and by far the main source of constant app rejections. It’s very, very frustrating.</p><p>Apple even launched a <a href="https://developer.apple.com/design/human-interface-guidelines/subscriptions/overview/">Subscriptions Human Interface Guidelines Page</a> early this year, however fellow developer David Barnard <a href="https://twitter.com/drbarnard/status/1089154933822230528">quickly pointed out</a> that not even Apple's own examples would pass a real App Review 🙃.</p><p>I would love for Apple to end all of this. Thousands of hours have been wasted, between both Apple employees and third party developers. It is beyond nonsensical. Apple’s own apps don’t follow their own guideline, it’s just bad all around.</p><img src="https://www.fivestars.blog/assets/posts/wwdc19-wishlist/subscription-2.jpg" alt="subscription-confirmation"/><blockquote><p>Apple's confirmation Sheet</p></blockquote><p>I'm certain that there's a legal reason behind all of this, however, since Apple already presents a confirmation sheet where people have to authenticate and approve a transaction, I believe that it would make sense to move all these fine prints in there, too (maybe this confirmation sheet could be a two step process, or something similar).</p><p>This way developers could focus solely on the content of the app, while the legal side is taken care of by the iOS sheet.</p><blockquote><p><a href="https://openradar.appspot.com/50479123">Open Radar</a>, <a href="rdar://50479123">rdar://50479123</a></p></blockquote><h2>Alternate App Icons Management</h2><img src="https://www.fivestars.blog/assets/posts/wwdc19-wishlist/app-icon.gif" alt="change-icon-confirmation"/><blockquote><p>Apple's confirmation after changing an app icon.</p></blockquote><p>Users can change their apps icons since iOS 10.3.</p><p>While this is awesome, the developer implementation is not friendly:</p><ul><li>all these alternate icons images must be placed at the root of the app project, which means having tons of <code>.png</code>s (one for each size, for each custom icon) just sitting there.</li></ul><ul><li>all alternate icons (again, one for each size, for each custom icon) must be declared, one by one, into the app’s <code>info.plist</code>.</li></ul><ul><li>there’s no native way to automate this.</li></ul><p>The whole process is very error prone and could be completely replaced by letting developers add alternate icons directly in one assets catalog. As someone with several apps on the App Store, this would make me very happy and make me want to offer way more alternate icons.</p><blockquote><p><a href="https://openradar.appspot.com/50479188">Open Radar</a>, <a href="rdar://50479188">rdar://50479188</a></p></blockquote><h2>Multiple Bedtime Scheduling</h2><img src="https://www.fivestars.blog/assets/posts/wwdc19-wishlist/bed-time.png" alt="ios-12-bed-time"/><blockquote><p>iOS 12 Bedtime.</p></blockquote><p>One of my favorite introductions in iOS 12 is <a href="https://support.apple.com/en-is/HT208655">Bedtime</a>.</p><p>I work out three times a week: on the days that I work out, I must wake up before 6am in order to hit the gym and then go to work at a reasonable time. Currently, there’s no way to set two different <em>bedtimes</em>, depending on the day of the week.</p><p>Even If I had a more regular schedule, I still would like to have a <em>weekday</em> bedtime, and a <em>weekend</em> bedtime. Again, with iOS 12 this is not possible.</p><p>Having the possibility to define multiple bedtimes would be awesome: for the moment, I'm back to <em>normal</em> alarms.</p><blockquote><p><a href="https://openradar.appspot.com/50479208">Open Radar</a>, <a href="rdar://50479208">rdar://50479208</a></p></blockquote><h2>Push Notifications Spam Reporting</h2><img src="https://www.fivestars.blog/assets/posts/wwdc19-wishlist/notifications-hell.jpg" alt="notifications-hell"/><blockquote><p>Notifications Hell (credits: <a href="https://documentation.onesignal.com/docs/action-buttons">OneSignal</a>)</p></blockquote><p>iOS 12 brought us great notifications enhancements such as Deliver Quietly, Group Notifications, and a quick way to turn off an app notifications directly from any notification.</p><p>These are great ways for users to protect themselves from <em>noisy</em> apps however, this is not the best user experience.</p><p>Instead, I expect Apple to notice misbehaving apps (a.k.a. the ones that continuously push advertisements as notifications) and remove their notifications privilege altogether (maybe only temporarily first, and permanently later, if the misbehavior continues), don't ask the users to manually protect themselves, for every device they own.</p><p>I completely understand that detecting this behavior is basically impossible for the Apple Review team, especially since an app could "<em>play nice</em>" when it is in review, and start sending notifications only once it has been approved.</p><p>I think we, as app users, could be of great help: as soon as we get an iMessage from a unknown contact, Apple prompts us with an action to report it as junk (see picture below on the left, just under the text bubble).</p><p>Apple could implement something similar for notifications, you can see a mock on the image below on the right.</p><img src="https://www.fivestars.blog/assets/posts/wwdc19-wishlist/report.jpg" alt="report-message-as-spam"/><blockquote><p>Left: the current report junk iMessage implementation (credits: <a href="https://www.iphonelife.com/content/fake-text-message-how-to-report-block-spam-text-messages">iPhoneLife</a>). Right: A mock up of what report junk for notifications could be.</p></blockquote><p>Once Apple receives multiple reports of the same app, then, and only then, an internal investigation is opened and it is decided whether to take further action or not.</p><p>This way:</p><ul><li>Apple doesn't need to observe this behavior during app review (again, apps can act nicely when in review, and start sending spam notifications only after approval)</li><li>Apple doesn't need to actively analyze every single notification that goes through its <a href="https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html#//apple_ref/doc/uid/TP40008194-CH8-SW1">Push Notification service</a>.</li><li>We users do the reporting for Apple.</li></ul><p>It’s a win-win situation.</p><blockquote><p><a href="https://openradar.appspot.com/50479242">Open Radar</a>, <a href="rdar://50479242">rdar://50479242</a></p></blockquote><h2>Smarter, Self-conscious Siri</h2><img src="https://www.fivestars.blog/assets/posts/wwdc19-wishlist/siri.jpg" alt="siri"/><blockquote><p>Photo Credits: <a href="https://www.apple.com/siri/">Apple</a></p></blockquote><p>My best purchase of 2018 was an HomePod: it feels empowering to simply shout actions anywhere at home and having them executed, especially after <a href="https://twitter.com/zntfdr/status/1079201278398320641">I started using HomeKit</a>.</p><p>With that being said, Siri still has a long way to go: among the improvements that I would like to see, there's Siri being more self conscious.</p><p>Questions like “When is the next Apple Event?”, “What is the battery on my phone?“ should always have a clear answer. When asked for an action that has to run on a device, I would like Siri (from the HomePod) to forward it to the correct device, not saying that it can’t do that on an HomePod. And other <em>small</em> details like this.</p><p>Being able to handoff music from one device to another, a-la "Hey Siri, continue play on my iPad/HomePod/iPhone/Airpods" would also be great.</p><p>Lastly, I would like to see Siri improving every week: so far it seems like Siri improves only when a new major iOS release comes out.</p><blockquote><p><a href="https://openradar.appspot.com/50479268">Open Radar</a>, <a href="rdar://50479268">rdar://50479268</a></p></blockquote><h2>Variable Width App Title</h2><table><tbody><tr><td><img src="https://www.fivestars.blog/assets/posts/wwdc19-wishlist/icon-title-XS.jpg" alt="icon-xs"/></td><td><img src="https://www.fivestars.blog/assets/posts/wwdc19-wishlist/icon-title-M.jpg" alt="icon-m"/></td><td><img src="https://www.fivestars.blog/assets/posts/wwdc19-wishlist/icon-title-L.jpg" alt="icon-l"/></td><td><img src="https://www.fivestars.blog/assets/posts/wwdc19-wishlist/icon-title-XL.jpg" alt="icon-xl"/></td></tr></tbody></table><blockquote><p>The Stack Exchange app icon and title with different text size preferences</p></blockquote><p>Apple does an astounding job to improve iOS accessibility every year: every time I show these features to people, they're blown away.</p><p>One aspect that is not yet very accessible yet is the app title, currently statically defined in the app <code>info.plist</code> under the <code>Bundle Display Name</code> key (<code>CFBundleDisplayName</code>): it can be localized, but that's it.</p><p>If the title is too long, it will be truncated: even if the title is not truncated at the current font size, this doesn't guarantee that it won't be truncated when the user set a different font size (see example above).</p><p>Since Xcode 10, we have the possibility to use <a href="https://useyourloaf.com/blog/variable-width-strings/">Variable Width Strings</a>, where the actual content of our string varies based on the space available.</p><p>It would be awesome if we could use this for the app titles, too.</p><blockquote><p>Another situation where the title might get truncated is after updating the app, or when the app is being tested via TestFlight:this is because of the blue/orange indicator next to the title that appears under these circumstances. While the dot is a nice reminder of the app state (just updated for blue, beta for orange), this indicator actually <em>steals</em> space from the title, which might therefore get truncated.</p></blockquote><blockquote><p><a href="https://openradar.appspot.com/50479294">Open Radar</a>, <a href="rdar://50479294">rdar://50479294</a></p></blockquote><h2>Network Sandboxing</h2><img src="https://www.fivestars.blog/assets/posts/wwdc19-wishlist/ios-sandboxing.png" alt="ios-sandboxing"/><blockquote><p><a href="https://krausefx.com/blog/ios-app-network-sandboxing ">Network Sandboxing</a>, concept by <a href="https://twitter.com/KrauseFx">Felix Krause</a></p></blockquote><p>iOS does an awesome job at protecting the privacy of its users. Want to listen to the microphone? Ask for authorization. Use the camera? Ask for authorization. Access the photo library? User location? Contacts? Reminders? Ask for authorization.</p><p>One aspect where apps have been completely free to do as they please is networking: as soon as any app is launched, without the user knowing, said app can start sending requests and data to any server in the world.</p><p>Even if the user trusts the developer behind an app, that app might use a framework that does <em>more</em> than what it promises on paper, and both the user and the developer using that framework cannot do anything about it (yet).</p><p>A very clean way to fix all of this would be for Apple to introduce Network Sandboxing: a concept presented by <a href="https://twitter.com/KrauseFx">Felix Krause</a> <a href="https://krausefx.com/blog/ios-app-network-sandboxing ">one year ago</a>.</p><p>In short, Apple could demand developers to disclose which domains their app need to access to, and make sure that any other request outside those is blocked. Felix goes more in detail in <a href="https://krausefx.com/blog/ios-app-network-sandboxing ">his article</a>, I suggest you to have a look there if you're interested in this topic.</p><blockquote><p><a href="https://openradar.appspot.com/50479334">Open Radar</a>, <a href="rdar://50479334">rdar://50479334</a></p></blockquote><h2>A macOS-friendly Marzipan</h2><img src="https://www.fivestars.blog/assets/posts/wwdc19-wishlist/marzipan-plz.png" alt="Home.app marzipan"/><blockquote><p>The Home.app running on macOS</p></blockquote><p>Having iOS apps like Home, News, Voice Memos etc on the Mac is way better than no app at all.</p><p>However, there are some elements in those apps that don’t have a home on the Mac: the screen shot above shows an example of an option <em>window</em>, very different than anything we've seen so far on desktop.</p><p>My hope is that the version of Marzipan that we will see released next month will give a chance to developers to make their iOS apps feel and look like native macOS apps (on macOS).</p><p>Speaking of Marzipan, I’ve always wanted to access my Health.app data on iPad and Mac, maybe Marzipan will take care of this 🤞🏻.</p><h2>Conclusions</h2><p>Obviously these are only a few points, I’m sure I’ve forgotten plenty and, to be honest, I haven’t put much thought on it (I want Apple to surprise me 😁).</p><p>I know that many wishes above are easier said than done, as pretty much anything in the software world, however a man can dream!</p><p>Regardless of whether any of these points will become a reality or not, I'm super excited for next month WWDC and can't wait to play with all the brand new technologies that we will see!</p><p>What do you think? Did I completely miss something? What are your wishes for this year WWDC? Let me know on <a href="https://twitter.com/zntfdr">Twitter @zntfdr</a> 😁.</p><p>Thank you for reading and <a href="https://www.fivestars.blog/feed.rss">stay tuned</a> for more articles!</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/five-stars</guid><title>Introducing Five Stars</title><description></description><link>https://www.fivestars.blog/articles/five-stars</link><pubDate>Wed, 1 May 2019 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>After posting for nearly two years in <a href="https://medium.com/swiftly-swift">Swiftly Swift</a>, and a hiatus of almost two years after that, I welcome you to my new blog!</p><p>A lot has happened since my last update, and I have various reasons behind the lack of posts: for starters, as somebody that cares deeply about everything that surrounds Apple, I felt too constrained by my previous, self-imposed, blog name.</p><p>I still want to write about Swift and iOS Development, however I don't want these topics to be the limit of what you can expect here: from now on do expect new experimental, meta, opinionistic, and just <em>different</em> articles as well.</p><p>In the meanwhile I've also become a full-time iOS Developer: previously I was building apps, learning, and writing about iOS development exclusively during my own spare time. I'm very happy to have turned my main hobby into something that I enjoy doing all day (almost) every day!</p><p>Thanks to this last change, my iOS knowledge, learning, and experience simply skyrocketed and, even to this day, I keep a pace that simply was not possible just two years ago. I'm very grateful for this opportunity, and this blog will definitively benefit and reflect these new gains.</p><p>Lastly, I want to mention that I've never stopped doing iOS development. In fact I'm more than happy to let you know that <a href="https://itunes.apple.com/us/developer/federico-zanetello/id1053443073?at=1001laCc">my own apps</a> are now more popular than ever, have more than double the revenue since my last update, and <a href="https://twitter.com/zntfdr/status/1117414145865830401">have been even featured in the App Store</a>, multiple times!</p><p>The future ahead is bright: I hope you will join me in this journey.</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/dijkstra-algorithm-swift-2</guid><title>The Right Way To Write Dijkstra’s Algorithm In Swift 👾</title><description></description><link>https://www.fivestars.blog/articles/dijkstra-algorithm-swift-2</link><pubDate>Tue, 29 Aug 2017 00:00:00 +0000</pubDate><content:encoded><![CDATA[<blockquote><p>Update: this project is now <a href="https://github.com/zntfdr/Connection">open source</a>.</p></blockquote><p>Last week <a href="https://www.fivestars.blog/articles/dijkstra-algorithm-swift/">I’ve written about how to code the famous Dijkstra’s Algorithm in Swift</a>: this article is its follow up.</p><h2>My Reason</h2><p>I didn’t write the previous article and spent time doing research just for the article’s sake: a similar algorithm is implemented in all my <a href="https://itunes.apple.com/us/developer/kimchi-media/id1053443073?at=1001laCc">🚇 Metro Apps</a>.</p><p>With this article I’m going to show you why you don’t need to follow my same path, by taking advantage of the tools offered by Apple and <em>obtain the same result with much less effort</em> (while gaining other benefits as well).</p><h2>Introducing GameplayKit</h2><p>Among the dozens of Apple’s frameworks there’s <em>GameplayKit</em>: this framework is always available to you, regardless of whether your App is game or not.</p><p>But what is GameplayKit? I’m glad you asked! I’ll let <a href="https://developer.apple.com/library/content/documentation/General/Conceptual/GameplayKit_Guide/index.html">Apple’s GameplayKit Guide</a> answer this for you:</p><blockquote><p>GameplayKit is a collection of foundational tools and technologies for building games in iOS, OS X, and tvOS. Building, evolving, and maintaining a sophisticated game requires a well-planned design — GameplayKit provides architectural tools to help you design modular, scalable game architecture with minimal effort.Wow, this framework sounds like a lot of stuff!</p></blockquote><h3>GameplayKit Cores</h3><img src="https://www.fivestars.blog/assets/posts/dijkstra-algorithm-swift-2/cores.png" alt="cores"/><blockquote><p>GameplayKit Cores</p></blockquote><p>Let’s use the <a href="https://en.wikipedia.org/wiki/Divide_and_conquer_algorithm">Divide And Conquer Paradigm</a> to understand this GameplayKit better, here are its main cores (from <a href="https://developer.apple.com/library/content/documentation/General/Conceptual/GameplayKit_Guide/index.html">Apple’s GameplayKit Guide</a>):</p><ol><li><a href="https://developer.apple.com/library/content/documentation/General/Conceptual/GameplayKit_Guide/RandomSources.html#//apple_ref/doc/uid/TP40015172-CH9-SW1">Randomization</a> Use these robust, flexible implementations of standard algorithms as the building blocks for many kinds of game mechanics.</li></ol><ol start="2"><li><a href="https://developer.apple.com/library/content/documentation/General/Conceptual/GameplayKit_Guide/EntityComponent.html#//apple_ref/doc/uid/TP40015172-CH6-SW1">Entities and Components</a> Design more reusable gameplay code by building on this architecture.</li></ol><ol start="3"><li><a href="https://developer.apple.com/library/content/documentation/General/Conceptual/GameplayKit_Guide/StateMachine.html#//apple_ref/doc/uid/TP40015172-CH7-SW1">State Machines</a> Use this architecture to untangle complex procedural code in your gameplay designs.</li></ol><ol start="4"><li><a href="https://developer.apple.com/library/content/documentation/General/Conceptual/GameplayKit_Guide/Minmax.html#//apple_ref/doc/uid/TP40015172-CH2-SW1">The Minmax Strategist</a> Create a model for your turn-based game and AI player objects that use the model to plan optimal moves.</li></ol><ol start="5"><li><a href="https://developer.apple.com/library/content/documentation/General/Conceptual/GameplayKit_Guide/Pathfinding.html#//apple_ref/doc/uid/TP40015172-CH3-SW1">Pathfinding</a> Describe a game world as a graph, allowing GameplayKit to plan optimal routes for game characters to follow.</li></ol><ol start="6"><li><a href="https://developer.apple.com/library/content/documentation/General/Conceptual/GameplayKit_Guide/Agent.html#//apple_ref/doc/uid/TP40015172-CH8-SW1">Agents, Goals, and Behaviors</a> Use this simulation to let game characters move themselves based on high-level goals and react to their surroundings.</li><li><a href="https://developer.apple.com/library/content/documentation/General/Conceptual/GameplayKit_Guide/RuleSystems.html#//apple_ref/doc/uid/TP40015172-CH10-SW1">Rule Systems</a> Separate game design from executable code to speed up your gameplay development cycle, or implement fuzzy logic reasoning to add realistic behavior to your game.</li></ol><p>Wait….what’s the fifth point again?</p><blockquote><p>5. <a href="https://developer.apple.com/library/content/documentation/General/Conceptual/GameplayKit_Guide/Pathfinding.html#//apple_ref/doc/uid/TP40015172-CH3-SW1">Pathfinding</a> Describe [...] world as a graph, […] plan optimal routes [...]</p></blockquote><p>This sounds a lot like what we’re trying to do! Let’s dig deeper.</p><p>GameplayKit Pathfinding If you read the whole <a href="https://developer.apple.com/library/content/documentation/General/Conceptual/GameplayKit_Guide/Pathfinding.html#//apple_ref/doc/uid/TP40015172-CH3-SW1">Pathfinding introduction</a>, you’ll discover that we can use it in three cases:</p><ul><li>Case 1 — A Continuous space interrupted by obstacles</li></ul><img src="https://www.fivestars.blog/assets/posts/dijkstra-algorithm-swift-2/case1.png" alt="case1"/><ul><li>Case 2 — A discrete two-dimensional grid, where the only valid locations are those with integer coordinates</li></ul><img src="https://www.fivestars.blog/assets/posts/dijkstra-algorithm-swift-2/case2.png" alt="case2"/><ul><li>Case 3 — A collection of discrete locations and the connections between them.</li></ul><img src="https://www.fivestars.blog/assets/posts/dijkstra-algorithm-swift-2/case3.png" alt="case3"/><p>Wow! The third case sounds exactly what we’re trying to do! 🙌🏻 Let’s implement it!</p><h2>GameplayKit Entities</h2><h3>Graph</h3><p>Firstly, GameplayKit want us to create a new graph, this is done by creating a new <code>GKGraph</code> instance:</p><pre><code><span class="keyword">let</span> myGraph = <span class="type">GKGraph</span>()
</code></pre><h3>Nodes</h3><p>Secondly, we will need to define our nodes: again, GameplayKit has us covered with its <code>GKGraphNode</code> class:</p><pre><code><span class="keyword">let</span> nodeA = <span class="type">GKGraphNode</span>()
<span class="keyword">let</span> nodeB = <span class="type">GKGraphNode</span>()
<span class="keyword">let</span> nodeC = <span class="type">GKGraphNode</span>()
<span class="keyword">let</span> nodeD = <span class="type">GKGraphNode</span>()
<span class="keyword">let</span> nodeE = <span class="type">GKGraphNode</span>()
</code></pre><p>Please don’t forget to add them into our Graph:</p><pre><code>myGraph.<span class="call">add</span>([nodeA, nodeB, nodeC, nodeD, nodeE])
</code></pre><h3>Node Connections</h3><p>While <a href="https://www.fivestars.blog/articles/dijkstra-algorithm-swift/">I created a different class for the Connections</a>, GameplayKit takes a much easier approach and just offers a <code>GKGraphNode</code> method <code>addConnection(to:[GKGraphNode], bidirectional: Bool)</code>:</p><pre><code>nodeA.<span class="call">addConnections</span>(to: [nodeB, nodeD], bidirectional: <span class="keyword">true</span>)
nodeB.<span class="call">addConnections</span>(to: [nodeD], bidirectional: <span class="keyword">true</span>)
nodeC.<span class="call">addConnections</span>(to: [nodeE], bidirectional: <span class="keyword">false</span>)
nodeD.<span class="call">addConnections</span>(to: [nodeC], bidirectional: <span class="keyword">false</span>)
</code></pre><p>GameplayKit way is much simpler than mine: this is because Apple’s framework assumes each <strong>connection weight</strong> to <strong>always</strong> be the <strong>same</strong> (I will take care of this point at the end of the article).</p><h3>Dijkstra’s Algorithm</h3><p>We’ve now defined the whole Graph! The only thing missing is the shortest path finder algorithm: how do we go from <code>nodeA</code> to <code>nodeE</code>s? Well…</p><pre><code>myGraph.<span class="call">findPath</span>(from: nodeA, to: nodeE)
</code></pre><blockquote><p>Yup, that’s one line.</p></blockquote><p>Wait, what? That’s it? Yes, the <a href="https://developer.apple.com/documentation/gameplaykit/gkgraph/1501270-findpath"><code>findPath</code> method</a> returns us an array of nodes that describe the path from the start node to the target node, or an empty array if there’s no path between the two nodes. 🎉🎉</p><h3>Complete Code</h3><p>Just to make you understand how amazing GameplayKit is, here’s the full code we’ve written so far (you can run it in a Xcode/Swift Playground):</p><pre><code><span class="keyword">import</span> GameplayKit

<span class="keyword">let</span> myGraph = <span class="type">GKGraph</span>() <span class="comment">// declaring the Graph</span>
<span class="keyword">let</span> nodeA = <span class="type">GKGraphNode</span>() <span class="comment">// declaring the Graph Nodes</span>
<span class="keyword">let</span> nodeB = <span class="type">GKGraphNode</span>()
<span class="keyword">let</span> nodeC = <span class="type">GKGraphNode</span>()
<span class="keyword">let</span> nodeD = <span class="type">GKGraphNode</span>()
<span class="keyword">let</span> nodeE = <span class="type">GKGraphNode</span>()

myGraph.<span class="call">add</span>([nodeA, nodeB, nodeC, nodeD, nodeE]) <span class="comment">// adding the Graph Nodes to the Graph</span>
nodeA.<span class="call">addConnections</span>(to: [nodeB, nodeD], bidirectional: <span class="keyword">true</span>) <span class="comment">// declaring the Graph Nodes connections</span>
nodeB.<span class="call">addConnections</span>(to: [nodeD], bidirectional: <span class="keyword">true</span>)
nodeC.<span class="call">addConnections</span>(to: [nodeE], bidirectional: <span class="keyword">false</span>)
nodeD.<span class="call">addConnections</span>(to: [nodeC], bidirectional: <span class="keyword">false</span>)

<span class="keyword">let</span> path: [<span class="type">GKGraphNode</span>] = myGraph.<span class="call">findPath</span>(from: nodeA, to: nodeE) <span class="comment">// finding the path between two nodes</span>
</code></pre><p>Our whole application now takes less lines of code than my shortestPath algorithm, it’s amazing how powerful this framework is! 🚀🚀🚀</p><h2>Weighted Connections</h2><p>One of the GameplayKit shortcomings is the lack of weighted connections between nodes.</p><p>I’ve tackled myself with this problem and found out an elegant minimal solution: subclass <code>GKGraphNode</code> and override the <code>cost(to node: GKGraphNode)</code> method. <a href="https://gist.github.com/zntfdr/c93fc2863ac0a847a90865564ca22121">Here’s my quick implementation</a>.</p><blockquote><p>In short, each node stores its connections cost in its (new)travelCost property: everything else stays the same 💯</p></blockquote><h2>Final Note</h2><p>The cool thing about GameplayKit is that we have no idea which shortestPath algorithm it uses: it could be <a href="https://www.fivestars.blog/articles/dijkstra-algorithm-swift/">Dijkstra</a>’s, it could be <a href="https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm">Bellman–Ford</a>’s, or it could be completely something else.</p><p>And you <em>should not care</em>, this has been taken care by very talented people at Apple, the problem is solved: by letting them taking care of this, we can <em>focus on building better products 💯</em>.</p><blockquote><p>On the same vibe: <a href="https://twitter.com/stevenhepting/status/878339681485635585">Have you ever wondered which algorithm Swift uses when we call <code>sort</code></a>?</p></blockquote><h2>Conclusions</h2><p>Both this and my <a href="https://www.fivestars.blog/articles/dijkstra-algorithm-swift/">previous article</a> has shown you a way to implement a shortestPath finder algorithm into your code.</p><p>The main reason why I like the latter much more than the former is, again, because most the core code of this option is maintained by dozens of people that work all year around in order to make it as fastest and as efficient as possible, while always taking advantage of the latest Apple hardware, and more.</p><p>If you write the code yourself, you’re the one maintaining: no one else will help you improve it, if there’s a bug, only you can find it and fix it. If you work on something else, that code will never improve (unless it’s open source).</p><p>If there’s one takeaway from this post, let it be this:<br><strong>Take advantage of the tools that are offered to you: the less you depend on yourself, the more powerful you are.</strong></p><p>Catch you in the next article! 👋🏻</p>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/dijkstra-algorithm-swift</guid><title>Dijkstra’s Algorithm In Swift</title><description></description><link>https://www.fivestars.blog/articles/dijkstra-algorithm-swift</link><pubDate>Tue, 22 Aug 2017 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>If you’ve ever heard of the term Graph Theory, surely you’re acquaintance with the Dijkstra’s Algorithm.</p><p>If you’re not, it’s all right! This article includes everything you need to know.</p><h2>Quick Introduction</h2><p>This chapter will bring you up to speed on what <em>Graph Theory</em> and the <em>Dijkstra’s Algorithm</em> are.</p><p>If you’re confident enough, you can skip this part! (🚀 jump to the <code>Swift Time</code> chapter)</p><h3>Graph Theory</h3><img src="https://www.fivestars.blog/assets/posts/dijkstra-algorithm-swift/graph.png" alt="graph"/><p>See the picture above? This is a what in Mathematics and Computer Science we call a <strong>Graph</strong>.</p><p>The circles are called <strong>Nodes</strong> (or <em>vertices</em>) and they represent the graph entities (more on this later) while the lines connecting the nodes are called <strong>Connections</strong> (or <em>edges</em>).</p><p>These connections, which always connect two nodes, are mainly of two types: bidirectional and mono-directional.<br>The former means that the connection works in both ways (duh!), while the latter means that the connection exists only from one node to the other, but not vice-versa (you will need a second connection to go the other way around).</p><p>These simple concepts have huge applications all over the world and you are probably using them all the time!</p><h4>Real World Examples</h4><h5>Bidirectional Graph: Facebook</h5><p>Let’s visualize your Facebook friends Graph!<br>You might end up with something like this:</p><img src="https://www.fivestars.blog/assets/posts/dijkstra-algorithm-swift/facebook.png" alt="facebook"/><blockquote><p>Picture from <a href="https://mathematica.stackexchange.com/questions/11673/how-to-play-with-facebook-data-inside-mathematica">Mathematica StackExchange</a>.</p></blockquote><p>In this graph each <strong>node</strong> is a <strong>person</strong>, and each (bidirectional) <strong>connection</strong> represents the <strong>friendship</strong> between these people.</p><blockquote><p>“Coincidentally” Facebook has a developer API called Graph.</p></blockquote><h5>Mono-directional Graph: Twitter</h5><p>If we take a look at our followers on Twitter, the result might be something like this:</p><img src="https://www.fivestars.blog/assets/posts/dijkstra-algorithm-swift/twitter.png" alt="twitter"/><blockquote><p>Picture from <a href="http://www.smrfoundation.org/2009/10/01/twitter-flickr-networks-in-nodexl-version-95-lots-of-new-features-improved-performance-2/">Social Media Research Foundation</a>.</p></blockquote><p>In this case each node is a Twitter Account, but the connections now are <strong>mono</strong>-directional, because if I follow you, this doesn’t imply that you are following me back.</p><h4>Dijkstra’s Algorithm</h4><p>Now that we understand what a Graph is, let’s talk about one of its hottest topics: the Shortest Path Problem.</p><p>This challenge is super simple to understand:<br>given two nodes in one Graph, find the shortest way to go from one node to the other (if it exists).</p><p>For us it’s a simple game (like solving a maze), but for a machine it’s a challenge that has to be solved as quick as possible.</p><p>Again, this is something that you’re using all the time, just think about how the Apple Maps App computes the best route, or how LinkedIn determines that a profile is a first/second/third level connection from you.</p><img src="https://www.fivestars.blog/assets/posts/dijkstra-algorithm-swift/linkedin.png" alt="linkedin"/><blockquote><p>Picture from <a href="https://www.linkedin.com/pulse/building-new-business-relationships-connecting-your-linkedin-miller">Linkedin</a>.</p></blockquote><p>One of the most famous (dare I say, <em>THE</em> most famous) Shortest Path Finder Algorithm is Dijkstra’s Algorithm, which is based on three steps:</p><ol><li>Find the cheapest unvisited node reachable</li></ol><ol start="2"><li>Mark it as visited and keep track on which nodes you can visit from it</li></ol><ol start="3"><li>Repeat</li></ol><p>The algorithm ends as soon as we reach the destination node, or whenever there are no more reachable nodes.</p><p>When I say <em>cheap</em> I mean the node that costs less to reach from all the nodes we’ve visited so far.</p><p>This cost comes from the connections: sometimes the graph connections are equal (like the Facebook friendships, there’s no difference between one connection and another) but sometimes they differ: if you have two ways to go to your home, one way might be <em>easier</em>/<em>cheaper</em>, than the other because on the latter you may have to climb a mountain or something.</p><h2>Swift Time!</h2><p>Now that we’re all up to speed, let’s implement everything in Swift!</p><h3>Node</h3><pre><code><span class="keyword">class</span> Node {
  <span class="keyword">var</span> visited = <span class="keyword">false
  var</span> connections: [<span class="type">Connection</span>] = []
}
</code></pre><p>The Node class will be nothing more than a property (used by the algorithm) to see if we’ve visited it already, and an array of connections to other nodes.</p><h3>Connection</h3><pre><code><span class="keyword">class</span> Connection {
  <span class="keyword">public let</span> to: <span class="type">Node</span>
  <span class="keyword">public let</span> weight: <span class="type">Int</span>
  
  <span class="keyword">public init</span>(to node: <span class="type">Node</span>, weight: <span class="type">Int</span>) {
    <span class="call">assert</span>(weight &gt;= <span class="number">0</span>, <span class="string">"weight has to be equal or greater than zero"</span>)
    <span class="keyword">self</span>.<span class="property">to</span> = node
    <span class="keyword">self</span>.<span class="property">weight</span> = weight
  }
}
</code></pre><p>As seen in the <code>Node</code> definition, each connection is assigned to a specific node, therefore all we need to define inside the connection itself is its weight (also known as cost) and the node it connects to.</p><p>I’m obviously using the mono-directional connections, this way it’s easier to manage both bidirectional and mono-directional Graphs.</p><h3>Path</h3><p>Lastly we need define a path: a path is nothing more than a successions of nodes.</p><p>This will help us keeping track which paths in our graph we’ve already visited and how we got there.</p><p>More importantly, our algorithm will return us this entity to describe the shortest path between our challenge source node and destination node.</p><p>We will use a recursive way for this definition:</p><pre><code><span class="keyword">class</span> Path {
  <span class="keyword">public let</span> cumulativeWeight: <span class="type">Int</span>
  <span class="keyword">public let</span> node: <span class="type">Node</span>
  <span class="keyword">public let</span> previousPath: <span class="type">Path</span>?
  
  <span class="keyword">init</span>(to node: <span class="type">Node</span>, via connection: <span class="type">Connection</span>? = <span class="keyword">nil</span>, previousPath path: <span class="type">Path</span>? = <span class="keyword">nil</span>) {
    <span class="keyword">if
      let</span> previousPath = path,
      <span class="keyword">let</span> viaConnection = <span class="call">connection</span> {
      <span class="keyword">self</span>.<span class="property">cumulativeWeight</span> = viaConnection.<span class="property">weight</span> + previousPath.<span class="property">cumulativeWeight</span>
    } <span class="keyword">else</span> {
      <span class="keyword">self</span>.<span class="property">cumulativeWeight</span> = <span class="number">0</span>
    }
    
    <span class="keyword">self</span>.<span class="property">node</span> = node
    <span class="keyword">self</span>.<span class="property">previousPath</span> = path
  }
}
</code></pre><p>For convenience, I’m also adding a <code>cumulativeWeight</code> property to keep track on the cost to reach the path’s node: this cost is the sum of all the connections weights that we have traveled from the source node to this node.</p><h3>The Algorithm</h3><p>Everything is set! Let’s dig into the algorithm:</p><pre><code><span class="keyword">func</span> shortestPath(source: <span class="type">Node</span>, destination: <span class="type">Node</span>) -&gt; <span class="type">Path</span>? {
  <span class="keyword">var</span> frontier: [<span class="type">Path</span>] = [] {
    <span class="keyword">didSet</span> { frontier.<span class="call">sort</span> { <span class="keyword">return</span> $0.<span class="property">cumulativeWeight</span> &lt; $1.<span class="property">cumulativeWeight</span> } } <span class="comment">// the frontier has to be always ordered</span>
  }
  
  frontier.<span class="call">append</span>(<span class="type">Path</span>(to: source)) <span class="comment">// the frontier is made by a path that starts nowhere and ends in the source</span>
  
  <span class="keyword">while</span> !frontier.<span class="call">isEmpty</span> {
    <span class="keyword">let</span> cheapestPathInFrontier = frontier.<span class="call">removeFirst</span>() <span class="comment">// getting the cheapest path available</span>
    <span class="keyword">guard</span> !cheapestPathInFrontier.<span class="property">node</span>.<span class="property">visited</span> <span class="keyword">else</span> { <span class="keyword">continue</span> } <span class="comment">// making sure we haven't visited the node already</span>
    
    <span class="keyword">if</span> cheapestPathInFrontier.<span class="property">node</span> === destination {
      <span class="keyword">return</span> cheapestPathInFrontier <span class="comment">// found the cheapest path 😎</span>
    }
    
    cheapestPathInFrontier.<span class="property">node</span>.<span class="property">visited</span> = <span class="keyword">true
    
    for</span> connection <span class="keyword">in</span> cheapestPathInFrontier.<span class="property">node</span>.<span class="property">connections</span> <span class="keyword">where</span> !connection.<span class="property">to</span>.<span class="property">visited</span> { <span class="comment">// adding new paths to our frontier</span>
      frontier.<span class="call">append</span>(<span class="type">Path</span>(to: connection.<span class="property">to</span>, via: connection, previousPath: cheapestPathInFrontier))
    }
  } <span class="comment">// end while</span>
  <span class="keyword">return nil</span> <span class="comment">// we didn't find a path 😣</span>
}
</code></pre><blockquote><p>Yup, just 23 lines of code.</p></blockquote><p>Firstly we define the <code>frontier</code>: the <code>frontier</code> is a collection of paths to nodes that can reach from the nodes the we’ve visited so far.</p><p>It’s initially empty, but as soon as we launch the script we will add a path to our start node (line <code>6</code>).</p><p>We can now start following the Dijkstra’s Algorithm steps:</p><h4>1. Find the cheapest unvisited node</h4><p>To do so we extract the cheapest path from our frontier (line <code>9</code>), check if the node was not visited yet and, if it is not, we proceed to the next step (line <code>10</code>).</p><h4>2. Mark it as visited and keep track on which nodes you can visit from it</h4><p>As soon as we reach this step we make sure to mark our node as visited (line <code>16</code>), and then we add all the new (unvisited) reachable nodes from this node by exploring its connections (lines <code>18–20</code>).</p><h4>3. Repeat</h4><p>The <code>while</code> cycle is now complete, therefore we really just repeat the two steps above!</p><h4>Note 1</h4><p>As you may have noticed, we do something between step 1 and 2 (line <code>12–14</code>): we check if the new cheapest node is our destination node: if it is, congrats! 🎉 We’ve completed the algorithm! Otherwise we continue to step 2.</p><h4>Note 2</h4><p>The algorithm may return an optional (line <code>1</code> and <code>22</code>): it is possible that the source and destination nodes don’t have a path that connect each other.</p><h2>👾 Swift Playground</h2><p>All right! We’ve now everything we need to play with the Dijkstra algorithm in Swift! Here’s the Playground with an example at the bottom.</p><pre><code><span class="keyword">class</span> Node {
  <span class="keyword">var</span> visited = <span class="keyword">false
  var</span> connections: [<span class="type">Connection</span>] = []
}

<span class="keyword">class</span> Connection {
  <span class="keyword">public let</span> to: <span class="type">Node</span>
  <span class="keyword">public let</span> weight: <span class="type">Int</span>
  
  <span class="keyword">public init</span>(to node: <span class="type">Node</span>, weight: <span class="type">Int</span>) {
    <span class="call">assert</span>(weight &gt;= <span class="number">0</span>, <span class="string">"weight has to be equal or greater than zero"</span>)
    <span class="keyword">self</span>.<span class="property">to</span> = node
    <span class="keyword">self</span>.<span class="property">weight</span> = weight
  }
}

<span class="keyword">class</span> Path {
  <span class="keyword">public let</span> cumulativeWeight: <span class="type">Int</span>
  <span class="keyword">public let</span> node: <span class="type">Node</span>
  <span class="keyword">public let</span> previousPath: <span class="type">Path</span>?
  
  <span class="keyword">init</span>(to node: <span class="type">Node</span>, via connection: <span class="type">Connection</span>? = <span class="keyword">nil</span>, previousPath path: <span class="type">Path</span>? = <span class="keyword">nil</span>) {
    <span class="keyword">if
      let</span> previousPath = path,
      <span class="keyword">let</span> viaConnection = <span class="call">connection</span> {
      <span class="keyword">self</span>.<span class="property">cumulativeWeight</span> = viaConnection.<span class="property">weight</span> + previousPath.<span class="property">cumulativeWeight</span>
    } <span class="keyword">else</span> {
      <span class="keyword">self</span>.<span class="property">cumulativeWeight</span> = <span class="number">0</span>
    }
    
    <span class="keyword">self</span>.<span class="property">node</span> = node
    <span class="keyword">self</span>.<span class="property">previousPath</span> = path
  }
}

<span class="keyword">extension</span> <span class="type">Path</span> {
  <span class="keyword">var</span> array: [<span class="type">Node</span>] {
    <span class="keyword">var</span> array: [<span class="type">Node</span>] = [<span class="keyword">self</span>.<span class="property">node</span>]
    
    <span class="keyword">var</span> iterativePath = <span class="keyword">self
    while let</span> path = iterativePath.<span class="call">previousPath</span> {
      array.<span class="call">append</span>(path.<span class="property">node</span>)
      
      iterativePath = path
    }
    
    <span class="keyword">return</span> array
  }
}

<span class="keyword">func</span> shortestPath(source: <span class="type">Node</span>, destination: <span class="type">Node</span>) -&gt; <span class="type">Path</span>? {
  <span class="keyword">var</span> frontier: [<span class="type">Path</span>] = [] {
    <span class="keyword">didSet</span> { frontier.<span class="call">sort</span> { <span class="keyword">return</span> $0.<span class="property">cumulativeWeight</span> &lt; $1.<span class="property">cumulativeWeight</span> } } <span class="comment">// the frontier has to be always ordered</span>
  }
  
  frontier.<span class="call">append</span>(<span class="type">Path</span>(to: source)) <span class="comment">// the frontier is made by a path that starts nowhere and ends in the source</span>
  
  <span class="keyword">while</span> !frontier.<span class="call">isEmpty</span> {
    <span class="keyword">let</span> cheapestPathInFrontier = frontier.<span class="call">removeFirst</span>() <span class="comment">// getting the cheapest path available</span>
    <span class="keyword">guard</span> !cheapestPathInFrontier.<span class="property">node</span>.<span class="property">visited</span> <span class="keyword">else</span> { <span class="keyword">continue</span> } <span class="comment">// making sure we haven't visited the node already</span>
    
    <span class="keyword">if</span> cheapestPathInFrontier.<span class="property">node</span> === destination {
      <span class="keyword">return</span> cheapestPathInFrontier <span class="comment">// found the cheapest path 😎</span>
    }
    
    cheapestPathInFrontier.<span class="property">node</span>.<span class="property">visited</span> = <span class="keyword">true
    
    for</span> connection <span class="keyword">in</span> cheapestPathInFrontier.<span class="property">node</span>.<span class="property">connections</span> <span class="keyword">where</span> !connection.<span class="property">to</span>.<span class="property">visited</span> { <span class="comment">// adding new paths to our frontier</span>
      frontier.<span class="call">append</span>(<span class="type">Path</span>(to: connection.<span class="property">to</span>, via: connection, previousPath: cheapestPathInFrontier))
    }
  } <span class="comment">// end while</span>
  <span class="keyword">return nil</span> <span class="comment">// we didn't find a path 😣</span>
}

<span class="comment">// **** EXAMPLE BELOW ****</span>
<span class="keyword">class</span> MyNode: <span class="type">Node</span> {
  <span class="keyword">let</span> name: <span class="type">String</span>
  
  <span class="keyword">init</span>(name: <span class="type">String</span>) {
    <span class="keyword">self</span>.<span class="property">name</span> = name
    <span class="keyword">super</span>.<span class="keyword">init</span>()
  }
}

<span class="keyword">let</span> nodeA = <span class="type">MyNode</span>(name: <span class="string">"A"</span>)
<span class="keyword">let</span> nodeB = <span class="type">MyNode</span>(name: <span class="string">"B"</span>)
<span class="keyword">let</span> nodeC = <span class="type">MyNode</span>(name: <span class="string">"C"</span>)
<span class="keyword">let</span> nodeD = <span class="type">MyNode</span>(name: <span class="string">"D"</span>)
<span class="keyword">let</span> nodeE = <span class="type">MyNode</span>(name: <span class="string">"E"</span>)

nodeA.<span class="property">connections</span>.<span class="call">append</span>(<span class="type">Connection</span>(to: nodeB, weight: <span class="number">1</span>))
nodeB.<span class="property">connections</span>.<span class="call">append</span>(<span class="type">Connection</span>(to: nodeC, weight: <span class="number">3</span>))
nodeC.<span class="property">connections</span>.<span class="call">append</span>(<span class="type">Connection</span>(to: nodeD, weight: <span class="number">1</span>))
nodeB.<span class="property">connections</span>.<span class="call">append</span>(<span class="type">Connection</span>(to: nodeE, weight: <span class="number">1</span>))
nodeE.<span class="property">connections</span>.<span class="call">append</span>(<span class="type">Connection</span>(to: nodeC, weight: <span class="number">1</span>))

<span class="keyword">let</span> sourceNode = nodeA
<span class="keyword">let</span> destinationNode = nodeD

<span class="keyword">var</span> path = <span class="call">shortestPath</span>(source: sourceNode, destination: destinationNode)

<span class="keyword">if let</span> succession: [<span class="type">String</span>] = path?.<span class="property">array</span>.<span class="call">reversed</span>().<span class="call">flatMap</span>({ $0 <span class="keyword">as</span>? <span class="type">MyNode</span>}).<span class="call">map</span>({$0.<span class="property">name</span>}) {
  <span class="call">print</span>(<span class="string">"🏁 Quickest path:</span> \(succession)<span class="string">"</span>)
} <span class="keyword">else</span> {
  <span class="call">print</span>(<span class="string">"💥 No path between</span> \(sourceNode.<span class="property">name</span>) <span class="string">&amp;</span> \(destinationNode.<span class="property">name</span>)<span class="string">"</span>)
}
</code></pre><h2>Conclusion</h2><p>In the academic world there are discussions on whether Graph Theory should replace Geometry in our schools curriculum: as a Computer Engineer myself, I can see why this might happen in the future 😁.</p><p>I hope you’ve learn something new today! Till next time 👋🏻</p><blockquote><p>⚠️️ Note️ ⚠️️ Some readers have pointed out that the <code>frontier</code> array is not the most efficient approach to obtain the cheapest available path, because of its sorting overhead. I agree, there are better ways: for example by using <a href="https://en.wikipedia.org/wiki/Priority_queue">Priority Queue</a> data structure. Thanks to <a href="https://www.reddit.com/user/madiyar">u/madiyar</a> and <a href="https://medium.com/u/fe5937eabcda">Ilya Myakotin</a> for the support!</p></blockquote>]]></content:encoded></item><item><guid isPermaLink="true">https://www.fivestars.blog/articles/little-xcode-beta-surprises</guid><title>Little Xcode Beta Surprises 🎁: Core Graphics Codable Conformance</title><description></description><link>https://www.fivestars.blog/articles/little-xcode-beta-surprises</link><pubDate>Tue, 25 Jul 2017 00:00:00 +0000</pubDate><content:encoded><![CDATA[<p>If you have dove straight into <a href="https://www.fivestars.blog/articles/swift-decodable/">Swift 4’s <code>Codable</code> protocol</a> as early as Xcode 9 Beta 1, while firing up one of the latest betas (<a href="https://twitter.com/zntfdr/status/885881044896841728">3 or later</a>) you might have seen a warning similar to this:</p><img src="https://www.fivestars.blog/assets/posts/xcode-beta-surprises/warning.png" alt="warning"/><blockquote><p>Surprise!</p></blockquote><p>The <a href="https://developer.apple.com/documentation/coregraphics">Core Graphics Framework</a> is not open source <a href="http://github.com/apple/swift">like Swift</a>, so it wasn’t something that we could have seen coming. Still, this is great news!</p><h2>Geometric Data Types Codable Conformance</h2><p>It turns out that <a href="https://developer.apple.com/documentation/coregraphics?changes=latest_minor#2008762">every single <code>CoreGraphics</code> Geometric Data Type</a> now conforms to <code>Codable</code>, namely:</p><ul><li><code>CGFloat</code></li><li><code>CGPoint</code></li><li><code>CGSize</code></li><li><code>CGRect</code></li><li><code>CGVector</code></li><li><code>CGAffineTransform</code></li></ul><p>Awesome! How do we take advantage of this?</p><h2>Following the new Conformance</h2><p>If we store <code>CGPoints</code>/<code>CGSizes</code>/etc somewhere, we must now make sure to do so in the same way as the new <code>Codable</code> conformance expect them to.</p><p>Let’s find out how!</p><h3>Try 1: Source Code</h3><p>The easiest way would be to look at the source code, if we go to the <code>CGPoint</code> definition for example, this is what we’ll see:</p><img src="https://www.fivestars.blog/assets/posts/xcode-beta-surprises/closed-source.png" alt="closed-source"/><blockquote><p>Closed Source won’t show us any useful information</p></blockquote><p>Again, the Core Graphics Framework is not open source, so we get to see only the headers and the definitions of the public functions. This won’t help us understand how things work behind the scene, let’s try another way…</p><p>Try 2: Apple’s Documentation My second best guess was <a href="https://developer.apple.com/documentation/coregraphics/cgpoint/2919705-init?changes=latest_minor">Apple’s Documentation</a>, boy was it disappointing:</p><img src="https://www.fivestars.blog/assets/posts/xcode-beta-surprises/apple-doc.png" alt="apple-doc"/><blockquote><p>No overview available 🤣</p></blockquote><h4>Try 3: The Hard Way (Playgrounds!)</h4><p>Tired of guessing new ways to uncover the mystery, I’ve decided to do things the easy way. I’ve fired up a new playground and start coding:</p><img src="https://www.fivestars.blog/assets/posts/xcode-beta-surprises/playground.png" alt="playground"/><blockquote><p>Can’t go wrong with Playgrounds! (code <a href="https://gist.github.com/zntfdr/62bcaf71a533ad49b70b1cfc3b0eac82">here</a>)</p></blockquote><p>So there you have it! 🎉🎉🎉</p><h2>The Undocumented CoreGraphics Conformance</h2><p>To make a quick recap, this is how you store/read the data (as seen in the Playground above):</p><ul><li><code>GFloat</code> Expect just a number, with an optional decimal point <code>.</code> to split the integer part from the fractional part (if there’s a fractional part)</li></ul><ul><li><code>CGPoint</code> Expect an array of two <code>CGFloats</code>, the first represents the <code>x</code>, the second the <code>y</code></li></ul><ul><li><code>CGSize</code> Expect an array of two <code>CGFloats</code>, the first represents the <code>width</code>, the second the <code>height</code></li></ul><ul><li><code>CGVector</code> Expect an array of two <code>CGFloats</code>, the first represents the <code>dx</code>, the second the <code>dy</code></li></ul><ul><li><code>CGRect</code> Expect an array of two arrays of two <code>CGFloat</code>, the first represents the origin (<code>CGPoint</code>), the second array the size (<code>CGSize</code>)</li></ul><ul><li><code>CGAffineTransform</code> Expect an array of six <code>CGFloat</code>s, representing (in order) <code>a</code>, <code>b</code>, <code>c</code>, <code>d</code>, <code>tx</code>, and <code>ty</code></li></ul><h2>Conclusions</h2><p>It’s great that Apple has decided to start conforming its Frameworks Data Objects to <code>Codable</code>, I’m looking forward to more and more Apple’s Framework jump on the <code>Codable</code> wagon! (please do <code>Core Location</code> next?)</p><h2>Update: Source Code</h2><blockquote><p>(2017–08–01) While my mission is now complete, <a href="https://medium.com/@londeix">Bartosz Polaczyk</a> <a href="https://medium.com/@londeix/there-is-however-a-way-to-look-into-source-code-try-1-6071081adc49">has pointed out</a> that there’s an actual way to see the <code>Codable</code> conformance source code 🎉</p></blockquote><p>It turns out that, if we look at the Swift source code, we have the complete CoreGraphics <code>Codable</code> conformance <a href="https://github.com/apple/swift/blob/391d49a31e5117585d05a2d003047698142fd1a6/stdlib/public/SDK/CoreGraphics/CoreGraphics.swift">here</a>!</p><p>Everything written above is still 100% true and perfectly valid, but it’s awesome to have the actual code: thank you <a href="https://medium.com/@londeix">Bartosz Polaczyk</a> 🙌</p>]]></content:encoded></item></channel></rss>