<?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, 02 Apr 2026 04:21:51 GMT</lastBuildDate>
    <atom:link href="https://zacwhite.com/feed.xml" rel="self" type="application/rss+xml"/>
    
    <item>
      <title><![CDATA[What Apple means to me]]></title>
      <link>https://zacwhite.com/2026/what-apple-means-to-me/</link>
      <guid isPermaLink="true">https://zacwhite.com/2026/what-apple-means-to-me/</guid>
      <pubDate>Wed, 01 Apr 2026 19:00:00 GMT</pubDate>
      <description><![CDATA[Growing up with Apple computers and how that turned into a career building apps]]></description>
      <content:encoded><![CDATA[<p>I used my first Apple computer before I could remember. My mom was a professor and would bring home her computers for work. One of my earliest core memories is sitting on her lap, playing games and clicking around.</p><p>I instantly felt an affinity to Apple.</p><p>My mom would end up bringing home or buying an <a href="https://everymac.com/systems/apple/mac_lc/specs/mac_lc.html">LC</a> “pizza box,” a <a href="https://everymac.com/systems/apple/mac_classic/specs/mac_tv.html">Macintosh TV</a> (I still have it!), a <a href="https://en.wikipedia.org/wiki/Power_Macintosh_6200">Performa 6300CD</a>, a <a href="https://everymac.com/systems/apple/powerbook_g3/specs/powerbook_g3_333.html">PowerBook G3 Lombard</a>, a <a href="https://everymac.com/systems/apple/powermac_g3/specs/powermac_g3_300_bl.html">G3 Blue &#x26; White</a>, a <a href="https://everymac.com/systems/apple/powerbook_g4/specs/powerbook_g4_667_dvi.html">TiBook</a>, and on and on.</p><p>The most anticipated event of each month was a new issue of MacAddict arriving in my mailbox and I was particularly impacted by issue <a href="https://archive.org/details/MacAddict-054-200102">#54</a> with the cover story “Build Your Own Apps.”</p><figure class="post-figure post-figure--caption"><img src="/assets/images/macaddict-54.jpg" alt="MacAddict cover for issue #54"><figcaption class="post-image-caption">The issue that kick-started my love for programming on the Apple platforms</figcaption></figure><p>I fired up the included demo of RealBASIC. I was reading about cryptography at the time and made a simple letter frequency analysis app. On my computer! I made it! It worked exactly the way I wanted it to work.</p><p>I was hooked. I was also interested in exploring the other option in the magazine: Cocoa. This turned out to be a pretty important article for my future career. Thank you to the writer, Ian Sammis!</p><p>I wanted to build an app for everything. I made a chat app, I made a music guessing game, I made a screensaver that would <a href="https://web.archive.org/web/20040714043503/http://versiontracker.com/dyn/moreinfo/macosx/22916">simulate a kernel panic for April Fools’ Day</a>. I took classes in high school, but had to do programming on Windows with C++. It was an awful experience, but I knew I wanted to do this for a living. And I knew what platform I wanted to do it on.</p><p>I went into college knowing exactly what major I wanted: Computer Science. And that meant Java at that time. So I did Java at school and then kept learning Objective-C to fuel my need to build native, beautiful apps for the computer I loved.</p><p>Then came the iPhone, running iPhone OS 1.0, and it was incredible.</p><p>I wanted to build apps for it! But the “sweet solution” wasn’t so enticing. I eventually found a group of hackers out there who found a way to get their Objective-C code running on-device. All I had to do was perform this thing called a "jailbreak," install <a href="https://en.wikipedia.org/wiki/Cydia">Cydia</a>, install OpenSSH, get an ARM cross-compilation setup, <a href="https://github.com/zac/mobilebeat/blob/master/Makefile">run some Makefiles</a>, and BOOM! I built an app I called <a href="https://github.com/zac/mobilebeat">MobileBeat</a>, a rudimentary step sequencer.</p><figure class="post-figure post-figure--caption"><img src="/assets/images/mobilebeat.jpg" alt="MobileBeat for iPhone OS 1.0"><figcaption class="post-image-caption">MobileBeat, a simple step sequencer, running on my jailbroken iPhone</figcaption></figure><p>I was running the same code that Apple was running. And I could poke around at how they built things using the same tools I had used on the Mac to dump all the classes of Apple’s built-in apps. I was learning to build for this new Apple platform.</p><p>Then came the SDK with iPhone OS 2.0. I wanted to ship something to the App Store, but I was more interested in shipping something that wasn't possible with the official APIs. I wanted copy and paste, which surprisingly didn't come in 2.0, so I built a library to do it. I also wanted a system-wide search of Contacts, calendar events, Safari history, etc. That didn't come in 2.0... or 3.0, or 4.0, or 5.0, or 6.0. So I built an app called "Searcher" to do that. I also wanted drag and drop between apps, so I built a library to do that. Everything I could dream of with this new platform, I could build.</p><p>I lived in Xcode. I loved building out my UI in Interface Builder. These free tools were incredible and unlocked a whole career for me.</p><p>I received a WWDC scholarship in 2006, 2007, and 2008. In 2008, I spoke with a mobile agency doing J2ME/Brew work that wanted to take a flyer on this new platform and a young kid (almost) out of college to build their first iPhone app. I moved to SF and started making a living doing what I always wanted to do: building apps for the platforms from a company whose products I had loved since I was a kid.</p><p>And I've stuck with it. I left that agency with a few others about 15 years ago and started <a href="https://velosmobile.com">my own agency</a>. We focused on mobile apps, but my focus was fully on the Apple platforms. As Apple made new platforms, I built for them: tvOS, watchOS, iPadOS, visionOS, heck, we even built an iMessage app! Apple created the ecosystem and community, and I built my business around it.</p><p>Apple's impact on me continues today. The tools are changing, and the way I'm writing software is changing rapidly, but I’m still building, and I’m excited for the next platform (glasses? HomePod with a screen? pendant?). I am a user, developer, and just plain <em>fan</em> of Apple, and here's to another 50 years for them, and another 30 years of their impact on me 🎉</p>]]></content:encoded>
      <author>Zac White</author>
    </item>
    <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>