<?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>Zac White</title>
    <link>https://zacwhite.com</link>
    <description>Code, Snippets, Tips &amp; Tricks</description>
    <language>en-us</language>
    <lastBuildDate>Thu, 26 Feb 2026 06:13:33 GMT</lastBuildDate>
    <atom:link href="https://zacwhite.com/feed.xml" rel="self" type="application/rss+xml"/>
    
    <item>
      <title><![CDATA[Ideal Cross-Platform Return Key Behavior with SwiftUI's TextField]]></title>
      <link>https://zacwhite.com/2024/textfield-return-key-behavior/</link>
      <guid isPermaLink="true">https://zacwhite.com/2024/textfield-return-key-behavior/</guid>
      <pubDate>Thu, 06 Jun 2024 19:00:00 GMT</pubDate>
      <description><![CDATA[Getting ideal return key behavior for a multi-line TextField for chat message input in SwiftUI on macOS Catalyst]]></description>
      <content:encoded><![CDATA[<p>I've recently been working on a cross-platform chat application for macOS and iOS written in SwiftUI. One of the challenges I ran into was getting the return key to behave as expected on all platforms in a multi-line <code>TextField</code>. The behavior I wanted was for the return key (↩) to send the message and for option return (⌥↩) to insert a newline. This is the behavior of a chat application like iMessage, and it's what I wanted to replicate.</p>
<p>I started with something like this:</p>
<pre class="shiki github-dark" style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="swift" data-theme="github-dark"><code data-line-count="13" class="language-swift"><span class="line" data-line="1"><span style="color:#F97583">struct</span><span style="color:#B392F0"> EntryField</span><span style="color:#E1E4E8">: </span><span style="color:#B392F0">View </span><span style="color:#E1E4E8">{</span></span>
<span class="line" data-line="2"><span style="color:#F97583">    let</span><span style="color:#E1E4E8"> titleKey: LocalizedStringKey</span></span>
<span class="line" data-line="3"><span style="color:#F97583">    @Binding</span><span style="color:#F97583"> var</span><span style="color:#E1E4E8"> text: </span><span style="color:#79B8FF">String</span></span>
<span class="line" data-line="4"></span>
<span class="line" data-line="5"><span style="color:#F97583">    let</span><span style="color:#E1E4E8"> onSend: (</span><span style="color:#79B8FF">String</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">-></span><span style="color:#79B8FF"> Void</span></span>
<span class="line" data-line="6"></span>
<span class="line" data-line="7"><span style="color:#F97583">    var</span><span style="color:#E1E4E8"> body: </span><span style="color:#F97583">some</span><span style="color:#E1E4E8"> View {</span></span>
<span class="line" data-line="8"><span style="color:#79B8FF">        TextField</span><span style="color:#E1E4E8">(titleKey, </span><span style="color:#79B8FF">text</span><span style="color:#E1E4E8">: _text, </span><span style="color:#79B8FF">axis</span><span style="color:#E1E4E8">: .vertical)</span></span>
<span class="line" data-line="9"><span style="color:#E1E4E8">            .</span><span style="color:#79B8FF">onSubmit</span><span style="color:#E1E4E8"> {</span></span>
<span class="line" data-line="10"><span style="color:#79B8FF">                onSend</span><span style="color:#E1E4E8">(text)</span></span>
<span class="line" data-line="11"><span style="color:#E1E4E8">            }</span></span>
<span class="line" data-line="12"><span style="color:#E1E4E8">    }</span></span>
<span class="line" data-line="13"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>If using an AppKit macOS application, you get the desired return key behavior for free. You also get it with iOS / iPadOS and a connected hardware keyboard. However, when using a Catalyst macOS app target (at least on Sonoma), the return key always inserts a return character and never triggers the <code>onSubmit</code> closure. 🤔</p>
<p>So as a workaround, you can add an invisible <code>Button</code> that responds to the return and performs the passed in closure.</p>
<pre class="shiki github-dark" style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="swift" data-theme="github-dark"><code data-line-count="13" class="language-swift"><span class="line" data-line="1"><span style="color:#79B8FF">TextField</span><span style="color:#E1E4E8">(titleKey, </span><span style="color:#79B8FF">text</span><span style="color:#E1E4E8">: _text, </span><span style="color:#79B8FF">axis</span><span style="color:#E1E4E8">: .vertical)</span></span>
<span class="line" data-line="2"><span style="color:#E1E4E8">    .</span><span style="color:#79B8FF">onSubmit</span><span style="color:#E1E4E8"> {</span></span>
<span class="line" data-line="3"><span style="color:#79B8FF">        onSend</span><span style="color:#E1E4E8">(text)</span></span>
<span class="line" data-line="4"><span style="color:#E1E4E8">    }</span></span>
<span class="line" data-line="5"><span style="color:#E1E4E8">    .</span><span style="color:#79B8FF">background</span><span style="color:#E1E4E8"> {</span></span>
<span class="line" data-line="6"><span style="color:#79B8FF">        Button</span><span style="color:#E1E4E8"> {</span></span>
<span class="line" data-line="7"><span style="color:#79B8FF">            onSend</span><span style="color:#E1E4E8">(text)</span></span>
<span class="line" data-line="8"><span style="color:#E1E4E8">        } </span><span style="color:#79B8FF">label</span><span style="color:#E1E4E8">: {</span></span>
<span class="line" data-line="9"><span style="color:#79B8FF">            EmptyView</span><span style="color:#E1E4E8">()</span></span>
<span class="line" data-line="10"><span style="color:#E1E4E8">        }</span></span>
<span class="line" data-line="11"><span style="color:#E1E4E8">        .</span><span style="color:#79B8FF">keyboardShortcut</span><span style="color:#E1E4E8">(.defaultAction)</span></span>
<span class="line" data-line="12"><span style="color:#E1E4E8">        .</span><span style="color:#79B8FF">opacity</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">0</span><span style="color:#E1E4E8">)</span></span>
<span class="line" data-line="13"><span style="color:#E1E4E8">    }</span></span></code></pre>
<p>Now the field behaves as expected on all platforms. The return key sends the message, and option return inserts a newline. This is a simple solution (hack?) that works well for my little app, but if you have a better solution I'd love to hear it!</p>]]></content:encoded>
      <author>Zac White</author>
    </item>
    <item>
      <title><![CDATA[Detecting SwiftUI Previews]]></title>
      <link>https://zacwhite.com/2020/detecting-swiftui-previews/</link>
      <guid isPermaLink="true">https://zacwhite.com/2020/detecting-swiftui-previews/</guid>
      <pubDate>Wed, 02 Sep 2020 15:00:00 GMT</pubDate>
      <description><![CDATA[How to figure out if your View is being displayed in a SwiftUI preview]]></description>
      <content:encoded><![CDATA[<p>It shouldn't be often, but sometimes you need to know within a View or some code that you are running within the SwiftUI preview system. There isn't a built-in way to do this, so you have to infer this yourself. It's not too terribly difficult though, since Apple includes some process environment values that can clue you into the exact context in which your view is running.</p>
<p>To start, declare an <code>EnvironmentValues</code> extension which includes a read-only <code>isPreview</code> value. This peeks into the current process environment and looks for <code>XCODE_RUNNING_FOR_PREVIEWS</code>, which is set to "1" if Xcode is running your preview. As an optimization, you can optionally compile this code out if the project isn't in the <code>DEBUG</code> configuration. This saves checking the process info for a value that definitely isn't there.</p>
<pre class="shiki github-dark" style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="swift" data-theme="github-dark"><code data-line-count="9" class="language-swift"><span class="line" data-line="1"><span style="color:#F97583">public</span><span style="color:#F97583"> extension</span><span style="color:#B392F0"> EnvironmentValues</span><span style="color:#E1E4E8"> {</span></span>
<span class="line" data-line="2"><span style="color:#F97583">    var</span><span style="color:#E1E4E8"> isPreview: </span><span style="color:#79B8FF">Bool</span><span style="color:#E1E4E8"> {</span></span>
<span class="line" data-line="3"><span style="color:#E1E4E8">        #</span><span style="color:#F97583">if</span><span style="color:#E1E4E8"> DEBUG</span></span>
<span class="line" data-line="4"><span style="color:#F97583">        return</span><span style="color:#E1E4E8"> ProcessInfo.processInfo.environment[</span><span style="color:#9ECBFF">"XCODE_RUNNING_FOR_PREVIEWS"</span><span style="color:#E1E4E8">] </span><span style="color:#F97583">==</span><span style="color:#9ECBFF"> "1"</span></span>
<span class="line" data-line="5"><span style="color:#E1E4E8">        #</span><span style="color:#F97583">else</span></span>
<span class="line" data-line="6"><span style="color:#F97583">        return</span><span style="color:#79B8FF"> false</span></span>
<span class="line" data-line="7"><span style="color:#E1E4E8">        #</span><span style="color:#F97583">endif</span></span>
<span class="line" data-line="8"><span style="color:#E1E4E8">    }</span></span>
<span class="line" data-line="9"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>Then usage is pretty straightforward. Declare your <code>@Environment</code> var and use your new-found Bool to alter your view based on it being in preview mode.</p>
<pre class="shiki github-dark" style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="swift" data-theme="github-dark"><code data-line-count="13" class="language-swift"><span class="line" data-line="1"><span style="color:#F97583">struct</span><span style="color:#B392F0"> MyView</span><span style="color:#E1E4E8">: </span><span style="color:#B392F0">View </span><span style="color:#E1E4E8">{</span></span>
<span class="line" data-line="2"><span style="color:#F97583">    @Environment</span><span style="color:#E1E4E8">(\.isPreview) </span><span style="color:#F97583">var</span><span style="color:#E1E4E8"> isPreview</span></span>
<span class="line" data-line="3"></span>
<span class="line" data-line="4"><span style="color:#F97583">    var</span><span style="color:#E1E4E8"> body: </span><span style="color:#F97583">some</span><span style="color:#E1E4E8"> View {</span></span>
<span class="line" data-line="5"><span style="color:#79B8FF">        Group</span><span style="color:#E1E4E8"> {</span></span>
<span class="line" data-line="6"><span style="color:#F97583">            if</span><span style="color:#E1E4E8"> isPreview {</span></span>
<span class="line" data-line="7"><span style="color:#79B8FF">                Text</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"This is a preview!"</span><span style="color:#E1E4E8">)</span></span>
<span class="line" data-line="8"><span style="color:#E1E4E8">            } </span><span style="color:#F97583">else</span><span style="color:#E1E4E8"> {</span></span>
<span class="line" data-line="9"><span style="color:#79B8FF">                Text</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"This is not a preview!"</span><span style="color:#E1E4E8">)</span></span>
<span class="line" data-line="10"><span style="color:#E1E4E8">            }</span></span>
<span class="line" data-line="11"><span style="color:#E1E4E8">        }</span></span>
<span class="line" data-line="12"><span style="color:#E1E4E8">    }</span></span>
<span class="line" data-line="13"><span style="color:#E1E4E8">}</span></span></code></pre>
<p><del>Bonus tip: In figuring out what environment is available in a preview, I stumbled upon this <a href="https://web.archive.org/web/20210923151359/https://developer.apple.com/news/?id=8vkqn3ih">super useful trick</a> that I somehow missed when it was shared. It's not often you should need to debug something happening in a preview, but now you can!</del></p>
<p><strong>Edit 8/16/23:</strong> Unfortunately the above referenced ability to debug SwiftUI previews was <a href="https://developer.apple.com/forums/thread/683773?answerId=680833022#680833022">taken out in Xcode 13</a>. Oh well! You can at least see logs for previews, so attaching the debugger isn't as necessary as it used to be.</p>]]></content:encoded>
      <author>Zac White</author>
    </item>
    <item>
      <title><![CDATA[ScrollView Content Offsets with SwiftUI]]></title>
      <link>https://zacwhite.com/2019/scrollview-content-offsets-swiftui/</link>
      <guid isPermaLink="true">https://zacwhite.com/2019/scrollview-content-offsets-swiftui/</guid>
      <pubDate>Mon, 30 Sep 2019 21:00:00 GMT</pubDate>
      <description><![CDATA[How to get a content offset from a ScrollView in SwiftUI]]></description>
      <content:encoded><![CDATA[<p>Scroll views are an incredibly important view type for displaying content on all the Apple platforms. Unfortunately, <code>ScrollView</code> in SwiftUI has some pretty glaring and difficult to work around limitations. Some of the pretty important features lacking out of the box are:</p>
<ol>
<li>Offset change callback (via <code>UIScrollViewDelegate.scrollViewDidScroll(_ scrollView: UIScrollView)</code> in UIKit)</li>
<li>Scroll end point control for pagination (via <code>UIScrollViewDelegate.scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer&#x3C;CGPoint>)</code> in UIKit)</li>
<li>Animation / setting of offset (via <code>UIScrollView.setContentOffset(_ contentOffset: CGPoint, animated: Bool)</code> in UIKit)</li>
</ol>
<p>Luckily, for #1 we can use <code>GeometryReader</code> and our own container view to achieve the same view containment behavior as <code>ScrollView</code>, but add a <code>Binder&#x3C;CGPoint></code> which can give us the offset.</p>
<p>To begin, we start with all the same API available to <code>ScrollView</code>:</p>
<pre class="shiki github-dark" style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="swift" data-theme="github-dark"><code data-line-count="18" class="language-swift"><span class="line" data-line="1"><span style="color:#F97583">public</span><span style="color:#F97583"> struct</span><span style="color:#B392F0"> OffsetScrollView</span><span style="color:#E1E4E8">&#x3C;</span><span style="color:#79B8FF">Content</span><span style="color:#E1E4E8">>: </span><span style="color:#B392F0">View </span><span style="color:#F97583">where</span><span style="color:#E1E4E8"> Content </span><span style="color:#F97583">:</span><span style="color:#B392F0"> View</span><span style="color:#E1E4E8"> {</span></span>
<span class="line" data-line="2"></span>
<span class="line" data-line="3"><span style="color:#6A737D">    /// The content of the scroll view.</span></span>
<span class="line" data-line="4"><span style="color:#F97583">    public</span><span style="color:#F97583"> var</span><span style="color:#E1E4E8"> content: Content</span></span>
<span class="line" data-line="5"></span>
<span class="line" data-line="6"><span style="color:#6A737D">    /// The scrollable axes.</span></span>
<span class="line" data-line="7"><span style="color:#6A737D">    ///</span></span>
<span class="line" data-line="8"><span style="color:#6A737D">    /// The default is `.vertical`.</span></span>
<span class="line" data-line="9"><span style="color:#F97583">    public</span><span style="color:#F97583"> var</span><span style="color:#E1E4E8"> axes: Axis.</span><span style="color:#79B8FF">Set</span></span>
<span class="line" data-line="10"></span>
<span class="line" data-line="11"><span style="color:#6A737D">    /// If true, the scroll view may indicate the scrollable component of</span></span>
<span class="line" data-line="12"><span style="color:#6A737D">    /// the content offset, in a way suitable for the platform.</span></span>
<span class="line" data-line="13"><span style="color:#6A737D">    ///</span></span>
<span class="line" data-line="14"><span style="color:#6A737D">    /// The default is `true`.</span></span>
<span class="line" data-line="15"><span style="color:#F97583">    public</span><span style="color:#F97583"> var</span><span style="color:#E1E4E8"> showsIndicators: </span><span style="color:#79B8FF">Bool</span></span>
<span class="line" data-line="16"></span>
<span class="line" data-line="17"><span style="color:#F97583">    ...</span></span>
<span class="line" data-line="18"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>But we want to add a <code>Binder&#x3C;CGPoint></code> to the mix and an initializer that initializes all our properties and a private <code>@State</code> to hold the initial offset:</p>
<pre class="shiki github-dark" style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="swift" data-theme="github-dark"><code data-line-count="12" class="language-swift"><span class="line" data-line="1"><span style="color:#6A737D">/// The initial offset of the view as measured in the global frame</span></span>
<span class="line" data-line="2"><span style="color:#F97583">@State</span><span style="color:#F97583"> private</span><span style="color:#F97583"> var</span><span style="color:#E1E4E8"> initialOffset: CGPoint</span><span style="color:#F97583">?</span></span>
<span class="line" data-line="3"></span>
<span class="line" data-line="4"><span style="color:#6A737D">/// The offset of the scroll view updated as the scroll view scrolls</span></span>
<span class="line" data-line="5"><span style="color:#F97583">@Binding</span><span style="color:#F97583"> public</span><span style="color:#F97583"> var</span><span style="color:#E1E4E8"> offset: CGPoint</span></span>
<span class="line" data-line="6"></span>
<span class="line" data-line="7"><span style="color:#F97583">public</span><span style="color:#F97583"> init</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">_</span><span style="color:#E1E4E8"> axes: Axis.</span><span style="color:#79B8FF">Set</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> .vertical, </span><span style="color:#B392F0">showsIndicators</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">Bool</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> true</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">offset</span><span style="color:#E1E4E8">: Binding&#x3C;CGPoint> </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> .</span><span style="color:#79B8FF">constant</span><span style="color:#E1E4E8">(.zero), @</span><span style="color:#B392F0">ViewBuilder</span><span style="color:#E1E4E8"> content: () </span><span style="color:#F97583">-></span><span style="color:#E1E4E8"> Content) {</span></span>
<span class="line" data-line="8"><span style="color:#79B8FF">    self</span><span style="color:#E1E4E8">.axes </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> axes</span></span>
<span class="line" data-line="9"><span style="color:#79B8FF">    self</span><span style="color:#E1E4E8">.showsIndicators </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> showsIndicators</span></span>
<span class="line" data-line="10"><span style="color:#79B8FF">    self</span><span style="color:#E1E4E8">._offset </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> offset</span></span>
<span class="line" data-line="11"><span style="color:#79B8FF">    self</span><span style="color:#E1E4E8">.content </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> content</span><span style="color:#E1E4E8">()</span></span>
<span class="line" data-line="12"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>Now in the <code>body</code> property, we'll want to return a normal <code>ScrollView</code>, but add our own view above the provided <code>content</code> that can utilize <code>GeometryReader</code> to get a callback for position. By passing the frame origin of this empty, 0x0 view into our <code>Binder&#x3C;CGPoint></code> we can get a callback for geometry changes that will allow us to update the binding and subsequently any listeners to the binding:</p>
<pre class="shiki github-dark" style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="swift" data-theme="github-dark"><code data-line-count="17" class="language-swift"><span class="line" data-line="1"><span style="color:#F97583">public</span><span style="color:#F97583"> var</span><span style="color:#E1E4E8"> body: </span><span style="color:#F97583">some</span><span style="color:#E1E4E8"> View {</span></span>
<span class="line" data-line="2"><span style="color:#79B8FF">    ScrollView</span><span style="color:#E1E4E8">(axes, </span><span style="color:#79B8FF">showsIndicators</span><span style="color:#E1E4E8">: showsIndicators) {</span></span>
<span class="line" data-line="3"><span style="color:#79B8FF">        VStack</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">alignment</span><span style="color:#E1E4E8">: .leading, </span><span style="color:#79B8FF">spacing</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">0</span><span style="color:#E1E4E8">) {</span></span>
<span class="line" data-line="4"><span style="color:#79B8FF">            GeometryReader</span><span style="color:#E1E4E8"> { geometry </span><span style="color:#F97583">in</span></span>
<span class="line" data-line="5"><span style="color:#79B8FF">                Run</span><span style="color:#E1E4E8"> {</span></span>
<span class="line" data-line="6"><span style="color:#F97583">                    let</span><span style="color:#E1E4E8"> globalOrigin </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> geometry.</span><span style="color:#79B8FF">frame</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">in</span><span style="color:#E1E4E8">: .global).origin</span></span>
<span class="line" data-line="7"><span style="color:#79B8FF">                    self</span><span style="color:#E1E4E8">.initialOffset </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8">.initialOffset </span><span style="color:#F97583">??</span><span style="color:#E1E4E8"> globalOrigin</span></span>
<span class="line" data-line="8"><span style="color:#F97583">                    let</span><span style="color:#E1E4E8"> initialOffset </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> (</span><span style="color:#79B8FF">self</span><span style="color:#E1E4E8">.initialOffset </span><span style="color:#F97583">??</span><span style="color:#E1E4E8"> .zero)</span></span>
<span class="line" data-line="9"><span style="color:#F97583">                    let</span><span style="color:#E1E4E8"> offset </span><span style="color:#F97583">=</span><span style="color:#79B8FF"> CGPoint</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">x</span><span style="color:#E1E4E8">: globalOrigin.x </span><span style="color:#F97583">-</span><span style="color:#E1E4E8"> initialOffset.x, </span><span style="color:#79B8FF">y</span><span style="color:#E1E4E8">: globalOrigin.y </span><span style="color:#F97583">-</span><span style="color:#E1E4E8"> initialOffset.y)</span></span>
<span class="line" data-line="10"><span style="color:#79B8FF">                    self</span><span style="color:#E1E4E8">.offset.wrappedValue </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> offset</span></span>
<span class="line" data-line="11"><span style="color:#E1E4E8">                }</span></span>
<span class="line" data-line="12"><span style="color:#E1E4E8">            }.</span><span style="color:#79B8FF">frame</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">width</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">0</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">height</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">0</span><span style="color:#E1E4E8">)</span></span>
<span class="line" data-line="13"></span>
<span class="line" data-line="14"><span style="color:#E1E4E8">            content</span></span>
<span class="line" data-line="15"><span style="color:#E1E4E8">        }</span></span>
<span class="line" data-line="16"><span style="color:#E1E4E8">    }</span></span>
<span class="line" data-line="17"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>You'll notice a <code>Run</code> 'view' which is pretty simple and lets us just execute a bit of code without affecting the layout:</p>
<pre class="shiki github-dark" style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="swift" data-theme="github-dark"><code data-line-count="8" class="language-swift"><span class="line" data-line="1"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Run</span><span style="color:#E1E4E8">: </span><span style="color:#B392F0">View </span><span style="color:#E1E4E8">{</span></span>
<span class="line" data-line="2"><span style="color:#F97583">    let</span><span style="color:#E1E4E8"> block: () </span><span style="color:#F97583">-></span><span style="color:#79B8FF"> Void</span></span>
<span class="line" data-line="3"></span>
<span class="line" data-line="4"><span style="color:#F97583">    var</span><span style="color:#E1E4E8"> body: </span><span style="color:#F97583">some</span><span style="color:#E1E4E8"> View {</span></span>
<span class="line" data-line="5"><span style="color:#E1E4E8">        DispatchQueue.main.</span><span style="color:#79B8FF">async</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">execute</span><span style="color:#E1E4E8">: block)</span></span>
<span class="line" data-line="6"><span style="color:#F97583">        return</span><span style="color:#79B8FF"> AnyView</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">EmptyView</span><span style="color:#E1E4E8">())</span></span>
<span class="line" data-line="7"><span style="color:#E1E4E8">    }</span></span>
<span class="line" data-line="8"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>Putting it all together, we now have a way to access the offset of the <code>ScrollView</code> and update a <code>@State</code> wrapped property. This can be used within other Views in our hierarchy to achieve some interesting effects.</p>
<p>In this watchOS example, we can use it to achieve an effect similar to the built-in News app where as you scroll the content, a header recedes by changing opacity and position based on the scroll offset.</p>
<p>This kind of behavior wasn't possible on previous versions of watchOS, so this serves as a perfect example of why watchOS + SwiftUI is an awesome combo! 🎉</p>
<video height="auto" controls="controls" preload="auto" onclick="this.play()">
  <source src="/assets/images/corgi-news-watch.mp4" type="video/mp4">
</video>
<hr />
<p>If you'd like to mess around with the watchOS project demonstrating this, <a href="/files/Headlines.zip">here's the zip</a></p>
<h3><em>Update 10/28/19</em></h3>
<p>Thanks to <a href="https://twitter.com/dmcgloin">@dmcgloin</a> on Twitter for pointing out that Xcode 11.2 beta 2 was throwing a helpful warning that the original code was "Modifying state during view update, this will cause undefined behavior." One workaround was to kick the closure inside the <code>Run</code> view to run asynchronously. The above inline code and the sample zip have been updated with this workaround.</p>]]></content:encoded>
      <author>Zac White</author>
    </item>
    <item>
      <title><![CDATA[Templates in Swift with ExpressibleByStringInterpolation]]></title>
      <link>https://zacwhite.com/2019/template-swift/</link>
      <guid isPermaLink="true">https://zacwhite.com/2019/template-swift/</guid>
      <pubDate>Mon, 13 May 2019 21:00:00 GMT</pubDate>
      <description><![CDATA[Using ExpressibleByStringInterpolation to build better type-safe templates]]></description>
      <content:encoded><![CDATA[<p>In Swift, we're always striving for type-safety. The less we can 'stringly' type the better. When it comes to situations where we need to define a string that inserts various elements, the canonical example being a URL path, there have been plenty of approaches in the past. In Objective-C, you might see something like this:</p>
<pre class="shiki github-dark" style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="objc" data-theme="github-dark"><code data-line-count="8" class="language-objc"><span class="line" data-line="1"><span style="color:#E1E4E8">Template </span><span style="color:#F97583">*</span><span style="color:#E1E4E8">template </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> [[Template </span><span style="color:#79B8FF">alloc</span><span style="color:#E1E4E8">] </span><span style="color:#79B8FF">initWithTemplateString:</span><span style="color:#9ECBFF">@"/users/:username:/:id:"</span><span style="color:#E1E4E8">];</span></span>
<span class="line" data-line="2"></span>
<span class="line" data-line="3"><span style="color:#79B8FF">NSString</span><span style="color:#F97583"> *</span><span style="color:#E1E4E8">fullString </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> [template </span><span style="color:#79B8FF">fill:</span><span style="color:#E1E4E8">@{</span></span>
<span class="line" data-line="4"><span style="color:#9ECBFF">    @"usernane"</span><span style="color:#E1E4E8"> : </span><span style="color:#9ECBFF">@"zac"</span><span style="color:#E1E4E8">,</span></span>
<span class="line" data-line="5"><span style="color:#9ECBFF">    @"id"</span><span style="color:#E1E4E8"> : @(</span><span style="color:#79B8FF">42</span><span style="color:#E1E4E8">)</span></span>
<span class="line" data-line="6"><span style="color:#E1E4E8">}];</span></span>
<span class="line" data-line="7"></span>
<span class="line" data-line="8"><span style="color:#6A737D">// now fullString should be: "/users/zac/42"</span></span></code></pre>
<p>What's wrong with that? Well, first of all the identifiers in the template string have to match the strings in the dictionary. If one is wrong, then the other won't be inserted and you'll get a runtime issue. In fact, I snuck in "usernane" above instead of "username" and I bet you didn't notice!</p>
<p>The other issue that can come up is what kinds of values are supported? If you are a particularly lazy developer, you might define the parameters of the fill method as being: <code>NSDictionary&#x3C;NSString *, id></code>. And if someone passes in an unsupported type, just ignore it I guess?</p>
<h3>We can do better with Swift.</h3>
<p>What if we utilize <a href="https://developer.apple.com/documentation/swift/expressiblebystringinterpolation"><code>ExpressibleByStringInterpolation</code></a> and <a href="https://developer.apple.com/documentation/swift/keypath">key paths</a> to create something that doesn't have the above issues?</p>
<pre class="shiki github-dark" style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="swift" data-theme="github-dark"><code data-line-count="7" class="language-swift"><span class="line" data-line="1"><span style="color:#F97583">struct</span><span style="color:#B392F0"> Values</span><span style="color:#E1E4E8"> {</span></span>
<span class="line" data-line="2"><span style="color:#F97583">  let</span><span style="color:#E1E4E8"> username: </span><span style="color:#79B8FF">String</span></span>
<span class="line" data-line="3"><span style="color:#F97583">  let</span><span style="color:#E1E4E8"> id: </span><span style="color:#79B8FF">Int</span></span>
<span class="line" data-line="4"><span style="color:#E1E4E8">}</span></span>
<span class="line" data-line="5"></span>
<span class="line" data-line="6"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> template: Template&#x3C;Values> </span><span style="color:#F97583">=</span><span style="color:#9ECBFF"> "/users/</span><span style="color:#9ECBFF">\(keyPath</span><span style="color:#F97583">:</span><span style="color:#9ECBFF"> \.</span><span style="color:#E1E4E8">username</span><span style="color:#9ECBFF">)</span><span style="color:#9ECBFF">/</span><span style="color:#9ECBFF">\(keyPath</span><span style="color:#F97583">:</span><span style="color:#9ECBFF"> \.</span><span style="color:#E1E4E8">id</span><span style="color:#9ECBFF">)</span><span style="color:#9ECBFF">"</span></span>
<span class="line" data-line="7"><span style="color:#F97583">let</span><span style="color:#E1E4E8"> fullString </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> template.</span><span style="color:#79B8FF">fill</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">with</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">Values</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">username</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"zac"</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">id</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">42</span><span style="color:#E1E4E8">))</span></span></code></pre>
<p>So now we have something that shows exactly what value should go where in the string. And more importantly, those values are strongly typed to be something that can definitely be inserted into the template. If I mistype "usernane" again, I'll get a compiler error.</p>
<p>There are a couple down-sides though:</p>
<ol>
<li>It requires a definition of a type to 'contain' the inserted values so the key paths have something to reference.</li>
<li>Because the interpolation syntax in Swift uses <code>\()</code> and the key paths use <code>\.</code> there are a lot of backslashes. Couple that with our current use-case of a path which has several forward slashes and visually parsing the string can be difficult.</li>
</ol>
<p>(1) doesn't bother me too much, but (2) makes the string a little tough to read. I'm not sure what would be better, but another approach is to use overloading to use "+" as a concatenation operator. So you'd end up with something like this: <code>"/users/" + \.username + "/" + \.id</code>. Is this better? 🤷‍♂️</p>
<h3>How to build it?</h3>
<p>We first need to control the type being passed to us to fill values in the template, but in a way that allows developers to extend support for custom types in the future. We can accomplish this with a protocol:</p>
<pre class="shiki github-dark" style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="swift" data-theme="github-dark"><code data-line-count="11" class="language-swift"><span class="line" data-line="1"><span style="color:#F97583">public</span><span style="color:#F97583"> protocol</span><span style="color:#B392F0"> TemplateInsertable</span><span style="color:#E1E4E8"> {</span></span>
<span class="line" data-line="2"><span style="color:#F97583">    var</span><span style="color:#E1E4E8"> stringValue: </span><span style="color:#79B8FF">String</span><span style="color:#E1E4E8"> { </span><span style="color:#F97583">get</span><span style="color:#E1E4E8"> }</span></span>
<span class="line" data-line="3"><span style="color:#E1E4E8">}</span></span>
<span class="line" data-line="4"></span>
<span class="line" data-line="5"><span style="color:#F97583">extension</span><span style="color:#79B8FF"> String</span><span style="color:#E1E4E8">: </span><span style="color:#B392F0">TemplateInsertable </span><span style="color:#E1E4E8">{</span></span>
<span class="line" data-line="6"><span style="color:#F97583">    public</span><span style="color:#F97583"> var</span><span style="color:#E1E4E8"> stringValue: </span><span style="color:#79B8FF">String</span><span style="color:#E1E4E8"> { </span><span style="color:#F97583">return</span><span style="color:#79B8FF"> self</span><span style="color:#E1E4E8"> }</span></span>
<span class="line" data-line="7"><span style="color:#E1E4E8">}</span></span>
<span class="line" data-line="8"></span>
<span class="line" data-line="9"><span style="color:#F97583">extension</span><span style="color:#79B8FF"> Int</span><span style="color:#E1E4E8">: </span><span style="color:#B392F0">TemplateInsertable </span><span style="color:#E1E4E8">{</span></span>
<span class="line" data-line="10"><span style="color:#F97583">    public</span><span style="color:#F97583"> var</span><span style="color:#E1E4E8"> stringValue: </span><span style="color:#79B8FF">String</span><span style="color:#E1E4E8"> { </span><span style="color:#F97583">return</span><span style="color:#9ECBFF"> "</span><span style="color:#9ECBFF">\(</span><span style="color:#79B8FF">self</span><span style="color:#9ECBFF">)</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8"> }</span></span>
<span class="line" data-line="11"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>Next we'll need the <code>Template</code> type which can handle 'appending' <code>TemplateInsertable</code>s or can append key paths to <code>TemplateInsertable</code>s. We have to store these components in a way that allows us to go through and construct the final template string with different passed in values. Below is one way to accomplish that where we keep a string internally of all the strings that have been appended to the template. And when a key path is appended, the future index in that string where the value will be inserted is stored along with the key path itself.</p>
<p>This lets us enumerate through those key paths and insert the string values into the positions which were defined in the interpolation.</p>
<pre class="shiki github-dark" style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="swift" data-theme="github-dark"><code data-line-count="26" class="language-swift"><span class="line" data-line="1"><span style="color:#F97583">public</span><span style="color:#F97583"> struct</span><span style="color:#B392F0"> Template</span><span style="color:#E1E4E8">&#x3C;</span><span style="color:#79B8FF">T</span><span style="color:#E1E4E8">> {</span></span>
<span class="line" data-line="2"></span>
<span class="line" data-line="3"><span style="color:#F97583">    private</span><span style="color:#F97583"> var</span><span style="color:#E1E4E8"> template: </span><span style="color:#79B8FF">String</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> ""</span></span>
<span class="line" data-line="4"><span style="color:#F97583">    private</span><span style="color:#F97583"> var</span><span style="color:#E1E4E8"> keyPaths: [(offset: </span><span style="color:#79B8FF">String</span><span style="color:#E1E4E8">.</span><span style="color:#79B8FF">IndexDistance</span><span style="color:#E1E4E8">, keyPath: PartialKeyPath&#x3C;T>)] </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> []</span></span>
<span class="line" data-line="5"></span>
<span class="line" data-line="6"><span style="color:#F97583">    mutating</span><span style="color:#F97583"> func</span><span style="color:#B392F0"> append</span><span style="color:#E1E4E8">&#x3C;</span><span style="color:#79B8FF">U</span><span style="color:#E1E4E8">: </span><span style="color:#B392F0">TemplateInsertable</span><span style="color:#E1E4E8">>(</span><span style="color:#B392F0">keyPath</span><span style="color:#E1E4E8">: KeyPath&#x3C;T, U>) {</span></span>
<span class="line" data-line="7"><span style="color:#E1E4E8">        keyPaths.</span><span style="color:#79B8FF">append</span><span style="color:#E1E4E8">((</span><span style="color:#79B8FF">offset</span><span style="color:#E1E4E8">: template.</span><span style="color:#79B8FF">count</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">keyPath</span><span style="color:#E1E4E8">: keyPath))</span></span>
<span class="line" data-line="8"><span style="color:#E1E4E8">    }</span></span>
<span class="line" data-line="9"></span>
<span class="line" data-line="10"><span style="color:#F97583">    mutating</span><span style="color:#F97583"> func</span><span style="color:#B392F0"> append</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">string</span><span style="color:#E1E4E8">: TemplateInsertable) {</span></span>
<span class="line" data-line="11"><span style="color:#E1E4E8">        template.</span><span style="color:#79B8FF">append</span><span style="color:#E1E4E8">(string.</span><span style="color:#79B8FF">stringValue</span><span style="color:#E1E4E8">)</span></span>
<span class="line" data-line="12"><span style="color:#E1E4E8">    }</span></span>
<span class="line" data-line="13"></span>
<span class="line" data-line="14"><span style="color:#F97583">    public</span><span style="color:#F97583"> func</span><span style="color:#B392F0"> fill</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">with</span><span style="color:#E1E4E8"> value: T) </span><span style="color:#F97583">-></span><span style="color:#79B8FF"> String</span><span style="color:#E1E4E8"> {</span></span>
<span class="line" data-line="15"><span style="color:#F97583">        var</span><span style="color:#E1E4E8"> fullString </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> template</span></span>
<span class="line" data-line="16"><span style="color:#F97583">        var</span><span style="color:#E1E4E8"> indexOffset: </span><span style="color:#79B8FF">String</span><span style="color:#E1E4E8">.</span><span style="color:#79B8FF">IndexDistance</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> 0</span></span>
<span class="line" data-line="17"><span style="color:#F97583">        for</span><span style="color:#E1E4E8"> item </span><span style="color:#F97583">in</span><span style="color:#E1E4E8"> keyPaths {</span></span>
<span class="line" data-line="18"><span style="color:#F97583">            let</span><span style="color:#E1E4E8"> stringValue </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> (value[</span><span style="color:#79B8FF">keyPath</span><span style="color:#E1E4E8">: item.keyPath] </span><span style="color:#F97583">as!</span><span style="color:#E1E4E8"> TemplateInsertable).</span><span style="color:#79B8FF">stringValue</span></span>
<span class="line" data-line="19"><span style="color:#F97583">            let</span><span style="color:#E1E4E8"> insertionIndex </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> fullString.</span><span style="color:#79B8FF">index</span><span style="color:#E1E4E8">(fullString.</span><span style="color:#79B8FF">startIndex</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">offsetBy</span><span style="color:#E1E4E8">: item.offset </span><span style="color:#F97583">+</span><span style="color:#E1E4E8"> indexOffset)</span></span>
<span class="line" data-line="20"><span style="color:#E1E4E8">            fullString.</span><span style="color:#79B8FF">insert</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">contentsOf</span><span style="color:#E1E4E8">: stringValue, </span><span style="color:#79B8FF">at</span><span style="color:#E1E4E8">: insertionIndex)</span></span>
<span class="line" data-line="21"><span style="color:#E1E4E8">            indexOffset </span><span style="color:#F97583">+=</span><span style="color:#E1E4E8"> stringValue.</span><span style="color:#79B8FF">count</span></span>
<span class="line" data-line="22"><span style="color:#E1E4E8">        }</span></span>
<span class="line" data-line="23"></span>
<span class="line" data-line="24"><span style="color:#F97583">        return</span><span style="color:#E1E4E8"> fullString</span></span>
<span class="line" data-line="25"><span style="color:#E1E4E8">    }</span></span>
<span class="line" data-line="26"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>Now comes the <code>ExpressibleByStringInterpolation</code> piece. The protocol requires declaring a type conforming to <code>StringInterpolationProtocol</code> which will be called by the standard library based on the components in the order required to 'build up' the full instance of your type. An example from <a href="https://developer.apple.com/documentation/swift/stringinterpolationprotocol">the docs</a> explains that if you have <code>("The time is \(time)." as MyString)</code> in your source, the compiled code will be similar to:</p>
<pre class="shiki github-dark" style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="swift" data-theme="github-dark"><code data-line-count="8" class="language-swift"><span class="line" data-line="1"><span style="color:#F97583">var</span><span style="color:#E1E4E8"> interpolation </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> MyString.</span><span style="color:#79B8FF">StringInterpolation</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">literalCapacity</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">13</span><span style="color:#E1E4E8">, </span></span>
<span class="line" data-line="2"><span style="color:#79B8FF">                                                 interpolationCount</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">)</span></span>
<span class="line" data-line="3"></span>
<span class="line" data-line="4"><span style="color:#E1E4E8">interpolation.</span><span style="color:#79B8FF">appendLiteral</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"The time is "</span><span style="color:#E1E4E8">)</span></span>
<span class="line" data-line="5"><span style="color:#E1E4E8">interpolation.</span><span style="color:#79B8FF">appendInterpolation</span><span style="color:#E1E4E8">(time)</span></span>
<span class="line" data-line="6"><span style="color:#E1E4E8">interpolation.</span><span style="color:#79B8FF">appendLiteral</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"."</span><span style="color:#E1E4E8">)</span></span>
<span class="line" data-line="7"></span>
<span class="line" data-line="8"><span style="color:#79B8FF">MyString</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">stringInterpolation</span><span style="color:#E1E4E8">: interpolation)</span></span></code></pre>
<p>If we implement <code>StringInterpolationProtocol</code>, we can call our internal <code>append()</code> functions. Note the type of the appendInterpolation function. We get to define the number of parameters, the parameter name and importantly for us, the type constraints on the parameters. Now we can be sure that all elements in our string interpolation will definitely be from our generic type <code>T</code> and be pointing to a value of type <code>U</code> which conforms to <code>TemplateInsertable</code>.</p>
<pre class="shiki github-dark" style="background-color:#24292e;color:#e1e4e8" tabindex="0" data-language="swift" data-theme="github-dark"><code data-line-count="23" class="language-swift"><span class="line" data-line="1"><span style="color:#F97583">extension</span><span style="color:#B392F0"> Template</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">ExpressibleByStringInterpolation</span><span style="color:#E1E4E8"> {</span></span>
<span class="line" data-line="2"></span>
<span class="line" data-line="3"><span style="color:#F97583">    public</span><span style="color:#F97583"> init</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">stringInterpolation</span><span style="color:#E1E4E8">: StringInterpolation) {</span></span>
<span class="line" data-line="4"><span style="color:#79B8FF">        self</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> stringInterpolation.template</span></span>
<span class="line" data-line="5"><span style="color:#E1E4E8">    }</span></span>
<span class="line" data-line="6"></span>
<span class="line" data-line="7"><span style="color:#F97583">    public</span><span style="color:#F97583"> struct</span><span style="color:#B392F0"> StringInterpolation</span><span style="color:#E1E4E8">: </span><span style="color:#B392F0">StringInterpolationProtocol </span><span style="color:#E1E4E8">{</span></span>
<span class="line" data-line="8"></span>
<span class="line" data-line="9"><span style="color:#F97583">        fileprivate</span><span style="color:#F97583"> var</span><span style="color:#E1E4E8"> template: Template&#x3C;T></span></span>
<span class="line" data-line="10"></span>
<span class="line" data-line="11"><span style="color:#F97583">        public</span><span style="color:#F97583"> init</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">literalCapacity</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">Int</span><span style="color:#E1E4E8">, </span><span style="color:#B392F0">interpolationCount</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">Int</span><span style="color:#E1E4E8">) {</span></span>
<span class="line" data-line="12"><span style="color:#E1E4E8">            template </span><span style="color:#F97583">=</span><span style="color:#E1E4E8"> Template</span><span style="color:#F97583">&#x3C;</span><span style="color:#E1E4E8">T</span><span style="color:#F97583">></span><span style="color:#E1E4E8">()</span></span>
<span class="line" data-line="13"><span style="color:#E1E4E8">        }</span></span>
<span class="line" data-line="14"></span>
<span class="line" data-line="15"><span style="color:#F97583">        mutating</span><span style="color:#F97583"> public</span><span style="color:#F97583"> func</span><span style="color:#B392F0"> appendLiteral</span><span style="color:#E1E4E8">(</span><span style="color:#B392F0">_</span><span style="color:#E1E4E8"> literal: </span><span style="color:#79B8FF">String</span><span style="color:#E1E4E8">) {</span></span>
<span class="line" data-line="16"><span style="color:#E1E4E8">            template.</span><span style="color:#79B8FF">append</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">string</span><span style="color:#E1E4E8">: literal)</span></span>
<span class="line" data-line="17"><span style="color:#E1E4E8">        }</span></span>
<span class="line" data-line="18"></span>
<span class="line" data-line="19"><span style="color:#F97583">        mutating</span><span style="color:#F97583"> public</span><span style="color:#F97583"> func</span><span style="color:#B392F0"> appendInterpolation</span><span style="color:#E1E4E8">&#x3C;</span><span style="color:#79B8FF">U</span><span style="color:#E1E4E8">: </span><span style="color:#B392F0">TemplateInsertable</span><span style="color:#E1E4E8">>(</span><span style="color:#B392F0">keyPath</span><span style="color:#E1E4E8">: KeyPath&#x3C;T, U>) {</span></span>
<span class="line" data-line="20"><span style="color:#E1E4E8">            template.</span><span style="color:#79B8FF">append</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">keyPath</span><span style="color:#E1E4E8">: keyPath)</span></span>
<span class="line" data-line="21"><span style="color:#E1E4E8">        }</span></span>
<span class="line" data-line="22"><span style="color:#E1E4E8">    }</span></span>
<span class="line" data-line="23"><span style="color:#E1E4E8">}</span></span></code></pre>
<h3>Conclusion</h3>
<p><code>ExpressibleByStringInterpolation</code> is a powerful way to build some interesting functionality out of syntax that appears like it's part of the language itself. Apple is going to be allowing more hooks like this in the future. Along similar lines, <a href="https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-delegates.md">Property Delegates</a> is another possible upcoming feature of Swift which could allow developers to define how properties store their values.</p>
<p>Type-safety can be tricky and require jumping through some hoops. Fortunately, they are hoops you jump through once when creating interfaces for your future self or other developers to use. Then, no more hoops. Just auto-completable, typo-proof code.</p>
<p>If you'd like to mess around with the Playground, <a href="/files/Template.playground.zip">here's the zip</a></p>]]></content:encoded>
      <author>Zac White</author>
    </item>
    <item>
      <title><![CDATA[New Site]]></title>
      <link>https://zacwhite.com/2019/new-site/</link>
      <guid isPermaLink="true">https://zacwhite.com/2019/new-site/</guid>
      <pubDate>Sun, 12 May 2019 21:00:00 GMT</pubDate>
      <description><![CDATA[New site with posts coming soon]]></description>
      <content:encoded><![CDATA[<p>Welcome to the new zacwhite.com! I'm going to try to post every once in a while with interesting code snippets (usually Swift), some personal projects and occationally some thoughts.</p>]]></content:encoded>
      <author>Zac White</author>
    </item>
  </channel>
</rss>