<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.2.2">Jekyll</generator><link href="https://ampersandsoftworks.com/feed/by_tag/development.xml" rel="self" type="application/atom+xml" /><link href="https://ampersandsoftworks.com/" rel="alternate" type="text/html" /><updated>2024-09-22T15:21:58-06:00</updated><id>https://ampersandsoftworks.com/feed/by_tag/development.xml</id><title type="html">Ampersand Softworks</title><subtitle>Your friendly neighbourhood development blog about Apple branded pocket computer development.</subtitle><author><name>brett ohland</name></author><entry><title type="html">Building A Stopwatch And Timer Using Xcode 16’s New SystemFormatStyles</title><link href="https://ampersandsoftworks.com/posts/building-a-stopwatch-and-timer-using-xcode16s-new-systemformatstyle/index.html" rel="alternate" type="text/html" title="Building A Stopwatch And Timer Using Xcode 16’s New SystemFormatStyles" /><published>2024-09-22T06:00:57-06:00</published><updated>2024-09-22T06:00:57-06:00</updated><id>https://ampersandsoftworks.com/posts/building-a-stopwatch-and-timer-using-xcode16s-new-systemformatstyle/building-a-stopwatch-and-timer-using-xcode16s-new-systemformatstyle</id><content type="html" xml:base="https://ampersandsoftworks.com/posts/building-a-stopwatch-and-timer-using-xcode16s-new-systemformatstyle/index.html"><![CDATA[<p><small><em>Updated September 22nd, 2024 to use the new <code class="language-plaintext highlighter-rouge">TimeDataSource</code> in the examples, not a <code class="language-plaintext highlighter-rouge">TimelineView</code>. <a href="https://hachyderm.io/@jasongregori/113164581438630467">Props to friend of the blog Jason Gregori for pointing this out</a></em></small></p>

<hr />

<p>Apple has provided some new format style implementations with Xcode 16 (which is in beta as of this writing).</p>

<p>I’ve been working to update the <a href="https://goshdarnformatstyle.com/">Gosh Darned Site</a> to include all of the new updates, but ran into a wall when it comes to two of the new SwiftUI-only styles: <code class="language-plaintext highlighter-rouge">SystemFormatStyle.Stopwatch</code> and <code class="language-plaintext highlighter-rouge">SystemFormatStyle.Timer</code>.  Initially, I expected to get an animating SwiftUI view “for free” without needing to create any sort of a view hierarchy but quickly found out I was wrong.</p>

<p>Trying to figure out they’re to be used was frustrating. Apple’s documentation is pretty sparse, as is the code comment documentation in <code class="language-plaintext highlighter-rouge">SwiftUICore</code>.</p>

<p>Because these new styles aren’t a part of <code class="language-plaintext highlighter-rouge">Foundation</code>, you can’t see their internals at the <a href="https://github.com/apple/swift-foundation">Swift Foundation</a> Github repo. It’s unclear exactly how to use these new styles to make a stopwatch or a timer in your views.</p>

<p>I did figure it out, and decided that sending some information out into the æther might help out any other devs who’re looking to use these styles.</p>

<h1 id="a-formatstyle-refresher">A <code class="language-plaintext highlighter-rouge">FormatStyle</code> Refresher</h1>

<p>In brief, format styles are Apple’s modern Swift replacements for the older Objective-C <code class="language-plaintext highlighter-rouge">Formatter</code> classes. They allow you to quickly and easily create localized string representations of various data types to display to a user without all of the “gotchas” relating to the old classes.</p>

<p>They’re safe, performant, and really easy to use throughout your code to convert one type into another. Apple provides quite a few implementations for all sorts of data types from dates, numbers, lists, to measurements.</p>

<p>I’ve covered them extensively in the past, as well as created an entire site which fills in the gaps in Apple’s documentation.</p>

<ul>
  <li><a href="/posts/format-style-deep-dive/">FormatStyle Deep Dive</a></li>
  <li><a href="https://goshdarnedformatstyle.com">Gosh Darned FormatStyle</a></li>
</ul>

<p>The big issue is that until very recently Apple’s documentation has been extremely sparse, making these powerful tools hard to use.</p>

<hr />

<h1 id="systemformatstyle-differences-from-formatstyle"><code class="language-plaintext highlighter-rouge">SystemFormatStyle</code> Differences from <code class="language-plaintext highlighter-rouge">FormatStyle</code></h1>

<p>All of the new styles included in the <code class="language-plaintext highlighter-rouge">SystemFormatStyle</code> enum/namespace are all made to format <code class="language-plaintext highlighter-rouge">Date</code> objects. However unlike the <code class="language-plaintext highlighter-rouge">FormatStyle</code> implementations inside of <code class="language-plaintext highlighter-rouge">Foundation</code> which have their outputs set to be <code class="language-plaintext highlighter-rouge">String</code> values, these new styles will output an <code class="language-plaintext highlighter-rouge">AttributedString</code> by default.</p>

<p>The only SwiftUI View that accepts an <code class="language-plaintext highlighter-rouge">AttributedString</code> is the <code class="language-plaintext highlighter-rouge">Text</code> View, and that’s only because Apple added a new initializer which accepts it. This really isn’t as limiting as it might seem at first, really the only places you’re going to be using these new styles are in a <code class="language-plaintext highlighter-rouge">Text</code> view anyway.</p>

<p>One last big difference that’s only present in the new <code class="language-plaintext highlighter-rouge">SystemFormatStyle.Stopwatch</code> and <code class="language-plaintext highlighter-rouge">SystemFormatStyle.Timer</code> styles is that a second data value is passed into the styles in order to correctly calculate date offsets. All other <code class="language-plaintext highlighter-rouge">Foundation</code> format styles will accept different <code class="language-plaintext highlighter-rouge">enum</code> types which control the output of style. These are the first ones which take in an additional value or object in order to work.</p>

<p>Enough preamble, let’s build a stopwatch.</p>

<h1 id="stopwatch-mvp">Stopwatch MVP</h1>

<p>The MVP for a stopwatch that counts up needs two pieces of data to function, and two pieces of UI:</p>

<ol>
  <li>Data
    <ol>
      <li>A <code class="language-plaintext highlighter-rouge">Date</code> which represents starting time</li>
      <li>A <code class="language-plaintext highlighter-rouge">Date</code> which represents the current moment in time to calculate an offset to the starting time</li>
    </ol>
  </li>
  <li>UI
    <ol>
      <li>A <code class="language-plaintext highlighter-rouge">Text</code> view to show the styled differences between those dates</li>
      <li>A <code class="language-plaintext highlighter-rouge">Button</code> which starts the stopwatch counting up</li>
    </ol>
  </li>
</ol>

<p>Lets build that out:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">Stopwatch</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
    <span class="kd">@State</span> <span class="k">var</span> <span class="nv">startDate</span><span class="p">:</span> <span class="kt">Date</span><span class="p">?</span> <span class="c1">// 1.1</span>

    <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="kt">Text</span><span class="p">(</span><span class="kt">Date</span><span class="o">.</span><span class="n">now</span><span class="p">,</span> <span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="nf">stopwatch</span><span class="p">(</span><span class="nv">startingAt</span><span class="p">:</span> <span class="n">startDate</span> <span class="p">??</span> <span class="o">.</span><span class="n">now</span><span class="p">))</span> <span class="c1">// 2.1 </span>
        <span class="kt">Button</span><span class="p">(</span><span class="s">"Start"</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 2.2</span>
            <span class="n">startDate</span> <span class="o">=</span> <span class="o">.</span><span class="n">now</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This gives us the base view hierarchy that we’ll need to make our stopwatch. It’s nonfunctional right now, since our start and end dates are <code class="language-plaintext highlighter-rouge">Date.now</code> and there’s no mechanism to animate the view.</p>

<p><img src="/images/2024/Aug/stopwatch-mvp.png" alt="The SwiftUI Output of the above code" /></p>

<p>It’s <code class="language-plaintext highlighter-rouge">2.1</code> where our new <code class="language-plaintext highlighter-rouge">SystemFormatStyle.Stopwatch</code> style is being used. We’re using the new initializer on the <code class="language-plaintext highlighter-rouge">Text</code> view which takes in a type as well as a format style which has it’s output set to an <code class="language-plaintext highlighter-rouge">AttributedString</code>. The <code class="language-plaintext highlighter-rouge">.stopwatch(startingAt:)</code> static method we’re using here is an Apple-provided extension on <code class="language-plaintext highlighter-rouge">FormatStyle</code> that we use as a shortcut to creating a new instance of the required <code class="language-plaintext highlighter-rouge">SystemFormatStyle.Stopwatch</code> format style.</p>

<h2 id="adding-animation-using-the-new-timedatasource">Adding Animation Using The New <code class="language-plaintext highlighter-rouge">TimeDataSource</code></h2>

<p>If you read an earlier version of this post used a <code class="language-plaintext highlighter-rouge">TimelineView</code> to handle the animation. Little did I know that there is an even easier way: A new struct called <code class="language-plaintext highlighter-rouge">TimeDataSource</code>.</p>

<p>A <code class="language-plaintext highlighter-rouge">TimeDataStruct</code> is a special new type provided when importing SwiftUI. It’s purpose-built to give a <code class="language-plaintext highlighter-rouge">Text</code> view a live-updating values which will allow for animations without any sort of Timeline view or other <code class="language-plaintext highlighter-rouge">@State</code> property.</p>

<p>Using this new type has a small amount of complexity though, the new struct has no public-facing initializers and you’re reliant on the type properties and methods available. For us, the purpose-built property that we’ll use is <code class="language-plaintext highlighter-rouge">TimeDataStruct&lt;Date&gt;.currentDate</code>.</p>

<p>You’ll notice that <code class="language-plaintext highlighter-rouge">TimeDataStruct</code> has an associated type (the <code class="language-plaintext highlighter-rouge">&lt;Date&gt;</code> portion of the code). This is because each of the different properties and methods can provide you with a different value type, from <code class="language-plaintext highlighter-rouge">Duration</code> to <code class="language-plaintext highlighter-rouge">Range&lt;Date&gt;</code>.</p>

<p>Let’s add that, and also let’s add a reset button and a few view modifiers to make things look nice.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">Stopwatch</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
    <span class="kd">@State</span> <span class="k">var</span> <span class="nv">startDate</span><span class="p">:</span> <span class="kt">Date</span><span class="p">?</span> <span class="c1">// Stores when the user presses the start button</span>
    <span class="kd">@State</span> <span class="k">var</span> <span class="nv">isAnimationPaused</span> <span class="o">=</span> <span class="kc">true</span> <span class="c1">// Flag to control the animation update schedule</span>
    <span class="kd">@State</span> <span class="k">var</span> <span class="nv">lastUpdate</span><span class="p">:</span> <span class="kt">Date</span><span class="p">?</span> <span class="c1">// Stores the date of the last update on screen</span>

    <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="k">switch</span> <span class="n">isAnimationPaused</span> <span class="p">{</span>
        <span class="k">case</span> <span class="nv">true</span><span class="p">:</span>
            <span class="c1">// By using a static `Date` value, the view will be static</span>
            <span class="c1">// Using the `startDate` or Date.now fallback allows the stopwatch to pause and</span>
            <span class="c1">// continue to display the duration </span>
            <span class="kt">Text</span><span class="p">(</span><span class="kt">Date</span><span class="o">.</span><span class="n">now</span><span class="p">,</span> <span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="nf">stopwatch</span><span class="p">(</span><span class="nv">startingAt</span><span class="p">:</span> <span class="n">startDate</span> <span class="p">??</span> <span class="o">.</span><span class="n">now</span><span class="p">))</span>
        <span class="k">case</span> <span class="nv">false</span><span class="p">:</span>
            <span class="c1">// Using a `TimeDataSource`, the view will animate</span>
            <span class="kt">Text</span><span class="p">(</span><span class="kt">TimeDataSource</span><span class="o">&lt;</span><span class="kt">Date</span><span class="o">&gt;.</span><span class="n">currentDate</span><span class="p">,</span> <span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="nf">stopwatch</span><span class="p">(</span><span class="nv">startingAt</span><span class="p">:</span> <span class="n">startDate</span> <span class="p">??</span> <span class="o">.</span><span class="n">now</span><span class="p">))</span>
        <span class="p">}</span>
        <span class="kt">HStack</span> <span class="p">{</span>
            <span class="kt">Button</span><span class="p">(</span><span class="s">"Start"</span><span class="p">)</span> <span class="p">{</span>
                <span class="c1">// Store the moment at which the user presses the Start button</span>
                <span class="n">startDate</span> <span class="o">=</span> <span class="o">.</span><span class="n">now</span>
                <span class="c1">// Unpause the TimelineView's animation schedule</span>
                <span class="n">isAnimationPaused</span> <span class="o">=</span> <span class="kc">false</span>
            <span class="p">}</span>
            <span class="o">.</span><span class="nf">buttonStyle</span><span class="p">(</span><span class="o">.</span><span class="n">bordered</span><span class="p">)</span>
            <span class="o">.</span><span class="nf">tint</span><span class="p">(</span><span class="o">.</span><span class="n">green</span><span class="p">)</span>
            <span class="o">.</span><span class="nf">disabled</span><span class="p">(</span><span class="n">isAnimationPaused</span> <span class="o">==</span> <span class="kc">false</span><span class="p">)</span>
            <span class="kt">Button</span><span class="p">(</span><span class="s">"Stop"</span><span class="p">)</span> <span class="p">{</span>
                <span class="c1">// We only pause the animation schedule to stop the</span>
                <span class="n">isAnimationPaused</span> <span class="o">=</span> <span class="kc">true</span>
            <span class="p">}</span>
            <span class="o">.</span><span class="nf">buttonStyle</span><span class="p">(</span><span class="o">.</span><span class="n">borderedProminent</span><span class="p">)</span>
            <span class="o">.</span><span class="nf">tint</span><span class="p">(</span><span class="o">.</span><span class="n">red</span><span class="p">)</span>
            <span class="o">.</span><span class="nf">disabled</span><span class="p">(</span><span class="n">isAnimationPaused</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>We now have a a fully functioning stopwatch.</p>

<p><img src="/images/2024/Aug/stopwatch-complete.gif" alt="A screen recording of the completed stopwatch" /></p>

<hr />

<h1 id="lets-build-a-timer-using-systemformatstyletimer">Let’s Build A Timer Using <code class="language-plaintext highlighter-rouge">SystemFormatStyle.Timer</code></h1>

<p>Conceptually, <code class="language-plaintext highlighter-rouge">SystemFormatStyle.Timer</code> is similar to <code class="language-plaintext highlighter-rouge">SystemFormatStyle.Stopwatch</code>. The <code class="language-plaintext highlighter-rouge">Date</code> object that we apply the format to is the “current” date used for the style’s calculation, but instead of a single <code class="language-plaintext highlighter-rouge">Date</code> representing the starting time of the stopwatch, we pass a <code class="language-plaintext highlighter-rouge">Range&lt;Date&gt;</code> value representing the upper and lower bounds of the timer.</p>

<p>If you’re a visual learner (like myself), this might help:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>   0:00                                                              0:10
LowerBound                                                        UpperBound &lt;- Range&lt;Date&gt;
    └----------------------------------------------------------------- ┘
                                  &lt;-|
                                   0:05
                                currentDate
</code></pre></div></div>

<ul>
  <li>If the current date is at or below the lower bound, it will display <code class="language-plaintext highlighter-rouge">0:00</code></li>
  <li>If it’s within the range, it will calculate and display the offset between the current date and the lower bound</li>
  <li>If the current date is at or above the upper bound, it will display the offset between the lower and upper bound</li>
</ul>

<h2 id="timer-code-example">Timer Code Example</h2>

<p>The base structure of the view hierarchy is really similar to the stopwatch. We have a <code class="language-plaintext highlighter-rouge">Text</code> view with a <code class="language-plaintext highlighter-rouge">TimeDataSource</code> to handle the animation, a <code class="language-plaintext highlighter-rouge">Stepper</code> to add time to the timer, and some buttons to stop or reset it, and a few <code class="language-plaintext highlighter-rouge">@State</code> properties to handle user interaction.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">CountdownTimer</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
    <span class="kd">private</span> <span class="k">let</span> <span class="nv">timerStepRange</span> <span class="o">=</span> <span class="mi">0</span> <span class="o">...</span> <span class="mi">60</span>

    <span class="kd">@State</span> <span class="k">var</span> <span class="nv">isAnimationPaused</span> <span class="o">=</span> <span class="kc">true</span> <span class="c1">// Flag to control the animation update schedule</span>
    <span class="kd">@State</span> <span class="k">var</span> <span class="nv">timerRange</span><span class="p">:</span> <span class="kt">Range</span><span class="o">&lt;</span><span class="kt">Date</span><span class="o">&gt;</span><span class="p">?</span> <span class="c1">// Stores the range of the timer</span>
    <span class="kd">@State</span> <span class="k">var</span> <span class="nv">timerStep</span> <span class="o">=</span> <span class="mi">0</span> <span class="c1">// Bound to the stepper to control the timer</span>

    <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="n">timerDisplay</span>
            <span class="o">.</span><span class="nf">font</span><span class="p">(</span><span class="o">.</span><span class="n">title</span><span class="p">)</span>
        <span class="n">stepperControls</span>
        <span class="n">buttons</span>
    <span class="p">}</span>

    <span class="k">var</span> <span class="nv">timerDisplay</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">isAnimationPaused</span> <span class="p">{</span>
            <span class="kt">Text</span><span class="p">(</span><span class="o">.</span><span class="n">now</span><span class="p">,</span> <span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="nf">timer</span><span class="p">(</span><span class="nv">countingDownIn</span><span class="p">:</span> <span class="n">timerRange</span> <span class="p">??</span> <span class="o">.</span><span class="n">now</span> <span class="o">..&lt;</span> <span class="o">.</span><span class="n">now</span><span class="p">))</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="kt">Text</span><span class="p">(</span><span class="kt">TimeDataSource</span><span class="o">&lt;</span><span class="kt">Date</span><span class="o">&gt;.</span><span class="n">currentDate</span><span class="p">,</span> <span class="nv">format</span><span class="p">:</span> <span class="o">.</span><span class="nf">timer</span><span class="p">(</span><span class="nv">countingDownIn</span><span class="p">:</span> <span class="n">timerRange</span> <span class="p">??</span> <span class="o">.</span><span class="n">now</span> <span class="o">..&lt;</span> <span class="o">.</span><span class="n">now</span><span class="p">))</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">var</span> <span class="nv">stepperControls</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="c1">// A simple stepper that adds seconds to the timer in increments of 10</span>
        <span class="kt">Stepper</span><span class="p">(</span><span class="nv">value</span><span class="p">:</span> <span class="err">$</span><span class="n">timerStep</span><span class="p">,</span> <span class="nv">in</span><span class="p">:</span> <span class="n">timerStepRange</span><span class="p">,</span> <span class="nv">step</span><span class="p">:</span> <span class="mi">10</span><span class="p">)</span> <span class="p">{</span>
            <span class="kt">Text</span><span class="p">(</span><span class="s">"Seconds"</span><span class="p">)</span>
                <span class="o">.</span><span class="nf">padding</span><span class="p">()</span>
        <span class="p">}</span> <span class="nv">onEditingChanged</span><span class="p">:</span> <span class="p">{</span> <span class="n">didPressDown</span> <span class="k">in</span>
            <span class="c1">// Verify that the user has released the button</span>
            <span class="k">guard</span> <span class="n">didPressDown</span> <span class="o">==</span> <span class="kc">false</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
            <span class="n">timerRange</span> <span class="o">=</span> <span class="nf">makeTimerRange</span><span class="p">(</span><span class="nv">addingSeconds</span><span class="p">:</span> <span class="kt">TimeInterval</span><span class="p">(</span><span class="n">timerStep</span><span class="p">))</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">var</span> <span class="nv">buttons</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="kt">HStack</span> <span class="p">{</span>
            <span class="kt">Button</span><span class="p">(</span><span class="s">"Start"</span><span class="p">)</span> <span class="p">{</span>
                <span class="c1">// Recalculate the timer's range based on the current step on the Stepper vie</span>
                <span class="n">timerRange</span> <span class="o">=</span> <span class="nf">makeTimerRange</span><span class="p">(</span><span class="nv">addingSeconds</span><span class="p">:</span> <span class="kt">TimeInterval</span><span class="p">(</span><span class="n">timerStep</span><span class="p">))</span>
                <span class="c1">// Toggle the animation</span>
                <span class="n">isAnimationPaused</span><span class="o">.</span><span class="nf">toggle</span><span class="p">()</span>
            <span class="p">}</span>
            <span class="c1">// Disable the button if the timer is running OR if the stepper value is '0"</span>
            <span class="o">.</span><span class="nf">disabled</span><span class="p">(</span><span class="n">isAnimationPaused</span> <span class="o">==</span> <span class="kc">false</span> <span class="o">||</span> <span class="n">timerStep</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
            <span class="o">.</span><span class="nf">tint</span><span class="p">(</span><span class="o">.</span><span class="n">green</span><span class="p">)</span>
            <span class="kt">Button</span><span class="p">(</span><span class="s">"Stop"</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">isAnimationPaused</span><span class="o">.</span><span class="nf">toggle</span><span class="p">()</span>
            <span class="p">}</span>
            <span class="c1">// Disable the button if the animation is paused</span>
            <span class="o">.</span><span class="nf">disabled</span><span class="p">(</span><span class="n">isAnimationPaused</span><span class="p">)</span>
            <span class="o">.</span><span class="nf">tint</span><span class="p">(</span><span class="o">.</span><span class="n">red</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="o">.</span><span class="nf">buttonStyle</span><span class="p">(</span><span class="o">.</span><span class="n">bordered</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="c1">/// Returns a `Range&lt;Date&gt;` that can be used by timer's format style</span>
    <span class="c1">/// - Parameter seconds: `TimeInterval`</span>
    <span class="c1">/// - Returns: `Range&lt;Date&gt;` where the lower bound is the current date and the upper bound is the</span>
    <span class="c1">/// current date adding the `seconds` parameter</span>
    <span class="kd">func</span> <span class="nf">makeTimerRange</span><span class="p">(</span><span class="n">addingSeconds</span> <span class="nv">seconds</span><span class="p">:</span> <span class="kt">TimeInterval</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Range</span><span class="o">&lt;</span><span class="kt">Date</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="c1">// We capture the current date to be used throughout this method to guarantee accuracy</span>
        <span class="k">let</span> <span class="nv">currentDateTime</span> <span class="o">=</span> <span class="kt">Date</span><span class="o">.</span><span class="n">now</span>

        <span class="c1">// Make sure that our seconds value is greater than zero. This guarantees that our range calculations will</span>
        <span class="c1">// be formatted correctly.</span>
        <span class="k">guard</span> <span class="n">seconds</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="k">else</span> <span class="p">{</span>
            <span class="c1">// Otherwise we return a 0 value</span>
            <span class="k">return</span> <span class="n">currentDateTime</span> <span class="o">..&lt;</span> <span class="n">currentDateTime</span>
        <span class="p">}</span>
        <span class="c1">// Date calculations should _always_ be done using the `Calendar` APIs to avoid any sort of strange date</span>
        <span class="c1">// related edge cases.</span>
        <span class="k">let</span> <span class="nv">upperBoundDate</span> <span class="o">=</span> <span class="kt">Calendar</span><span class="o">.</span><span class="n">current</span><span class="o">.</span><span class="nf">date</span><span class="p">(</span><span class="nv">byAdding</span><span class="p">:</span> <span class="o">.</span><span class="n">second</span><span class="p">,</span> <span class="nv">value</span><span class="p">:</span> <span class="kt">Int</span><span class="p">(</span><span class="n">seconds</span><span class="p">),</span> <span class="nv">to</span><span class="p">:</span> <span class="o">.</span><span class="n">now</span><span class="p">)</span>
        <span class="c1">// As a fallback, we use the less-safe (but probably okay) Date API.</span>
        <span class="k">return</span> <span class="n">currentDateTime</span> <span class="o">..&lt;</span> <span class="p">(</span><span class="n">upperBoundDate</span> <span class="p">??</span> <span class="n">currentDateTime</span><span class="o">.</span><span class="nf">addingTimeInterval</span><span class="p">(</span><span class="n">seconds</span><span class="p">))</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Running this code, you’ll end up with a very simple countdown timer:</p>

<p><img src="/images/2024/Aug/timer-complete.gif" alt="A screen recording of the completed countdown timer" /></p>

<h1 id="just-the-beginning">Just The Beginning</h1>

<p>These new styles are two of a handful that are being released with Xcode 16 under the <code class="language-plaintext highlighter-rouge">SystemFormatStyle</code> enum/namespace and examples only scratch the surface of what these new format styles can do. Unfortunately I wish I could say to “read the docs”, but the sad truth is that Apple has once again woefully fallen short in sharing documentation with their developers.</p>

<p>Thankfully in the comings weeks, there will be updates to the <a href="https://goshdarnformatstyle.com">Gosh Darn Format Style</a> that documents everything (and more!).</p>]]></content><author><name>brett ohland</name></author><category term="ios" /><category term="development" /><category term="swift" /><category term="formatstyle" /><summary type="html"><![CDATA[SwiftUI Documentation continues to be lacking. _(Updated 2024-09-22 to use the new TimeDataSource type)_]]></summary></entry><entry><title type="html">You can stop writing date format strings like “yyyy-MMM-dd”</title><link href="https://ampersandsoftworks.com/posts/you-can-stop-writing-yyyy-mmm-dd-as-of-ios-15/index.html" rel="alternate" type="text/html" title="You can stop writing date format strings like “yyyy-MMM-dd”" /><published>2022-11-19T05:21:00-07:00</published><updated>2022-11-19T05:21:00-07:00</updated><id>https://ampersandsoftworks.com/posts/you-can-stop-writing-yyyy-mmm-dd-as-of-ios-15/you-can-stop-writing-yyyy-mmm-dd-as-of-ios-15</id><content type="html" xml:base="https://ampersandsoftworks.com/posts/you-can-stop-writing-yyyy-mmm-dd-as-of-ios-15/index.html"><![CDATA[<p><small><em>Updated November 19th, 2022 to include clarity around setting the TimeZone and/or Calendar on the Verbatim Format Style.</em></small></p>

<p>I dislike time.</p>

<p>Well, more specifically: I hate being a programmer and dealing with times and dates.</p>

<blockquote>
  <p>This is your bi-annual reminder about the <a href="https://gist.github.com/timvisee/fcda9bbdff88d45cc9061606b4b923ca">falsehoods programmers believe about time</a>.</p>
</blockquote>

<p>As Apple ecosystem developers, I can say that we’re spoiled by <a href="https://developer.apple.com/documentation/foundation/dates_and_times">Foundation’s incredible date and time handling</a>. When used correctly, you can push a lot of those date and time assumptions out of your brain and trust that someone <em>much more dedicated to the cause than you</em> has been toiling in the calendar mines for a very long time and taken care of it for you.</p>

<p>But it’s not all sunshine and roses…</p>

<h1 id="enter-the-dateformatter">Enter the DateFormatter</h1>

<blockquote>
  <p>TL;DR <a href="https://nsdateformatter.com/#best-practices">Just read the best practices section of NSDateFormatter.com</a> on why DateFormatters are rough.</p>
</blockquote>

<p>To handle the complexities of showing the user a localized date string, we have the <a href="https://developer.apple.com/documentation/foundation/nsdateformatter"><code class="language-plaintext highlighter-rouge">(NS)DateFormatter</code></a> to do the heavy lifting for us.</p>

<p>I wouldn’t consider this, or any other of the formatter classes, to be beginner friendly. This is because there’s a level of specialized knowledge that you need to use them effectively. The biggest “gotcha” is that they’re expensive to initialize, but not so much that you’d notice if you create a handful of them.</p>

<p>A simple example that I’ve seen (and written myself before I knew better) would be solving the need of showing a date to a user in a <code class="language-plaintext highlighter-rouge">UITableView</code> cell:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">func</span> tableView(<span class="keyword token">_</span> tableView: <span class="type token">UITableView</span>, cellForRowAt indexPath: <span class="type token">IndexPath</span>) -&gt; <span class="type token">UITableViewCell</span> {
    <span class="keyword token">var</span> formatter = <span class="type token">DateFormatter</span>()
    formatter.<span class="property token">dateStyle</span> = .<span class="dotAccess token">full</span>
    formatter.<span class="property token">timeStyle</span> = .<span class="dotAccess token">full</span>

    <span class="keyword token">var</span> cell = tableView.<span class="call token">dequeueReusableCell</span>(withIdentifier: <span class="string token">"dateCell"</span>, for: indexPath)
    <span class="comment token">// Display the current date and time in the cell using the above date formatter.</span>
    cell.<span class="property token">textLabel</span>?.<span class="property token">text</span> = formatter.<span class="call token">string</span>(from: .<span class="dotAccess token">now</span>)
    <span class="keyword token">return</span> cell
}
</code></pre></div>

<p>We’re now in a situation where the size of our table’s data source can now cripple the performance of our application. You aren’t going to notice an issue scrolling through a table of a few items (or probably a few dozen), but the minute you’re scrolling through a list of posts in your social network client. You’re going to start seeing a lot of memory usage.</p>

<p>The solve for this is to create your date formatter once in a shared location and to use that instance everywhere.</p>

<p><a href="https://developer.apple.com/documentation/foundation/dateformatter">Apple’s current documentation doesn’t mention this pitfall</a>, which is a shame. If you dig deep, <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/DataFormatting/Articles/dfDateFormatting10_4.html#//apple_ref/doc/uid/TP40002369-SW10">you can find this info in the documentation archive</a>, but I’m not sure many developers will dig this deep. Hopefully you’ll run into this advice somewhere in Stack Overflow.</p>

<p>Next bit of esoterica:</p>

<h1 id="now-read-a-unicode-technical-standard">Now Read A Unicode Technical Standard!</h1>

<p>If you’re needing more fine-grained control over your date string (outside of the set none/short/medium/long/full options for <code class="language-plaintext highlighter-rouge">dateStyle</code> and <code class="language-plaintext highlighter-rouge">timeStyle</code>), you get to learn all about <a href="http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns">Unicode standard date format patterns</a>.</p>

<p>Thankfully, because it’s a Unicode standard, the internet is awash with information about how to write your magical date strings to get the exact output you want. But there’s weird traps here too. For example: <a href="https://dev.to/shane/yyyy-vs-yyyy-the-day-the-java-date-formatter-hurt-my-brain-4527">If you use “YYYY” instead of “yyyy”, your code is going to output the wrong year 1% of the time</a>. Good luck with that bug.</p>

<p>Infamously, <a href="https://benscheirman.com">Ben Scheirman</a>/<a href="https://nsscreencast.com/">NSScreencast</a> built a single serving site just to help with this: <a href="https://nsdateformatter.com">https://nsdateformatter.com</a>. Honestly, just read the <a href="https://nsdateformatter.com/#best-practices">Best Practices</a> section to immediately learn everything you need to know about this.</p>

<p>But to quickly recap:</p>

<ol>
  <li>You should be using the presets as much as possible</li>
  <li>The formatter isn’t fully Locale-aware by default</li>
  <li>You should be using the <code class="language-plaintext highlighter-rouge">ISO8601DateFormatter</code> if you’re dealing with standard dates (and set the <code class="language-plaintext highlighter-rouge">en_US_POSIX</code> locale)</li>
</ol>

<p>There has to be a better way.</p>

<h1 id="format-styles-are-great">Format Styles Are Great</h1>

<p>Starting with every platform supported by Xcode 13 <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> (iOS 15.0+, iPadOS 15.0+, Mac Catalyst 15.0+, tvOS 15.0+, watchOS 8.0+, macOS 12.0+) and made slightly easier in every platform supported by Xcode 14 <sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>. Apple has moved away from the formatter classes to the more modern Format Style protocol. But in true Apple fashion, the documentation for this functionality is spotty. In fact, <a href="https://goshdarnformatstyle.com">I made a whole site to document what they can do</a>.</p>

<blockquote>
  <p><strong>New to the concept of format styles? Here’s a recap:</strong></p>

  <ol>
    <li>Nearly every Foundation type have a <code class="language-plaintext highlighter-rouge">.formatted()</code> method as of Xcode 13 <sup id="fnref:1:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup></li>
    <li>You can optionally pass something that conforms to the <code class="language-plaintext highlighter-rouge">FormatStyle</code> protocol into this method to be specific in what shows up</li>
    <li>Apple extended <code class="language-plaintext highlighter-rouge">FormatStyle</code> with static properties and methods to give you easy access to special format styles</li>
    <li>The <code class="language-plaintext highlighter-rouge">Text</code> View in SwiftUI accepts a <code class="language-plaintext highlighter-rouge">FormatStyle</code> as an optional second parameter to simplify sting output</li>
  </ol>

  <p>I’ll refer you to <a href="https://goshdarnformatstyle.com/#the-basics">goshdarnformatstyle.com</a> for a lot more detail as to what you can do, so we can fully focus on our topic at hand.</p>
</blockquote>

<p>There’s a whole host of date format styles available to us that represent nearly every possible use case for displaying dates. And all of them support localization out of the box:</p>

<ul>
  <li><a href="https://goshdarnformatstyle.com/date-styles/">Single Date Styles</a>
    <ul>
      <li><a href="https://goshdarnformatstyle.com/date-styles/#compositing">Compositing</a></li>
      <li><a href="https://goshdarnformatstyle.com/date-styles/#date-and-time-single-date">Date and Time Style</a></li>
      <li><a href="https://goshdarnformatstyle.com/date-styles/#iso-8601-date-style-single-date">ISO 8601 Style</a></li>
      <li><a href="https://goshdarnformatstyle.com/date-styles/#relative-date-style-single-date">Relative Date Style</a></li>
      <li><a href="https://goshdarnformatstyle.com/date-styles/#verbatim-date-style-single-date">Verbatim Style</a> (Updated for Xcode 14)</li>
    </ul>
  </li>
  <li><a href="https://goshdarnformatstyle.com/date-range-styles/">Date Range Styles</a>
    <ul>
      <li><a href="https://goshdarnformatstyle.com/date-range-styles/#interval-date-style-date-range">Interval Style</a></li>
      <li><a href="https://goshdarnformatstyle.com/date-range-styles/#components-date-style-date-range">Components Style</a></li>
    </ul>
  </li>
</ul>

<p>I encourage you to <a href="https://gist.github.com/brettohland/ac2fbd1446bc7bb64da491587b010e3c">look through this gist</a> and marvel at just how comprehensive these various formatters are.</p>

<p>Apple has essentially split the functionality present in <code class="language-plaintext highlighter-rouge">DateFormatter</code> into discrete Format Style chunks, but not made it terribly obvious or discoverable what and how to use them.</p>

<p>So let’s change that a bit, by covering one specific use case: How can we replicate the <code class="language-plaintext highlighter-rouge">yyyy-MMM-dd</code> output from earlier?</p>

<h1 id="a-tale-of-two-date-styles">A Tale of Two Date Styles</h1>

<h2 id="using-dateformatstyle">Using Date.FormatStyle</h2>

<p>Let’s say, for example, that we want to replicate a <code class="language-plaintext highlighter-rouge">DateFormatter</code> that is set up to output a date in the following format: “yyyy-MMM-dd”. Knowing some of the available date format styles available to us, you may decide to simply do the following to grab each piece of the date using the <code class="language-plaintext highlighter-rouge">Date.FormatStyle</code> static methods and use string concatenation to put them together:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> twosdayDateComponents = <span class="type token">DateComponents</span>(
    year: <span class="number token">2_022</span>,
    month: <span class="number token">2</span>,
    day: <span class="number token">22</span>,
    hour: <span class="number token">2</span>,
    minute: <span class="number token">22</span>,
    second: <span class="number token">22</span>,
    nanosecond: <span class="number token">22</span>
)
<span class="keyword token">let</span> twosday = <span class="type token">Calendar</span>(identifier: .<span class="dotAccess token">gregorian</span>).<span class="call token">date</span>(from: twosdayDateComponents)!

<span class="comment token">// Outputs "2022-Feb-22"</span>
<span class="keyword token">let</span> outputString = <span class="string token">"</span>\(twosday.<span class="call token">formatted</span>(.<span class="dotAccess token">dateTime</span>.<span class="call token">year</span>()))<span class="string token">-</span>\(twosday.<span class="call token">formatted</span>(.<span class="dotAccess token">dateTime</span>.<span class="call token">month</span>()))<span class="string token">-</span>\(twosday.<span class="call token">formatted</span>(.<span class="dotAccess token">dateTime</span>.<span class="call token">day</span>()))<span class="string token">"</span>
</code></pre></div>

<p>Or, if you aren’t playing life on hard mode you may want to clean things up a bit:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> twosdayDateComponents = <span class="type token">DateComponents</span>(
    year: <span class="number token">2_022</span>,
    month: <span class="number token">2</span>,
    day: <span class="number token">22</span>,
    hour: <span class="number token">2</span>,
    minute: <span class="number token">22</span>,
    second: <span class="number token">22</span>,
    nanosecond: <span class="number token">22</span>
)
<span class="keyword token">let</span> twosday = <span class="type token">Calendar</span>(identifier: .<span class="dotAccess token">gregorian</span>).<span class="call token">date</span>(from: twosdayDateComponents)!

<span class="keyword token">let</span> yearString = twosday.<span class="call token">formatted</span>(.<span class="dotAccess token">dateTime</span>.<span class="call token">year</span>())
<span class="keyword token">let</span> monthString = twosday.<span class="call token">formatted</span>(.<span class="dotAccess token">dateTime</span>.<span class="call token">month</span>())
<span class="keyword token">let</span> dayString = twosday.<span class="call token">formatted</span>(.<span class="dotAccess token">dateTime</span>.<span class="call token">day</span>())

<span class="comment token">// Outputs "2022-Feb-22"</span>
<span class="keyword token">let</span> outputString = <span class="string token">"</span>\(yearString)<span class="string token">-</span>\(monthString)<span class="string token">-</span>\(dayString)<span class="string token">"</span>
</code></pre></div>

<p>Absolutely, this is a valid way of showing this information to the user. But it’s awkward to use in a reusable manner and we’ve effectively <a href="https://goshdarnformatstyle.com/swiftui/">lost the SwiftUI magic in the <code class="language-plaintext highlighter-rouge">Text</code> View</a>. If we’re needing to explicitly set the <code class="language-plaintext highlighter-rouge">Locale</code>, <code class="language-plaintext highlighter-rouge">TimeZone</code>, or <code class="language-plaintext highlighter-rouge">Calendar</code>, then this method further breaks down as we’ll have to specifically set it for each of the year, month, and day components.</p>

<p>Wouldn’t it be great to just do it once and be done with it?</p>

<h1 id="enter-the-verbatim-format-style">Enter: The Verbatim Format Style</h1>

<p>As a great philosopher once said: <a href="https://www.youtube.com/watch?v=pele5vptVgc">“Knowing is half the battle”</a>. <sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup></p>

<p>What the Verbatim Format Style gives us is the exact functionality we’re looking for, but without the need of that magic, tokenized date format string. Instead we have a new <code class="language-plaintext highlighter-rouge">Date.FormatString</code> which gives us access to all of our date components, but it’s type safe and easy to use.</p>

<blockquote>
  <p>Don’t use this for ISO8601 string output. <a href="https://goshdarnformatstyle.com/date-styles/#iso-8601-date-style-single-date">Apple built a purpose-built format style for this purpose</a>.</p>
</blockquote>

<p>The initializer for this style is fairly straightforward:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">public init</span>(format: <span class="type token">Date</span>.<span class="type token">FormatString</span>, locale: <span class="type token">Locale</span>? = <span class="keyword token">nil</span>, timeZone: <span class="type token">TimeZone</span>, calendar: <span class="type token">Calendar</span>)
</code></pre></div>

<p>The <code class="language-plaintext highlighter-rouge">Locale</code>, <code class="language-plaintext highlighter-rouge">TimeZone</code>, and <code class="language-plaintext highlighter-rouge">Calendar</code> parameters are self explanatory, and should inform you that this format style is a serious style for serious developers needing serious date strings. But what in the world is a <code class="language-plaintext highlighter-rouge">Date.FormatString</code>?</p>

<p>Well, this is where the magic is going to happen.</p>

<p>Let’s think about our example from earlier when we manually built out our date string using <code class="language-plaintext highlighter-rouge">Date.FormatStyle</code> static methods to access various date components:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> outputString = <span class="string token">"</span>\(twosday.<span class="call token">formatted</span>(.<span class="dotAccess token">dateTime</span>.<span class="call token">year</span>()))<span class="string token">-</span>\(twosday.<span class="call token">formatted</span>(.<span class="dotAccess token">dateTime</span>.<span class="call token">month</span>()))<span class="string token">-</span>\(twosday.<span class="call token">formatted</span>(.<span class="dotAccess token">dateTime</span>.<span class="call token">day</span>()))<span class="string token">"</span>
</code></pre></div>

<p>We’re using standard Swift string interpolation to get our final string (essentially everything between Swift magical <code class="language-plaintext highlighter-rouge">\()</code> characters). But because that same standard string interpolation has no idea about the domain of the problem that we’re trying to solve, we have to explicitly tell it about how to create the year, month, and day values.</p>

<p>As of Swift 5.0, we actually have the ability to get the compiler into night classes and teach it about what we want to do by using the <code class="language-plaintext highlighter-rouge">ExpressibleByStringInterpolation</code> protocol. And as luck would have it, <code class="language-plaintext highlighter-rouge">Date.FormatString</code> conforms to it and does just that.</p>

<blockquote>
  <p>If you aren’t familiar with <code class="language-plaintext highlighter-rouge">ExpressibleByStringInterpolation</code>, I honestly can’t blame you.  <a href="https://developer.apple.com/documentation/swift/expressiblebystringinterpolation">Apple’s documentation is pretty sparse</a>, but everyone’s favourite Mattt <a href="https://nshipster.com/expressiblebystringinterpolation/">wrote a great article in 2019 on NSHipster</a> which covered it in great detail (hilariously enough where he uses date formatting as an example).</p>
</blockquote>

<p><code class="language-plaintext highlighter-rouge">Date.FormatString</code> defines ways of accessing <em>all</em> of the date components available to us with the Unix standard date format patterns, but in a type safe and discoverable manner.</p>

<p>To output “yyyy-MMM-dd”, it’s as easy as:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> formatString: <span class="type token">Date</span>.<span class="type token">FormatString</span> = <span class="string token">"</span>\(year: .<span class="dotAccess token">defaultDigits</span>)<span class="string token">-</span>\(month: .<span class="dotAccess token">abbreviated</span>)<span class="string token">-</span>\(day: .<span class="dotAccess token">twoDigits</span>)<span class="string token">"</span>
</code></pre></div>

<p>Yes, this line of code is longer than our original <code class="language-plaintext highlighter-rouge">DateFormatter</code> format string, but what we’ve lost in horizontal compactness, we’ve gained five-fold in readability and discoverability. You can read this and reason out exactly what’s going to be output by this string and tweak it until it’s exactly what you need.</p>

<p>You can access:</p>

<ul>
  <li><a href="https://goshdarnformatstyle.com/date-styles/#era-token">Era</a></li>
  <li><a href="https://goshdarnformatstyle.com/date-styles/#year-token">Year</a></li>
  <li><a href="https://goshdarnformatstyle.com/date-styles/#yearforweekofyear-token">YearForWeekOfYear</a></li>
  <li><a href="https://goshdarnformatstyle.com/date-styles/#cyclicyear-token">CyclicYear</a></li>
  <li><a href="https://goshdarnformatstyle.com/date-styles/#quarter-token">Quarter</a></li>
  <li><a href="https://goshdarnformatstyle.com/date-styles/#month-token">Month</a></li>
  <li><a href="https://goshdarnformatstyle.com/date-styles/#week-token">Week</a></li>
  <li><a href="https://goshdarnformatstyle.com/date-styles/#day-token">Day</a></li>
  <li><a href="https://goshdarnformatstyle.com/date-styles/#dayofyear-token">DayOfYear</a></li>
  <li><a href="https://goshdarnformatstyle.com/date-styles/#weekday-token">Weekday</a></li>
  <li><a href="https://goshdarnformatstyle.com/date-styles/#dayperiod-token">DayPeriod</a></li>
  <li><a href="https://goshdarnformatstyle.com/date-styles/#minute-token">Minute</a></li>
  <li><a href="https://goshdarnformatstyle.com/date-styles/#second-token">Second</a></li>
  <li><a href="https://goshdarnformatstyle.com/date-styles/#secondfraction-token">SecondFraction</a></li>
  <li><a href="https://goshdarnformatstyle.com/date-styles/#timezone-token">TimeZone</a></li>
  <li><a href="https://goshdarnformatstyle.com/date-styles/#standalonequarter-token">StandaloneQuarter</a></li>
  <li><a href="https://goshdarnformatstyle.com/date-styles/#standalonemonth-token">StandaloneMonth</a></li>
  <li><a href="https://goshdarnformatstyle.com/date-styles/#standaloneweekday-token">StandaloneWeekday</a></li>
  <li><a href="https://goshdarnformatstyle.com/date-styles/#verbatimhour-token">VerbatimHour</a></li>
</ul>

<p>And each of these date components have quite a few customization options available.</p>

<p>(Did I mention I created a <a href="https://goshdarnformatstyle.com/date-styles/#verbatim-date-style-single-date">whole gosh darned website to document these</a>?)</p>

<p>Armed with this new knowledge, you quickly put it in practice:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> twosdayDateComponents = <span class="type token">DateComponents</span>(
    year: <span class="number token">2_022</span>,
    month: <span class="number token">2</span>,
    day: <span class="number token">22</span>,
    hour: <span class="number token">2</span>,
    minute: <span class="number token">22</span>,
    second: <span class="number token">22</span>,
    nanosecond: <span class="number token">22</span>
)
<span class="keyword token">let</span> twosday = <span class="type token">Calendar</span>(identifier: .<span class="dotAccess token">gregorian</span>).<span class="call token">date</span>(from: twosdayDateComponents)!

<span class="keyword token">let</span> verbatimStyle = <span class="type token">Date</span>.<span class="type token">VerbatimFormatStyle</span>(
    format: <span class="string token">"</span>\(year: .<span class="dotAccess token">defaultDigits</span>)<span class="string token">-</span>\(month: .<span class="dotAccess token">abbreviated</span>)<span class="string token">-</span>\(day: .<span class="dotAccess token">twoDigits</span>)<span class="string token">"</span>,
    timeZone: .<span class="dotAccess token">autoupdatingCurrent</span>,
    calendar: <span class="type token">Calendar</span>(identifier: .<span class="dotAccess token">gregorian</span>)
)

twosday.<span class="call token">formatted</span>(verbatimStyle) <span class="comment token">// "2022-M02-22"???????</span>
</code></pre></div>

<p><code class="language-plaintext highlighter-rouge">"2022-M02-22"</code>? Why in the world is our month being output as <code class="language-plaintext highlighter-rouge">M02</code>?</p>

<h1 id="chekhovs-optional-locale">Chekhov’s Optional Locale</h1>

<p>You might have noticed in the above example that I didn’t provide the <code class="language-plaintext highlighter-rouge">locale</code> parameter to the initializer as it’s an optional value that defaults to <code class="language-plaintext highlighter-rouge">nil</code>. This specifically is the problem as the system has no idea how to display the month to us. You can simply add it to fix our issue:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> twosdayDateComponents = <span class="type token">DateComponents</span>(
    year: <span class="number token">2_022</span>,
    month: <span class="number token">2</span>,
    day: <span class="number token">22</span>,
    hour: <span class="number token">2</span>,
    minute: <span class="number token">22</span>,
    second: <span class="number token">22</span>,
    nanosecond: <span class="number token">22</span>
)
<span class="keyword token">let</span> twosday = <span class="type token">Calendar</span>(identifier: .<span class="dotAccess token">gregorian</span>).<span class="call token">date</span>(from: twosdayDateComponents)!

<span class="keyword token">let</span> verbatimStyle = <span class="type token">Date</span>.<span class="type token">VerbatimFormatStyle</span>(
    format: <span class="string token">"</span>\(year: .<span class="dotAccess token">defaultDigits</span>)<span class="string token">-</span>\(month: .<span class="dotAccess token">abbreviated</span>)<span class="string token">-</span>\(day: .<span class="dotAccess token">twoDigits</span>)<span class="string token">"</span>,
    locale: <span class="type token">Locale</span>(identifier: <span class="string token">"en_US"</span>),
    timeZone: .<span class="dotAccess token">autoupdatingCurrent</span>,
    calendar: <span class="type token">Calendar</span>(identifier: .<span class="dotAccess token">gregorian</span>)
)

twosday.<span class="call token">formatted</span>(verbatimStyle) <span class="comment token">// "2022-Feb-22"</span>
</code></pre></div>

<p>Success.</p>

<h1 id="an-aside-about-calendars--locales">An Aside About Calendars &amp; Locales</h1>

<p>One thing that we need to remember as we’re using this format style is that our choices for <code class="language-plaintext highlighter-rouge">TimeZone</code> and <code class="language-plaintext highlighter-rouge">Calendar</code> have interesting side-effects that may not be obvious to us. As a developer based in Canada, I rarely have to consider the effects that using the Buddhist or Hebrew calendars might do to my code.</p>

<p>In the example above, you’ll notice that I explicitly set the <code class="language-plaintext highlighter-rouge">Locale</code> to be US English (<code class="language-plaintext highlighter-rouge">Locale(identifier: "en_US")</code>) and the <code class="language-plaintext highlighter-rouge">Calendar</code> to be Gregorian (<code class="language-plaintext highlighter-rouge">Calendar(identifier: .gregorian)</code>). Since the general goals of this format style is to have a fixed date format string as the output, it makes a lot of sense to set these to guarantee the output.</p>

<p>In cases where that need isn’t so fixed (maybe just showing the user a specifically formatted string), then it might make sense to set the <code class="language-plaintext highlighter-rouge">Locale</code>, <code class="language-plaintext highlighter-rouge">TimeZone</code>, and <code class="language-plaintext highlighter-rouge">Calendar</code> to <code class="language-plaintext highlighter-rouge">.autoupdatingCurrent</code>.</p>

<p>I will warn you though, it seems that using <code class="language-plaintext highlighter-rouge">.autoupdatingCurrent</code> for the <code class="language-plaintext highlighter-rouge">Locale</code> will override any set <code class="language-plaintext highlighter-rouge">Calendar</code> parameter used to create the format style.</p>

<div class="highlight"><pre class="splash"><code><span class="comment token">// By using `.autoupdatingCurrent` for the `Locale`, the calendar parameter is ignored in the output string.</span>
<span class="type token">Date</span>.<span class="type token">VerbatimFormatStyle</span>(
    format: <span class="string token">"</span>\(year: .<span class="dotAccess token">defaultDigits</span>)<span class="string token">-</span>\(month: .<span class="dotAccess token">abbreviated</span>)<span class="string token">-</span>\(day: .<span class="dotAccess token">twoDigits</span>)<span class="string token">"</span>,
    locale: .<span class="dotAccess token">autoupdatingCurrent</span>,
    timeZone: .<span class="dotAccess token">autoupdatingCurrent</span>,
    calendar: <span class="type token">Calendar</span>(identifier: .<span class="dotAccess token">buddhist</span>)
).<span class="call token">format</span>(twosday) <span class="comment token">// "2022-Feb-22"

// By setting an explicit Locale, the calendar parameter is used.</span>
<span class="type token">Date</span>.<span class="type token">VerbatimFormatStyle</span>(
    format: <span class="string token">"</span>\(year: .<span class="dotAccess token">defaultDigits</span>)<span class="string token">-</span>\(month: .<span class="dotAccess token">abbreviated</span>)<span class="string token">-</span>\(day: .<span class="dotAccess token">twoDigits</span>)<span class="string token">"</span>,
    locale: <span class="type token">Locale</span>(identifier: <span class="string token">"en_US"</span>),
    timeZone: .<span class="dotAccess token">autoupdatingCurrent</span>,
    calendar: <span class="type token">Calendar</span>(identifier: .<span class="dotAccess token">buddhist</span>)
).<span class="call token">format</span>(twosday) <span class="comment token">// "2065-Feb-22"

// When omitting the locale, the calendar parameter is used.</span>
<span class="type token">Date</span>.<span class="type token">VerbatimFormatStyle</span>(
    format: <span class="string token">"</span>\(year: .<span class="dotAccess token">defaultDigits</span>)<span class="string token">-</span>\(month: .<span class="dotAccess token">abbreviated</span>)<span class="string token">-</span>\(day: .<span class="dotAccess token">twoDigits</span>)<span class="string token">"</span>,
    timeZone: .<span class="dotAccess token">autoupdatingCurrent</span>,
    calendar: <span class="type token">Calendar</span>(identifier: .<span class="dotAccess token">buddhist</span>)
).<span class="call token">format</span>(twosday) <span class="comment token">// "2565-M02-22"</span>
</code></pre></div>

<p>Is this a bug? Possibly. (Feedback FB11806265 was submitted)</p>

<h1 id="one-final-convenience">One Final Convenience</h1>

<p>If you’ve used or have been comfortable with format styles in the past, you know that creating and holding onto instances of formats styles is a clumsy way of doing things. Apple has extended <code class="language-plaintext highlighter-rouge">FormatStyle</code> in ways to expose these styles to you in simple ways.</p>

<p>For all platforms supported by Xcode 13 <sup id="fnref:1:2" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>, the simple accessor for the verbatim format style is missing which did force you to create and hold onto instances. Thankfully Apple did fix this in Xcode 14 <sup id="fnref:2:1" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup> and you can simply do this:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> twosdayDateComponents = <span class="type token">DateComponents</span>(
    year: <span class="number token">2_022</span>,
    month: <span class="number token">2</span>,
    day: <span class="number token">22</span>,
    hour: <span class="number token">2</span>,
    minute: <span class="number token">22</span>,
    second: <span class="number token">22</span>,
    nanosecond: <span class="number token">22</span>
)
<span class="keyword token">let</span> twosday = <span class="type token">Calendar</span>(identifier: .<span class="dotAccess token">gregorian</span>).<span class="call token">date</span>(from: twosdayDateComponents)!

<span class="comment token">// "2022-Feb-22"</span>
twosday.<span class="call token">formatted</span>(
    .<span class="call token">verbatim</span>(<span class="string token">"</span>\(year: .<span class="dotAccess token">defaultDigits</span>)<span class="string token">-</span>\(month: .<span class="dotAccess token">abbreviated</span>)<span class="string token">-</span>\(day: .<span class="dotAccess token">twoDigits</span>)<span class="string token">"</span>,
    locale: .<span class="dotAccess token">autoupdatingCurrent</span>,
    timeZone: .<span class="dotAccess token">autoupdatingCurrent</span>,
    calendar: .<span class="dotAccess token">autoupdatingCurrent</span>)
)
</code></pre></div>

<p>If you are targeting those Xcode 13 platforms <sup id="fnref:1:3" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>, you can easily backport this functionality by simply adding this extension to your project:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">public extension</span> <span class="type token">FormatStyle</span> <span class="keyword token">where</span> <span class="type token">Self</span> == <span class="type token">Date</span>.<span class="type token">VerbatimFormatStyle</span> {
    <span class="keyword token">static func</span> verbatim(
        <span class="keyword token">_</span> format: <span class="type token">Date</span>.<span class="type token">FormatString</span>,
        locale: <span class="type token">Locale</span>? = <span class="keyword token">nil</span>,
        timeZone: <span class="type token">TimeZone</span>,
        calendar: <span class="type token">Calendar</span>
    ) -&gt; <span class="type token">Date</span>.<span class="type token">VerbatimFormatStyle</span> {
        <span class="keyword token">return</span> <span class="type token">Date</span>.<span class="type token">VerbatimFormatStyle</span>(format: format, locale: locale, timeZone: timeZone, calendar: calendar)
    }
}
</code></pre></div>

<p>And there we have it, you now know everything that there is to know to get started using this modern replacement for the <code class="language-plaintext highlighter-rouge">DateFormatters</code> of old. Format styles as a concept are fantastic once you’ve learned the ins and outs of the various styles available to you by Apple.</p>

<p>Happy verbatiming!</p>

<hr />
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Xcode 13 supports iOS 15.0+, iPadOS 15.0+, Mac Catalyst 15.0+, tvOS 15.0+, watchOS 8.0+, macOS 12.0+ <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:1:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:1:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:1:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>Xcode 14 supports iOS 16.0+, iPadOS 16.0+, Mac Catalyst 16.0+, tvOS 16.0+, watchOS 9.0+, macOS 13.0+ <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:2:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>When I started my journey into documenting the various format styles, the Verbatim Format Style was the one that had almost no information written about it online. The Apple docs were non-existent, and the one bit of info I did find was on <a href="https://forums.swift.org/t/new-date-formatstyle-anyway-to-do-24-hour/52994/38?page=2">the Swift language forums about outputting 24 hour time</a>. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>brett ohland</name></author><category term="ios" /><category term="development" /><category term="swift" /><category term="formatstyle" /><summary type="html"><![CDATA[Apple gives us a modern replacement for the magical format strings we pass to DateFormatters, let's talk about the Verbatim Format Style.]]></summary></entry><entry><title type="html">Over 3,000 Words On What The Measurement Type Is And Why You Should Be Using It</title><link href="https://ampersandsoftworks.com/posts/measurements-and-their-formatting/index.html" rel="alternate" type="text/html" title="Over 3,000 Words On What The Measurement Type Is And Why You Should Be Using It" /><published>2022-09-30T06:21:00-06:00</published><updated>2022-09-30T06:21:00-06:00</updated><id>https://ampersandsoftworks.com/posts/measurements-and-their-formatting/measurements-and-their-formatting</id><content type="html" xml:base="https://ampersandsoftworks.com/posts/measurements-and-their-formatting/index.html"><![CDATA[<p>Foundation’s <code class="language-plaintext highlighter-rouge">Measurement</code> type is an incredibly useful tool in an Apple developer’s toolkit. It’s a purpose-built type for storing, converting, and calculating the sum of physical measurements with a powerful localization system built on top of it. It’s a relatively new API that was introduced with Xcode 8 in 2016 and is supported by iOS 10+, macOS 10.12+, Mac Catalyst 13.0+, tvOS 10.0+, watchOS 3.0+.</p>

<p>From personal experience, there’s a hesitation to use this type in production code. I think the biggest reasons are that Apple hasn’t done a great job in selling devs on the benefits of the type, and that the powerful localization features were locked behind a clunky <code class="language-plaintext highlighter-rouge">MeasurementFormatter</code>.</p>

<p>It may seem overkill to wrap that temperature or distance value in an addition layer of complexity, especially if your app will never be released outside of the US market. But I’d like to change your mind on that.</p>

<p>Let’s walk though <code class="language-plaintext highlighter-rouge">Measurement</code>, how to use it, how to convert things using it, and how to localize its output. At the end I hope I’ve convinced you to use it in your code starting tomorrow.</p>

<blockquote>
  <p>This post is all about the Swift API for <code class="language-plaintext highlighter-rouge">Measurement</code>. Know that these are accessible in Objective-C as <code class="language-plaintext highlighter-rouge">NSMeasurement</code>, and nearly everything mentioned in this post is available to you. Unfortunately the <code class="language-plaintext highlighter-rouge">Measurement&lt;UnitType&gt;.FormatStyle</code> is Swift-only, you’ll need to rely on the <code class="language-plaintext highlighter-rouge">NSMeasurementFormatter</code> for localization.</p>
</blockquote>

<h2 id="measurement-basics">Measurement Basics</h2>

<p>To be a good developer in a type-safe language, you should be using the appropriate types for the data in question. Strings should be <code class="language-plaintext highlighter-rouge">String</code>, Numbers should be <code class="language-plaintext highlighter-rouge">Int</code>, <code class="language-plaintext highlighter-rouge">Float</code> or <code class="language-plaintext highlighter-rouge">Decimal</code> as needed and those measurements of physical properties should be a <code class="language-plaintext highlighter-rouge">Measurement</code>.</p>

<p>The <code class="language-plaintext highlighter-rouge">Measurement</code> type requires that you associate your measurement value with a unit. Makes sense.</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">struct</span> Measurement&lt;UnitType&gt; <span class="keyword token">where</span> <span class="type token">UnitType</span> : <span class="type token">Unit</span>
</code></pre></div>

<p>But here we have run into the first knowledge hurdle about the API: <strong>What units are even supported?</strong></p>

<p>As of Xcode 14, this is the canonical list of Dimensions <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> that are supported:</p>

<table>
  <thead>
    <tr>
      <th>Dimension <sup id="fnref:1:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup></th>
      <th>Description</th>
      <th>Base unit</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unitacceleration">Unit Acceleration</a></td>
      <td>Unit of measure for acceleration</td>
      <td>meters per second squared (m/s²)</td>
    </tr>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unitangle">Unit Angle</a></td>
      <td>Unit of measure for planar angle and rotation</td>
      <td>degrees (°)</td>
    </tr>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unitarea">Unit Area</a></td>
      <td>Unit of measure for area</td>
      <td>square meters (m²)</td>
    </tr>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unitconcentrationmass">Unit Mass</a></td>
      <td>Unit of measure for concentration of mass</td>
      <td>grams per liter (g/L)</td>
    </tr>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unitdispersion">Unit Dispersion</a></td>
      <td>Unit of measure for dispersion</td>
      <td>parts per million (ppm)</td>
    </tr>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unitduration">Unit Duration</a></td>
      <td>Unit of measure for duration of time</td>
      <td>seconds (sec)</td>
    </tr>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unitelectriccharge">Unit Charge</a></td>
      <td>Unit of measure for electric charge</td>
      <td>coulombs (C)</td>
    </tr>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unitelectriccurrent">Unit Current</a></td>
      <td>Unit of measure for electric current</td>
      <td>amperes (A)</td>
    </tr>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unitelectricpotentialdifference">Unit Difference</a></td>
      <td>Unit of measure for electric potential difference</td>
      <td>volts (V)</td>
    </tr>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unitelectricresistance">Unit Resistance</a></td>
      <td>Unit of measure for electric resistance</td>
      <td>ohms (Ω)</td>
    </tr>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unitenergy">Unit Energy</a></td>
      <td>Unit of measure for energy</td>
      <td>joules (J)</td>
    </tr>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unitfrequency">Unit Frequency</a></td>
      <td>Unit of measure for frequency</td>
      <td>hertz (Hz)</td>
    </tr>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unitfuelefficiency">Unit Efficiency</a></td>
      <td>Unit of measure for fuel efficiency</td>
      <td>liters per 100 kilometers (L/100km)</td>
    </tr>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unitilluminance">Unit Illuminance</a></td>
      <td>Unit of measure for illuminance</td>
      <td>lux (lx)</td>
    </tr>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unitinformationstorage">Unit Storage</a></td>
      <td>Unit of measure for quantities of information</td>
      <td>bytes (b)</td>
    </tr>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unitlength">Unit Length</a></td>
      <td>Unit of measure for length</td>
      <td>meters (m)</td>
    </tr>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unitmass">Unit Mass</a></td>
      <td>Unit of measure for mass</td>
      <td>kilograms (kg)</td>
    </tr>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unitpower">Unit Power</a></td>
      <td>Unit of measure for power</td>
      <td>watts (W)</td>
    </tr>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unitpressure">Unit Pressure</a></td>
      <td>Unit of measure for pressure</td>
      <td>newtons per square meter (N/m²)</td>
    </tr>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unitspeed">Unit Speed</a></td>
      <td>Unit of measure for speed</td>
      <td>meters per second (m/s)</td>
    </tr>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unittemperature">Unit Temperature</a></td>
      <td>Unit of measure for temperature</td>
      <td>kelvin (K)</td>
    </tr>
    <tr>
      <td><a href="https://developer.apple.com/documentation/foundation/unitvolume">Unit Volume</a></td>
      <td>Unit of measure for volume</td>
      <td>liters (L)</td>
    </tr>
  </tbody>
</table>

<p>Each of these Dimensions have a number of units available. Here’s every unit for <code class="language-plaintext highlighter-rouge">UnitLength</code> as an example:</p>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Method</th>
      <th>Symbol</th>
      <th>Coefficient</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Megameters</td>
      <td>megameters</td>
      <td>Mm</td>
      <td>1000000.0</td>
    </tr>
    <tr>
      <td>Kilometers</td>
      <td>kilometers</td>
      <td>kM</td>
      <td>1000.0</td>
    </tr>
    <tr>
      <td>Hectometers</td>
      <td>hectometers</td>
      <td>hm</td>
      <td>100.0</td>
    </tr>
    <tr>
      <td>Decameters</td>
      <td>decameters</td>
      <td>dam</td>
      <td>10.0</td>
    </tr>
    <tr>
      <td>Meters</td>
      <td>meters</td>
      <td>m</td>
      <td>1.0</td>
    </tr>
    <tr>
      <td>Decimeters</td>
      <td>decimeters</td>
      <td>dm</td>
      <td>0.1</td>
    </tr>
    <tr>
      <td>Centimeters</td>
      <td>centimeters</td>
      <td>cm</td>
      <td>0.01</td>
    </tr>
    <tr>
      <td>Millimeters</td>
      <td>millimeters</td>
      <td>mm</td>
      <td>0.001</td>
    </tr>
    <tr>
      <td>Micrometers</td>
      <td>micrometers</td>
      <td>µm</td>
      <td>0.000001</td>
    </tr>
    <tr>
      <td>Nanometers</td>
      <td>nanometers</td>
      <td>nm</td>
      <td>1e-9</td>
    </tr>
    <tr>
      <td>Picometers</td>
      <td>picometers</td>
      <td>pm</td>
      <td>1e-12</td>
    </tr>
    <tr>
      <td>Inches</td>
      <td>inches</td>
      <td>in</td>
      <td>0.0254</td>
    </tr>
    <tr>
      <td>Feet</td>
      <td>feet</td>
      <td>ft</td>
      <td>0.3048</td>
    </tr>
    <tr>
      <td>Yards</td>
      <td>yards</td>
      <td>yd</td>
      <td>0.9144</td>
    </tr>
    <tr>
      <td>Miles</td>
      <td>miles</td>
      <td>mi</td>
      <td>1609.34</td>
    </tr>
    <tr>
      <td>Scandinavian Miles</td>
      <td>scandinavianMiles</td>
      <td>smi</td>
      <td>10000</td>
    </tr>
    <tr>
      <td>Light Years</td>
      <td>lightyears</td>
      <td>ly</td>
      <td>9.461e+15</td>
    </tr>
    <tr>
      <td>Nautical Miles</td>
      <td>nauticalMiles</td>
      <td>NM</td>
      <td>1852</td>
    </tr>
    <tr>
      <td>Fathoms</td>
      <td>fathoms</td>
      <td>ftm</td>
      <td>1.8288</td>
    </tr>
    <tr>
      <td>Furlongs</td>
      <td>furlongs</td>
      <td>fur</td>
      <td>201.168</td>
    </tr>
    <tr>
      <td>Astronomical Units</td>
      <td>astronomicalUnits</td>
      <td>ua</td>
      <td>1.496e+11</td>
    </tr>
    <tr>
      <td>Parsecs</td>
      <td>parsecs</td>
      <td>pc</td>
      <td>3.086e+16</td>
    </tr>
  </tbody>
</table>

<p>I’d recommend that you check out <a href="https://developer.apple.com/documentation/foundation/dimension">Apple’s documentation for each of the Dimensions</a> to get a good idea of every possible unit available to you.</p>

<p>Creating a new measurement is as easy as setting the value and declaring it’s <code class="language-plaintext highlighter-rouge">Dimension</code> and it’s unit:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> speedLimit = <span class="type token">Measurement</span>(value: <span class="number token">100</span>, unit: <span class="type token">UnitSpeed</span>.<span class="property token">kilometersPerHour</span>)
<span class="keyword token">let</span> myHeight = <span class="type token">Measurement</span>(value: <span class="number token">190</span>, unit: <span class="type token">UnitLength</span>.<span class="property token">centimeters</span>)
<span class="keyword token">let</span> drivingDistance = <span class="type token">Measurement</span>(value: <span class="number token">200</span>, unit: <span class="type token">UnitLength</span>.<span class="property token">kilometers</span>)
<span class="keyword token">let</span> averageBaseballThrow = <span class="type token">Measurement</span>(value: <span class="number token">70</span>, unit: <span class="type token">UnitLength</span>.<span class="property token">feet</span>)
<span class="keyword token">let</span> bodyTemperature = <span class="type token">Measurement</span>(value: <span class="number token">98.5</span>, unit: <span class="type token">UnitTemperature</span>.<span class="property token">fahrenheit</span>)
<span class="keyword token">let</span> aNiceDay = <span class="type token">Measurement</span>(value: <span class="number token">25.0</span>, unit: <span class="type token">UnitTemperature</span>.<span class="property token">celsius</span>)

<span class="comment token">// Alternatively, declare the Dimension and only set the unit.</span>
<span class="keyword token">let</span> coldDay: <span class="type token">Measurement</span>&lt;<span class="type token">UnitTemperature</span>&gt; = .<span class="keyword token">init</span>(value: -<span class="number token">30</span>, unit: .<span class="dotAccess token">celsius</span>)
</code></pre></div>

<p>Once created, you can think of that value as a unit-agnostic representation of the <code class="language-plaintext highlighter-rouge">Dimension</code>. While you had initialized that speed limit as kilometers per hour, you should immediately stop thinking of it in that way. This is the completely opposite of simply storing these values as an <code class="language-plaintext highlighter-rouge">Int</code>, <code class="language-plaintext highlighter-rouge">Double</code>, <code class="language-plaintext highlighter-rouge">Float</code>, or <code class="language-plaintext highlighter-rouge">Decimal</code> as you’d be needing to keep that unit in mind at all times when working with the unit.</p>

<p>However, it is important to know that under the hood the value is tied to the unit it’s initialized with. This only really comes into play when you’re using the <code class="language-plaintext highlighter-rouge">MeasurementFormatStyle</code> to output your measurement as a nice looking string. This is detailed later on.</p>

<h2 id="calculating-sums">Calculating Sums</h2>

<p>Once you have your unit agnostic dimensional value, you can then do math on several unit agnostic dimensional values! Units no longer matter, as long as everything shares the same <code class="language-plaintext highlighter-rouge">Dimension</code>:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> myWalkingDistance = <span class="type token">Measurement</span>(value: <span class="number token">20</span>, unit: <span class="type token">UnitLength</span>.<span class="property token">fathoms</span>)
<span class="keyword token">let</span> yourSwimmingDistance = <span class="type token">Measurement</span>(value: <span class="number token">10</span>, unit: <span class="type token">UnitLength</span>.<span class="property token">nauticalMiles</span>)

<span class="keyword token">let</span> ourDistance = myWalkingDistance + yourSwimmingDistance <span class="comment token">// 18556.576 m</span>
</code></pre></div>

<h2 id="unit-conversions">Unit Conversions</h2>

<p>The best part about having your value stored in an agnostic fashion is that you can rely on the system to do your unit conversions for you.</p>

<p>To use, simply call <code class="language-plaintext highlighter-rouge">converted(to:)</code> on the Measurement which will return a Measurement in that new unit.</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> calgaryTemperature = <span class="type token">Measurement</span>(value: <span class="number token">9</span>, unit: <span class="type token">UnitTemperature</span>.<span class="property token">celsius</span>)
<span class="keyword token">let</span> bostonTemperature = <span class="type token">Measurement</span>(value: <span class="number token">58</span>, unit: <span class="type token">UnitTemperature</span>.<span class="property token">fahrenheit</span>)
<span class="keyword token">let</span> marsTemperature = <span class="type token">Measurement</span>(value: -<span class="number token">112</span>, unit: <span class="type token">UnitTemperature</span>.<span class="property token">fahrenheit</span>)
<span class="keyword token">let</span> surfaceOfTheSunTemperature = <span class="type token">Measurement</span>(value: <span class="number token">5772</span>, unit: <span class="type token">UnitTemperature</span>.<span class="property token">kelvin</span>)

calgaryTemperature.<span class="call token">converted</span>(to: .<span class="dotAccess token">celsius</span>) <span class="comment token">// 9.0 °C</span>
bostonTemperature.<span class="call token">converted</span>(to: .<span class="dotAccess token">celsius</span>) <span class="comment token">// 14.444444444446788 °C</span>
marsTemperature.<span class="call token">converted</span>(to: .<span class="dotAccess token">celsius</span>) <span class="comment token">// -79.99999999999841 °C</span>
surfaceOfTheSunTemperature.<span class="call token">converted</span>(to: .<span class="dotAccess token">celsius</span>) <span class="comment token">// 5498.85 °C</span>
</code></pre></div>

<p>There’s also the <code class="language-plaintext highlighter-rouge">convert(to:)</code> method, which will modify the internal unit of that Measurement. Again, this is used in specific cases when outputting string values.</p>

<h1 id="custom-units">Custom Units</h1>

<p><a href="https://developer.apple.com/documentation/foundation/dimension">Apple outlines how to create a custom Dimension, and extending Dimensions with a custom <code class="language-plaintext highlighter-rouge">Unit</code> on a provided <code class="language-plaintext highlighter-rouge">Dimension</code>.</a></p>

<div class="highlight"><pre class="splash"><code><span class="comment token">// A custom one-off Unit (https://en.wikipedia.org/wiki/Smoot)</span>
<span class="keyword token">let</span> smoots = <span class="type token">UnitLength</span>(symbol: <span class="string token">"smoot"</span>, converter: <span class="type token">UnitConverterLinear</span>(coefficient: <span class="number token">1.70180</span>))

<span class="comment token">// Extending a Dimension to include a custom Unit</span>
<span class="keyword token">extension</span> <span class="type token">UnitSpeed</span> {
    <span class="keyword token">static let</span> furlongPerFortnight = <span class="type token">UnitSpeed</span>(
        symbol: <span class="string token">"fur/ftn"</span>,
        converter: <span class="type token">UnitConverterLinear</span>(coefficient: <span class="number token">201.168</span> / <span class="number token">1209600.0</span>)
    )
}

<span class="comment token">// Fully custom Dimension subclass</span>
<span class="keyword token">class</span> CustomRadioactivityUnit: <span class="type token">Dimension</span> {
    <span class="keyword token">static let</span> becquerel = <span class="type token">CustomRadioactivityUnit</span>(symbol: <span class="string token">"Bq"</span>, <span class="type token">UnitConverterLinear</span>(coefficient: <span class="number token">1.0</span>))
    <span class="keyword token">static let</span> curie = <span class="type token">CustomRadioactivityUnit</span>(symbol: <span class="string token">"Ci"</span>, <span class="type token">UnitConverterLinear</span>(coefficient: <span class="number token">3</span>.7e10))
    <span class="keyword token">static let</span> baseUnit = <span class="keyword token">self</span>.<span class="property token">becquerel</span>
}
</code></pre></div>

<h1 id="pretty-strings">Pretty Strings</h1>

<p><strong>TL;DR: Measurement outputs are localized, and this can cause unexpected output.</strong></p>

<p>Having your data nicely represented as unit agnostic values is one thing, but showing them to your users is another thing all together.</p>

<p>In the past, you had to rely on the <code class="language-plaintext highlighter-rouge">MeasurementFormatter</code> class to handle this job. It was clunky to work with as you had to initialize a new instance of the formatter for every different output style that you wanted to show. And similar to the <code class="language-plaintext highlighter-rouge">DateFormatter</code> initializing them is expensive so you <em>should</em> be storing these instances in a shared location to avoid re-creating them as much as humanly possible.</p>

<p>That all changed with iOS 15 and <code class="language-plaintext highlighter-rouge">FormatStyle</code>, which created an extremely simplified API to convert your data into pretty strings. Unfortunately, Apple’s documentation is sparse on this topic, so I <a href="https://goshdarnformatstyle.com">created an entire site to document and show off what this system can do</a>.</p>

<h2 id="simple-formatstyle-output">Simple FormatStyle Output</h2>

<p>At it’s most basic, you can simply call <code class="language-plaintext highlighter-rouge">.formatted()</code> onto any Measurement instance and get a nice looking string out of it:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> speedLimit = <span class="type token">Measurement</span>(value: <span class="number token">100</span>, unit: <span class="type token">UnitSpeed</span>.<span class="property token">kilometersPerHour</span>)
<span class="keyword token">let</span> myHeight = <span class="type token">Measurement</span>(value: <span class="number token">190</span>, unit: <span class="type token">UnitLength</span>.<span class="property token">centimeters</span>)
<span class="keyword token">let</span> drivingDistance = <span class="type token">Measurement</span>(value: <span class="number token">200</span>, unit: <span class="type token">UnitLength</span>.<span class="property token">kilometers</span>)
<span class="keyword token">let</span> averageBaseballThrow = <span class="type token">Measurement</span>(value: <span class="number token">70</span>, unit: <span class="type token">UnitLength</span>.<span class="property token">feet</span>)
<span class="keyword token">let</span> averageWeight = <span class="type token">Measurement</span>(value: <span class="number token">197.9</span>, unit: <span class="type token">UnitMass</span>.<span class="property token">pounds</span>)
<span class="keyword token">let</span> bodyTemperature = <span class="type token">Measurement</span>(value: <span class="number token">98.5</span>, unit: <span class="type token">UnitTemperature</span>.<span class="property token">fahrenheit</span>)

speedLimit.<span class="call token">formatted</span>() <span class="comment token">// "62 mph"</span>
myHeight.<span class="call token">formatted</span>() <span class="comment token">// "6.2 ft"</span>
drivingDistance.<span class="call token">formatted</span>() <span class="comment token">// "124 mi"</span>
averageBaseballThrow.<span class="call token">formatted</span>() <span class="comment token">// "70 ft"</span>
averageWeight.<span class="call token">formatted</span>() <span class="comment token">// "198 lb"</span>
bodyTemperature.<span class="call token">formatted</span>() <span class="comment token">// "98°F"</span>
</code></pre></div>

<p>Useful for debugging or simple output, but you almost certainly want more control of things.</p>

<p>To customize the output, we can access the <code class="language-plaintext highlighter-rouge">.measurement(width:usage:numberFormatStyle)</code> extension on <code class="language-plaintext highlighter-rouge">FormatStyle</code> and pass in different options for the three parameters.</p>

<table>
  <thead>
    <tr>
      <th>Parameter</th>
      <th>Accepted Type</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">width</code></td>
      <td><code class="language-plaintext highlighter-rouge">Measurement&lt;UnitType&gt;.FormatStyle.UnitWidth</code></td>
      <td>Sets how verbose the output is</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">usage</code> (optional)</td>
      <td><code class="language-plaintext highlighter-rouge">MeasurementFormatUnitUsage&lt;UnitType&gt;</code></td>
      <td>Sets how the unit will be used</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">numberFormatStyle</code> (optional)</td>
      <td><code class="language-plaintext highlighter-rouge">FloatingPointFormatStyle&lt;Double&gt;</code></td>
      <td>Sets the format style on the number</td>
    </tr>
  </tbody>
</table>

<p>Explaining how each of these parameters interact with each other, and how this all ties into the <code class="language-plaintext highlighter-rouge">Measurement&lt;UnitType&gt;.FormatStyle</code> struct that backs this whole feature means we need to discuss this system from the ground up.</p>

<h2 id="width-parameter">Width parameter</h2>

<p>Before we dive deeply into the details, we can cover the <code class="language-plaintext highlighter-rouge">width</code> parameter quickly. There are three possible options:</p>

<table>
  <thead>
    <tr>
      <th>Width</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.wide</code></td>
      <td>Displays the full unit description</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.abbreviated</code></td>
      <td>Displays an abbreviated unit description</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.narrow</code></td>
      <td>Displays the unit in the least number of characters</td>
    </tr>
  </tbody>
</table>

<p>In general, <code class="language-plaintext highlighter-rouge">wide</code> will spell out the name of the unit in the most verbose way possible. <code class="language-plaintext highlighter-rouge">.abbreviated</code> will output the shortened version of the unit, and <code class="language-plaintext highlighter-rouge">.narrow</code> will remove any whitespace characters.</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> gForce = <span class="type token">Measurement</span>(value: <span class="number token">1.0</span>, unit: <span class="type token">UnitAcceleration</span>.<span class="property token">gravity</span>)

gForce.<span class="call token">formatted</span>(.<span class="call token">measurement</span>(width: .<span class="dotAccess token">wide</span>)) <span class="comment token">// "1 g-force"</span>
gForce.<span class="call token">formatted</span>(.<span class="call token">measurement</span>(width: .<span class="dotAccess token">narrow</span>)) <span class="comment token">// "1G"</span>
gForce.<span class="call token">formatted</span>(.<span class="call token">measurement</span>(width: .<span class="dotAccess token">abbreviated</span>)) <span class="comment token">// "1 G"</span>
</code></pre></div>

<h2 id="the-nitty-and-the-gritty">The Nitty and the Gritty</h2>

<p><a href="https://goshdarnformatstyle.com/measurement-style/">TL;DR: I’ve updated goshdarnformatstyle.com with all of this info if you just want to see the available options.</a></p>

<p>I’ve detailed how the <code class="language-plaintext highlighter-rouge">FormatStyle</code> protocol works by creating one for my own custom type (the ISBN) <a href="https://ampersandsoftworks.com/posts/formatstyle-parseableformatstyle-and-your-custom-types/">in an earlier post</a>. It’s a great primer on understanding <em>how</em> the system is implemented under the hood by Apple’s engineers.</p>

<p>The <code class="language-plaintext highlighter-rouge">.formatted()</code> method on any Measurement instance accepts an optional parameter that conforms to the <a href="https://developer.apple.com/documentation/foundation/formatstyle"><code class="language-plaintext highlighter-rouge">FormatStyle</code> protocol</a>. The protocol itself is quite simple: You define an input type, an output type, and you get a method to do your conversion. That’s it.</p>

<p>We can create an instance of our format style by initializing an instance with it’s <code class="language-plaintext highlighter-rouge">Dimension</code>. This is how it’s defined in the documentation:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">public init</span>(
    width: <span class="type token">Measurement</span>&lt;<span class="type token">UnitType</span>&gt;.<span class="type token">FormatStyle</span>.<span class="type token">UnitWidth</span>,
    locale: <span class="type token">Locale</span> = .<span class="dotAccess token">autoupdatingCurrent</span>,
    usage: <span class="type token">MeasurementFormatUnitUsage</span>&lt;<span class="type token">UnitType</span>&gt; = .<span class="dotAccess token">general</span>,
    numberFormatStyle: <span class="type token">FloatingPointFormatStyle</span>&lt;<span class="type token">Double</span>&gt;? = <span class="keyword token">nil</span>
)
</code></pre></div>

<p>Interestingly enough, there’s a special initializer with an extra parameter that’s available only when you’re creating a new instance with the <code class="language-plaintext highlighter-rouge">UnitTemperature</code> Dimension:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">@available</span>(macOS <span class="number token">12.0</span>, iOS <span class="number token">15.0</span>, tvOS <span class="number token">15.0</span>, watchOS <span class="number token">8.0</span>, *)
<span class="keyword token">extension</span> <span class="type token">Measurement</span>.<span class="type token">FormatStyle</span> <span class="keyword token">where</span> <span class="type token">UnitType</span> == <span class="type token">UnitTemperature</span> {

    <span class="comment token">/// Hides the scale name. For example, "90°" rather than "90°F" or "90°C" with the `narrow` unit width, 
    /// or "90 degrees" rather than "90 degrees celcius" or "90 degrees fahrenheit" with the `wide` width.</span>
    <span class="keyword token">public var</span> hidesScaleName: <span class="type token">Bool</span>

    <span class="keyword token">public init</span>(
        width: <span class="type token">Measurement</span>&lt;<span class="type token">UnitType</span>&gt;.<span class="type token">FormatStyle</span>.<span class="type token">UnitWidth</span> = .<span class="dotAccess token">abbreviated</span>,
        locale: <span class="type token">Locale</span> = .<span class="dotAccess token">autoupdatingCurrent</span>,
        usage: <span class="type token">MeasurementFormatUnitUsage</span>&lt;<span class="type token">UnitType</span>&gt; = .<span class="dotAccess token">general</span>,
        hidesScaleName: <span class="type token">Bool</span> = <span class="keyword token">false</span>,
        numberFormatStyle: <span class="type token">FloatingPointFormatStyle</span>&lt;<span class="type token">Double</span>&gt;? = <span class="keyword token">nil</span>
    )
}
</code></pre></div>

<p>The extra option allows us to omit the output of the temperature scale in the final string. Thanks to the magic of protocol extensions with <code class="language-plaintext highlighter-rouge">where</code> clauses, this is only available when our <code class="language-plaintext highlighter-rouge">UnitType</code> is <code class="language-plaintext highlighter-rouge">UnitTemperature</code>.</p>

<p>With this understanding, we can create a new instance of our format style and use it to format our measurement by using the <code class="language-plaintext highlighter-rouge">.formatted()</code> method on the measurement <em>OR</em> using the <code class="language-plaintext highlighter-rouge">.format()</code> method on our format style.</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> myHeight = <span class="type token">Measurement</span>(value: <span class="number token">190</span>, unit: <span class="type token">UnitLength</span>.<span class="property token">centimeters</span>)
<span class="keyword token">let</span> measurementStyle = <span class="type token">Measurement</span>&lt;<span class="type token">UnitLength</span>&gt;.<span class="type token">FormatStyle</span>(width: .<span class="dotAccess token">wide</span>)

myHeight.<span class="call token">formatted</span>(measurementStyle) <span class="comment token">// "6.2 feet"</span>
measurementStyle.<span class="call token">format</span>(myHeight) <span class="comment token">// "6.2 feet"</span>
</code></pre></div>

<p>But this brings up some issues and questions related to the output: “6.2 feet”?</p>

<ol>
  <li>Why am I getting “feet” as my output when I created it using <code class="language-plaintext highlighter-rouge">UnitLength.centimetres</code>?</li>
  <li>Why am I getting fractional feet as my output? No one uses that for a person’s height!</li>
</ol>

<p>Answering both questions means we need to understand how the format style uses the <code class="language-plaintext highlighter-rouge">locale</code> and <code class="language-plaintext highlighter-rouge">usage</code> parameters together to create our output.</p>

<h3 id="obfuscated-localization">Obfuscated Localization</h3>

<p>As a quick aside, you need to know that the string output of the <code class="language-plaintext highlighter-rouge">.formatted()</code> method on a <code class="language-plaintext highlighter-rouge">Measurement</code> instance is non-deterministic between different devices.</p>

<p>Because by default, the measurement format style will use the device’s current locale, this means that two developers <em>can</em> get different output by the simple fact that their devices are set to different locales.</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> myHeight = <span class="type token">Measurement</span>(value: <span class="number token">190</span>, unit: <span class="type token">UnitLength</span>.<span class="property token">centimeters</span>)
<span class="keyword token">let</span> measurementStyle = <span class="type token">Measurement</span>&lt;<span class="type token">UnitLength</span>&gt;.<span class="type token">FormatStyle</span>(width: .<span class="dotAccess token">wide</span>)

<span class="comment token">// When the device's Locale is US (en-US)</span>
myHeight.<span class="call token">formatted</span>(measurementStyle) <span class="comment token">// "6.2 feet"

// When the device's Local is Sweden (sv-SE)</span>
myHeight.<span class="call token">formatted</span>(measurementStyle) <span class="comment token">// "1.9 m"</span>
</code></pre></div>

<h3 id="that-formatstyle-extension">That FormatStyle extension</h3>

<p>Creating instances of <code class="language-plaintext highlighter-rouge">Measurement&lt;UnitType&gt;.FormatStyle</code> isn’t how you should be interacting with the system. At all. You’re much better off using the <code class="language-plaintext highlighter-rouge">.measurement(width:usage:numberFormatStyle)</code> extension on <code class="language-plaintext highlighter-rouge">FormatStyle</code> directly.</p>

<p>The <code class="language-plaintext highlighter-rouge">usage</code> and <code class="language-plaintext highlighter-rouge">numberFormatStyle</code> parameters are optional. And to specify the <code class="language-plaintext highlighter-rouge">Locale</code>, you can simply hang the <code class="language-plaintext highlighter-rouge">.locale()</code> off the end:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> usa = <span class="type token">Locale</span>(identifier: <span class="string token">"en-US"</span>)
<span class="keyword token">let</span> sweden = <span class="type token">Locale</span>(identifier: <span class="string token">"sv-SE"</span>)

<span class="keyword token">let</span> myHeight = <span class="type token">Measurement</span>(value: <span class="number token">190</span>, unit: <span class="type token">UnitLength</span>.<span class="property token">centimeters</span>)

myHeight.<span class="call token">formatted</span>(.<span class="call token">measurement</span>(width: .<span class="dotAccess token">abbreviated</span>, usage: .<span class="dotAccess token">general</span>).<span class="call token">locale</span>(usa)) <span class="comment token">// "6.2 ft"</span>
myHeight.<span class="call token">formatted</span>(.<span class="call token">measurement</span>(width: .<span class="dotAccess token">abbreviated</span>, usage: .<span class="dotAccess token">general</span>).<span class="call token">locale</span>(sweden)) <span class="comment token">// "1.9 m"</span>
</code></pre></div>

<p>We’re going to be using this from now on.</p>

<h3 id="usage-is-very-important">Usage Is Very Important</h3>

<p>Let’s answer both of those earlier questions with the same unsatisfying answer:</p>

<ol>
  <li>Why am I getting “feet” as my output when I created it using <code class="language-plaintext highlighter-rouge">UnitLength.centimetres</code>?</li>
  <li>Why am I getting fractional feet as my output? No one uses that for a person’s height!</li>
</ol>

<p>That’s easy: Because the default <code class="language-plaintext highlighter-rouge">usage</code> parameter is <code class="language-plaintext highlighter-rouge">.general</code> and the default <code class="language-plaintext highlighter-rouge">Locale</code> is <code class="language-plaintext highlighter-rouge">en-US</code> when you’re using an Xcode Playground.</p>

<p>But seriously, let’s get into more detail.</p>

<p>The <code class="language-plaintext highlighter-rouge">usage</code> parameter is interesting because while there are two shared options, Apple provides some specialized usages depending on your <code class="language-plaintext highlighter-rouge">Dimension</code> that is used.</p>

<p>The shared options are:</p>

<table>
  <thead>
    <tr>
      <th>Option</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.general</code></td>
      <td>Outputs the value in the most generalized way for the given locale</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.asProvided</code></td>
      <td>Outputs a string value of the unit the <code class="language-plaintext highlighter-rouge">Dimension</code> was created with or converted to</td>
    </tr>
  </tbody>
</table>

<p>Therefore this means that for the US English locale, the system defines fractional feet as the output when the <code class="language-plaintext highlighter-rouge">.general</code> usage parameter is used. Whereas for the Sweden Swedish locale defines the <code class="language-plaintext highlighter-rouge">.general</code> usage parameter as outputting fractional metres.</p>

<p>If we switch up the usage on the above code to use the <code class="language-plaintext highlighter-rouge">.asProvided</code> option, we’d get the output in the original units regardless of the Locale:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> usa = <span class="type token">Locale</span>(identifier: <span class="string token">"en-US"</span>)
<span class="keyword token">let</span> sweden = <span class="type token">Locale</span>(identifier: <span class="string token">"sv-SE"</span>)

<span class="keyword token">let</span> myHeight = <span class="type token">Measurement</span>(value: <span class="number token">190</span>, unit: <span class="type token">UnitLength</span>.<span class="property token">centimeters</span>)

myHeight.<span class="call token">formatted</span>(.<span class="call token">measurement</span>(width: .<span class="dotAccess token">abbreviated</span>, usage: .<span class="dotAccess token">asProvided</span>).<span class="call token">locale</span>(usa)) <span class="comment token">// "190 cm"</span>
myHeight.<span class="call token">formatted</span>(.<span class="call token">measurement</span>(width: .<span class="dotAccess token">abbreviated</span>, usage: .<span class="dotAccess token">asProvided</span>).<span class="call token">locale</span>(sweden)) <span class="comment token">// "190 cm"</span>
</code></pre></div>

<p>To answer the second question, we need to detail the usage options that are available to us only when the <code class="language-plaintext highlighter-rouge">Dimension</code> is <code class="language-plaintext highlighter-rouge">UnitLength</code>.</p>

<h4 id="measurementformatunitusage">MeasurementFormatUnitUsage</h4>

<p>All of the custom usages are static properties on the <code class="language-plaintext highlighter-rouge">MeasurementFormatUnitUsage</code> struct (<a href="https://developer.apple.com/documentation/foundation/measurementformatunitusage">you can check Apple’s docs here</a>). But here are all of the custom usages available to us when we use the <code class="language-plaintext highlighter-rouge">UnitLength</code> <code class="language-plaintext highlighter-rouge">Dimension</code>.</p>

<table>
  <thead>
    <tr>
      <th>Option</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.person</code></td>
      <td>For distances as they relate to a person</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.personHeight</code></td>
      <td>For displaying a person’s height</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.road</code></td>
      <td>For distances while driving</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.focalLength</code></td>
      <td>For the focal length of optics</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.rainfall</code></td>
      <td>For displaying rainfall values</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.snowfall</code></td>
      <td>For displaying snowfall values</td>
    </tr>
  </tbody>
</table>

<p>Look at that, we have a special usage option for displaying someone’s height.</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> usa = <span class="type token">Locale</span>(identifier: <span class="string token">"en-US"</span>)
<span class="keyword token">let</span> sweden = <span class="type token">Locale</span>(identifier: <span class="string token">"sv-SE"</span>)

<span class="keyword token">let</span> myHeight = <span class="type token">Measurement</span>(value: <span class="number token">190</span>, unit: <span class="type token">UnitLength</span>.<span class="property token">centimeters</span>)

myHeight.<span class="call token">formatted</span>(.<span class="call token">measurement</span>(width: .<span class="dotAccess token">abbreviated</span>, usage: .<span class="dotAccess token">personHeight</span>).<span class="call token">locale</span>(usa)) <span class="comment token">// "6 ft, 2.8 in"</span>
myHeight.<span class="call token">formatted</span>(.<span class="call token">measurement</span>(width: .<span class="dotAccess token">abbreviated</span>, usage: .<span class="dotAccess token">personHeight</span>).<span class="call token">locale</span>(sweden)) <span class="comment token">// "1 m, 90 cm"</span>
</code></pre></div>

<p>Better. Much better. We now get the height in the customary units of feet and inches for the US locale and metres and centimetres for the Sweden locale.</p>

<h2 id="formatting-the-number">Formatting the number</h2>

<p>Our new string is a lot better than before, there is one issue though: The fractional inches.</p>

<p>Colloquially in the US, you give your height in whole values for feet and inches so we need to somehow remove or round that inches value. This is where that final <code class="language-plaintext highlighter-rouge">numberFormatStyle</code> parameter comes in. As you can guess by looking at the name, this is simply another format style we provide to our <code class="language-plaintext highlighter-rouge">Measurement&lt;UnitType&gt;.FormatStyle</code>. In this case is specifically a <code class="language-plaintext highlighter-rouge">FloatingPointFormatStyle&lt;Double&gt;</code> format style.</p>

<p>This format style is complicated and powerful. If you’re looking for a detailed explanation of it, you can see my <a href="https://goshdarnformatstyle.com/numeric-styles/#number-style">write up on the goshdarnformatstyle.com site</a>. But for our uses here, we can see how to both round the value up and remove all fractional digits:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> usa = <span class="type token">Locale</span>(identifier: <span class="string token">"en-US"</span>)
<span class="keyword token">let</span> sweden = <span class="type token">Locale</span>(identifier: <span class="string token">"sv-SE"</span>)

<span class="keyword token">let</span> myHeight = <span class="type token">Measurement</span>(value: <span class="number token">190</span>, unit: <span class="type token">UnitLength</span>.<span class="property token">centimeters</span>)

myHeight.<span class="call token">formatted</span>(
    .<span class="call token">measurement</span>(
        width: .<span class="dotAccess token">abbreviated</span>,
        usage: .<span class="dotAccess token">personHeight</span>,
        numberFormatStyle: .<span class="dotAccess token">number</span>.<span class="call token">precision</span>(.<span class="call token">fractionLength</span>(<span class="number token">0</span>))
    ).<span class="call token">locale</span>(usa)
) <span class="comment token">// "6 ft, 3 in"</span>

myHeight.<span class="call token">formatted</span>(
    .<span class="call token">measurement</span>(
        width: .<span class="dotAccess token">abbreviated</span>,
        usage: .<span class="dotAccess token">personHeight</span>,
        numberFormatStyle: .<span class="dotAccess token">number</span>.<span class="call token">precision</span>(.<span class="call token">fractionLength</span>(<span class="number token">0</span>))
    ).<span class="call token">locale</span>(sweden)
) <span class="comment token">// "1 m, 90 cm"</span>
</code></pre></div>

<h1 id="the-hard-sell">The Hard Sell</h1>

<p>You can now see why storing your physical values as <code class="language-plaintext highlighter-rouge">Measurement</code> types can unlock some incredible features inside of your app. Converting and doing math on these values can be helpful, but the real power comes in when you get so much localization power for free.</p>

<p>Even though a country officially uses one set of units, colloquially a population can use different sets of units depending on the situation. Canadians give out their height and weight in imperial units, British roads use miles per hour as their speed, etc. Much like using the <code class="language-plaintext highlighter-rouge">Date</code> type to store dates <a href="https://gist.github.com/timvisee/fcda9bbdff88d45cc9061606b4b923ca">removes so many common date mistakes</a>, using <code class="language-plaintext highlighter-rouge">Measurement</code> for store and format your measurements saves you from having an encyclopedic knowledge of local customs.</p>

<p>The next time you’re faced with using a physical measurement value, it may be tempting to simply store it as a numeric type. But I hope now you see the power in fully embracing using <code class="language-plaintext highlighter-rouge">Measurement</code> from now on.</p>

<hr />
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p><a href="https://developer.apple.com/documentation/foundation/dimension">Apple’s documentation refers to these units as subclasses of <code class="language-plaintext highlighter-rouge">Dimension</code></a>, which is “An abstract class representing a dimensional unit of measure.”. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:1:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
  </ol>
</div>]]></content><author><name>brett ohland</name></author><category term="ios" /><category term="development" /><category term="swift" /><category term="formatstyle" /><summary type="html"><![CDATA[So much power to convert, do math on, and localize the display of your measurement values at your fingertips… yet no one uses it.]]></summary></entry><entry><title type="html">The Xcode 14 Format Style Updates</title><link href="https://ampersandsoftworks.com/posts/the-xcode-14-update/index.html" rel="alternate" type="text/html" title="The Xcode 14 Format Style Updates" /><published>2022-08-01T00:00:00-06:00</published><updated>2022-08-01T00:00:00-06:00</updated><id>https://ampersandsoftworks.com/posts/the-xcode-14-update/the-xcode-14-update</id><content type="html" xml:base="https://ampersandsoftworks.com/posts/the-xcode-14-update/index.html"><![CDATA[<p>I’ve been toiling away in the format style mines for the last month off and on as I’ve been fully documenting the new <code class="language-plaintext highlighter-rouge">FormatStyle</code> stuff that Apple has been including in the Xcode 14 betas.</p>

<p>Today I’ve finally launched the update!</p>

<p>The changelogs: <a href="https://fuckingformatstyle.com/changelog/">Fucking FormatStyle</a> or <a href="https://goshdarnformatstyle.com/changelog/">Gosh Darned FormatStyle</a></p>

<h2 id="updates">Updates</h2>

<ul>
  <li>ByteCountFormatStyle for Int64 values no longer crash when you try and use any unit larger than gigabyte.</li>
  <li>There’s a special FormatStyle for <code class="language-plaintext highlighter-rouge">Measurement&lt;UnitInformationStorage&gt;</code> values that is identical to <code class="language-plaintext highlighter-rouge">ByteCountFormatStyle</code></li>
  <li>We now have a <code class="language-plaintext highlighter-rouge">URL.FormatStyle</code> which also conforms to <code class="language-plaintext highlighter-rouge">ParseableFormatStyle</code>, which means we can format and parse URL data now</li>
  <li>You can now access the <code class="language-plaintext highlighter-rouge">Date.VerbatimFormatStyle</code> by using the type method <code class="language-plaintext highlighter-rouge">formatted(.verbatim())</code> on Date values</li>
  <li>The new <code class="language-plaintext highlighter-rouge">Duration</code> type also has full format style support with two new styles</li>
  <li>The site has quickly grown outside of my original goals of a single page, single serving site. So I’ve broken it up into individual pages for sections.</li>
</ul>

<p>Thanks everyone for sharing and using the site. I’ve gotten amazing feedback from the community about it.</p>]]></content><author><name>brett ohland</name></author><category term="formatstyle" /><category term="development" /><category term="swift" /><summary type="html"><![CDATA[I've released a big update for Fucking FormatStyle/Gosh Darned FormatStyle.]]></summary></entry><entry><title type="html">Help! My Xcode Extensions Disappeared</title><link href="https://ampersandsoftworks.com/posts/help-my-xcode-extensions-disappeared/index.html" rel="alternate" type="text/html" title="Help! My Xcode Extensions Disappeared" /><published>2022-07-06T00:00:00-06:00</published><updated>2022-07-06T00:00:00-06:00</updated><id>https://ampersandsoftworks.com/posts/help-my-xcode-extensions-disappeared/help-my-xcode-extensions-disappeared</id><content type="html" xml:base="https://ampersandsoftworks.com/posts/help-my-xcode-extensions-disappeared/index.html"><![CDATA[<p>Have your Xcode Extensions ever just… disappeared on you (usually after a crash)?</p>

<p>They’ll be completely missing from the <code class="language-plaintext highlighter-rouge">Editor</code> menu item in Xcode itself, but <em>also missing from the Extensions area of System Preferences</em>.</p>

<p>Well you’re in luck, I have a simple solution for you.</p>

<ol>
  <li>Quit Xcode Completely</li>
  <li>Go to <code class="language-plaintext highlighter-rouge">/Applications</code> (where Xcode.app is installed)</li>
  <li>RENAME Xcode to something (I usually just add a space before “.app”) and hit Enter</li>
  <li>Revert Xcode to it’s original name.</li>
  <li>There is no step 5.</li>
</ol>

<hr />

<h1 id="why">Why?</h1>

<p>From <a href="https://nshipster.com/xcode-source-extensions/">Xcode​Kit and Xcode Source Editor Extensions</a> on <a href="https://nshipster.com">NSHipster</a>:</p>

<figure class="quote">
  <blockquote cite="https://nshipster.com/xcode-source-extensions/">
    <p>when multiple copies of Xcode are on the same machine, extensions can stop working completely. In this case, Apple Developer Relations suggests re-registering your main copy of Xcode with Launch Services…</p>
  </blockquote>
  <figcaption>&mdash;Zoë Smith, <cite>NSHipster</cite></figcaption>
</figure>

<p>That’s all we’re doing when we rename Xcode, we’re simply re-registering it with Launch Services.</p>]]></content><author><name>brett ohland</name></author><category term="development" /><category term="xcode" /><summary type="html"><![CDATA[How to fix this unfortunately common problem.]]></summary></entry><entry><title type="html">Formatting your own types</title><link href="https://ampersandsoftworks.com/posts/formatstyle-parseableformatstyle-and-your-custom-types/index.html" rel="alternate" type="text/html" title="Formatting your own types" /><published>2022-06-30T00:00:00-06:00</published><updated>2022-06-30T00:00:00-06:00</updated><id>https://ampersandsoftworks.com/posts/formatstyle-parseableformatstyle-and-your-custom-types/formatstyle-parseableformatstyle-and-your-custom-types</id><content type="html" xml:base="https://ampersandsoftworks.com/posts/formatstyle-parseableformatstyle-and-your-custom-types/index.html"><![CDATA[<p><a href="https://goshdarnformatstyle.com">So you’ve read the gosh darn site and know how to get strings from data types.</a>.</p>

<p><a href="/posts/from-strings-to-data-using-parseableformatstyle/">Then you read the ParseableFormatStyle post and know how to parse strings into data</a>.</p>

<p>If your next thought was: “Now I want to do this with my own data types”, then this is for you.</p>

<p><strong>TL;DR:</strong> <a href="https://github.com/brettohland/ampersandsoftworks.com-examples/tree/main/%5B2022-06-30%5D%20ISBN-FormatStyle">Download the Xcode Playground</a> or <a href="https://gist.github.com/brettohland/744fcbd2a8aa77907ec84a286e8da3b0">See everything as a Gist</a></p>

<h1 id="sections">Sections</h1>

<ol>
  <li><a href="#our-data-type">Our Data Type</a></li>
  <li><a href="#string-output">String Output</a>
    <ul>
      <li><a href="#creating-our-isbnformatstyle">Creating our ISBN.FormatStyle</a></li>
      <li><a href="#making-access-easier">Making Access Easier</a></li>
    </ul>
  </li>
  <li><a href="#attributedstring-output">AttributedString Output</a>
    <ul>
      <li><a href="#creating-a-custom-attributedscope">Creating a Custom AttributedScope</a></li>
      <li><a href="#creating-our-isbnattributedstringformatstyle">Creating Our ISBN.AttributedStringFormatStyle</a></li>
    </ul>
  </li>
  <li><a href="#parsing-strings-into-isbns">Parsing Strings Into ISBNs</a>
    <ul>
      <li><a href="#isbn-validation-implementation">ISBN Validation Implementation</a></li>
      <li><a href="#creating-our-parsestrategy">Creating our ParseStrategy</a></li>
      <li><a href="#parseableformatstyle-conformance---convenience-extensions">ParseableFormatStyle Conformance &amp; Convenience Extensions</a></li>
    </ul>
  </li>
  <li><a href="#bonus-round--unit-testing">Bonus Round: Unit Testing</a></li>
</ol>

<hr />

<p><strong>TL;DR:</strong> <a href="https://github.com/brettohland/ISBN-FormatStyle/">Download the Xcode Playground</a> or <a href="https://gist.github.com/brettohland/744fcbd2a8aa77907ec84a286e8da3b0">See everything as a Gist</a></p>

<p>The API for this example is based heavily on the <a href=""><code class="language-plaintext highlighter-rouge">Date.FormatStyle</code></a> API, and the <a href="https://forums.swift.org/t/url-formatstyle-and-parsestrategy/56607">URL FormatStyle and ParseStyle Proposal</a> that was ultimately included in iOS 16.</p>

<p>We’re going to define a custom data type, and then add the following features:</p>

<ol>
  <li>A way to output <code class="language-plaintext highlighter-rouge">String</code> values</li>
  <li>A way to output <code class="language-plaintext highlighter-rouge">AttributedString</code> values</li>
  <li>A way to parse <code class="language-plaintext highlighter-rouge">String</code> inputs</li>
</ol>

<h1 id="our-data-type">Our Data Type</h1>

<p><a href="https://en.wikipedia.org/wiki/ISBN">The humble ISBN</a> is our international standard for uniquely identifying books. It has everything we need in a data type:</p>

<ul>
  <li>It exists</li>
  <li>It represent a structure of data</li>
  <li>It can be validated</li>
</ul>

<p>Our ISBN representation is going to be storing the 13 digit standard from 2007.</p>

<center><img src="/images/2022/Jun/isbn.png" alt="An ISBN Bacode (Based on the EAD13 Standard" width="200" style="margin-bottom: 10px" /></center>

<div class="highlight"><pre class="splash"><code><span class="comment token">/// Represents a 13 digit International Standard Book Number.</span>
<span class="keyword token">public struct</span> ISBN: <span class="type token">Codable</span>, <span class="type token">Sendable</span>, <span class="type token">Equatable</span>, <span class="type token">Hashable</span> {
    <span class="keyword token">public let</span> prefix: <span class="type token">String</span>
    <span class="keyword token">public let</span> registrationGroup: <span class="type token">String</span>
    <span class="keyword token">public let</span> registrant: <span class="type token">String</span>
    <span class="keyword token">public let</span> publication: <span class="type token">String</span>
    <span class="keyword token">public let</span> checkDigit: <span class="type token">String</span>

    <span class="comment token">/// Initializes a new ISBN struct
    /// - Parameters:
    ///   - prefix: The prefix to the registration group
    ///   - registrationGroup: The registration group (as numbers)
    ///   - registrant: The registrant (as number)
    ///   - publication: The publication (as numbers)
    ///   - checkDigit: The check digit used in validation</span>
    <span class="keyword token">public init</span>(
        prefix: <span class="type token">String</span>,
        registrationGroup: <span class="type token">String</span>,
        registrant: <span class="type token">String</span>,
        publication: <span class="type token">String</span>,
        checkDigit: <span class="type token">String</span>
    ) {
        <span class="keyword token">self</span>.<span class="property token">prefix</span> = prefix
        <span class="keyword token">self</span>.<span class="property token">registrationGroup</span> = registrationGroup
        <span class="keyword token">self</span>.<span class="property token">registrant</span> = registrant
        <span class="keyword token">self</span>.<span class="property token">publication</span> = publication
        <span class="keyword token">self</span>.<span class="property token">checkDigit</span> = checkDigit
    }
}
</code></pre></div>

<p>Simple enough. But one thing to note: Because of how the <a href="">validation is calculated</a>, each part of the ISBN is being stored as <code class="language-plaintext highlighter-rouge">String</code> values. Not <code class="language-plaintext highlighter-rouge">Int</code> values. There are no fixed sizes for each of the portions of the ISBN, and there may be a case where we might have values that start with 0.</p>

<h1 id="string-output">String Output</h1>

<p>We’re going to define a <code class="language-plaintext highlighter-rouge">struct</code> called <code class="language-plaintext highlighter-rouge">FormatStyle</code> inside of an extension on <code class="language-plaintext highlighter-rouge">ISBN</code>, which will then conform to the <code class="language-plaintext highlighter-rouge">FormatStyle</code> protocol.</p>

<p>Reading about the standard, we can learn that it’s valid for an ISBN to use hyphens or spaces to separate each part of the value. Also, that (generally) you can convert between ISBN-13 and ISBN-10 values fairly freely by omitting or adding a standard prefix. So let’s define two enums within our <code class="language-plaintext highlighter-rouge">FormatStyle</code> to define these options as well.</p>

<h2 id="creating-our-isbnformatstyle">Creating our ISBN.FormatStyle</h2>

<div class="highlight"><pre class="splash"><code><span class="keyword token">public extension</span> <span class="type token">ISBN</span> {

    <span class="keyword token">struct</span> FormatStyle: <span class="type token">Codable</span>, <span class="type token">Equatable</span>, <span class="type token">Hashable</span> {

        <span class="comment token">/// Defines which ISBN standard to output</span>
        <span class="keyword token">public enum</span> Standard: <span class="type token">Codable</span>, <span class="type token">Equatable</span>, <span class="type token">Hashable</span> {
            <span class="keyword token">case</span> isbn13
            <span class="keyword token">case</span> isbn10
        }

        <span class="keyword token">public enum</span> Separator: <span class="type token">String</span>, <span class="type token">Codable</span>, <span class="type token">Equatable</span>, <span class="type token">Hashable</span> {
            <span class="keyword token">case</span> hyphen = <span class="string token">"-"</span>
            <span class="keyword token">case</span> space = <span class="string token">" "</span>
            <span class="keyword token">case</span> none = <span class="string token">""</span>
        }

        <span class="keyword token">let</span> standard: <span class="type token">Standard</span>
        <span class="keyword token">let</span> separator: <span class="type token">Separator</span>

        <span class="comment token">/// Initialize an ISBN FormatStyle with the given Standard
        /// - Parameter standard: Standard, defaults to .isbn13(.hyphen)</span>
        <span class="keyword token">public init</span>(<span class="keyword token">_</span> standard: <span class="type token">Standard</span> = .<span class="dotAccess token">isbn13</span>, separator: <span class="type token">Separator</span> = .<span class="dotAccess token">hyphen</span>) {
            <span class="keyword token">self</span>.<span class="property token">standard</span> = standard
            <span class="keyword token">self</span>.<span class="property token">separator</span> = separator
        }

        <span class="comment token">// MARK: Customization Method Chaining</span>

        <span class="keyword token">public func</span> standard(<span class="keyword token">_</span> standard: <span class="type token">Standard</span>) -&gt; <span class="type token">Self</span> {
            .<span class="keyword token">init</span>(standard, separator: separator)
        }

        <span class="comment token">/// Returns a new instance of `self` with the standard property set.
        /// - Parameter standard: The standard to use on the final output
        /// - Returns: A copy of `self` with the standard set</span>
        <span class="keyword token">public func</span> separator(<span class="keyword token">_</span> separator: <span class="type token">Separator</span>) -&gt; <span class="type token">Self</span> {
            .<span class="keyword token">init</span>(standard, separator: separator)
        }
    }
}
</code></pre></div>

<p>You’ll also notice that we define two instance methods as well <code class="language-plaintext highlighter-rouge">standard(:)</code> and <code class="language-plaintext highlighter-rouge">separator(:)</code>. The goals here is to follow the Swift team’s standards and allow our new format style to be composited together like so: <code class="language-plaintext highlighter-rouge">ISBN.FormatStyle().standard(.isbn10).spacer(.space)</code>.</p>

<p>Because our <code class="language-plaintext highlighter-rouge">ISBN.FormatStyle</code> is a struct (a value type), this is the best way to do it. Modifying structs is to be generally avoided (and you have to declare things “mutating” in order to do it.)</p>

<p>You’ll notice that our shiny new <code class="language-plaintext highlighter-rouge">ISBN.FormatStyle</code> <em>doesn’t actually conform to the FormatStyle protocol</em>. Well that’s an easy fix:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">extension</span> <span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>: <span class="type token">Foundation</span>.<span class="type token">FormatStyle</span> {
    <span class="comment token">/// Returns a textual representation of the `ISBN` value passed in.
    /// - Parameter value: A `ISBN` value
    /// - Returns: The textual representation of the value, using the style's `standard`.</span>
    <span class="keyword token">public func</span> format(<span class="keyword token">_</span> value: <span class="type token">ISBN</span>) -&gt; <span class="type token">String</span> {
        <span class="keyword token">let</span> parts = [
            value.<span class="property token">prefix</span>,
            value.<span class="property token">registrationGroup</span>,
            value.<span class="property token">registrant</span>,
            value.<span class="property token">publication</span>,
            value.<span class="property token">checkDigit</span>,
        ]
        <span class="keyword token">switch</span> standard {
        <span class="keyword token">case</span> .<span class="dotAccess token">isbn13</span>:
            <span class="keyword token">return</span> parts.<span class="call token">joined</span>(separator: separator.<span class="property token">rawValue</span>)
        <span class="keyword token">case</span> .<span class="dotAccess token">isbn10</span>:
            <span class="comment token">// ISBN-10 is missing the "prefix" portion of the number.</span>
            <span class="keyword token">return</span> parts.<span class="call token">dropFirst</span>().<span class="call token">joined</span>(separator: separator.<span class="property token">rawValue</span>)
        }
    }
}
</code></pre></div>

<p>Now we’re in an interesting place. We have a valid <code class="language-plaintext highlighter-rouge">FormatStyle</code>, but it’s not great to use. Right now our API to use it would be limited to initializing an instance of it, and calling the <code class="language-plaintext highlighter-rouge">format(:)</code> method.</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> isbn = <span class="type token">ISBN</span>(
    prefix: <span class="string token">"978"</span>,
    registrationGroup: <span class="string token">"17"</span>,
    registrant: <span class="string token">"85889"</span>,
    publication: <span class="string token">"01"</span>,
    checkDigit: <span class="string token">"1"</span>
)

<span class="comment token">// The default (ISBN-13, hyphen)</span>
<span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>().<span class="call token">format</span>(isbn) <span class="comment token">// "978-17-85889-01-1"

// Initializer, with all properties set.</span>
<span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>(.<span class="dotAccess token">isbn13</span>, separator: .<span class="dotAccess token">hyphen</span>).<span class="call token">format</span>(isbn) <span class="comment token">// "978-17-85889-01-1"</span>
<span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>(.<span class="dotAccess token">isbn13</span>, separator: .<span class="dotAccess token">space</span>).<span class="call token">format</span>(isbn) <span class="comment token">// "978 17 85889 01 1"</span>
<span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>(.<span class="dotAccess token">isbn13</span>, separator: .<span class="dotAccess token">none</span>).<span class="call token">format</span>(isbn) <span class="comment token">// "9781785889011"</span>
<span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>(.<span class="dotAccess token">isbn10</span>, separator: .<span class="dotAccess token">hyphen</span>).<span class="call token">format</span>(isbn) <span class="comment token">// "17-85889-01-1"</span>
<span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>(.<span class="dotAccess token">isbn10</span>, separator: .<span class="dotAccess token">space</span>).<span class="call token">format</span>(isbn) <span class="comment token">// "17 85889 01 1"</span>
<span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>(.<span class="dotAccess token">isbn10</span>, separator: .<span class="dotAccess token">none</span>).<span class="call token">format</span>(isbn) <span class="comment token">// "1785889011"

// The ISBN-13 default, but using method chaining to change the separator</span>
<span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>().<span class="call token">separator</span>(.<span class="dotAccess token">hyphen</span>) <span class="comment token">// "978-17-85889-01-1"</span>
<span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>().<span class="call token">separator</span>(.<span class="dotAccess token">space</span>) <span class="comment token">// "978 17 85889 01 1"</span>
<span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>().<span class="call token">separator</span>(.<span class="dotAccess token">none</span>) <span class="comment token">// "9781785889011"

// Changing the standard and separator using method chainig</span>
<span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>().<span class="call token">standard</span>(.<span class="dotAccess token">isbn10</span>).<span class="call token">separator</span>(.<span class="dotAccess token">hyphen</span>) <span class="comment token">// "17-85889-01-1"</span>
<span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>().<span class="call token">standard</span>(.<span class="dotAccess token">isbn10</span>).<span class="call token">separator</span>(.<span class="dotAccess token">space</span>) <span class="comment token">// "17 85889 01 1"</span>
<span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>().<span class="call token">standard</span>(.<span class="dotAccess token">isbn10</span>).<span class="call token">separator</span>(.<span class="dotAccess token">none</span>) <span class="comment token">// "1785889011"</span>
</code></pre></div>

<h2 id="making-access-easier">Making Access Easier</h2>

<p>To follow along with the platform standards, we need to add  some extensions to <code class="language-plaintext highlighter-rouge">ISBN</code> and <code class="language-plaintext highlighter-rouge">FormatStyle</code> themselves:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">public extension</span> <span class="type token">ISBN</span> {

    <span class="comment token">/// Converts `self` to its textual representation.
    /// - Returns: String</span>
    <span class="keyword token">func</span> formatted() -&gt; <span class="type token">String</span> {
        <span class="type token">Self</span>.<span class="type token">FormatStyle</span>().<span class="call token">format</span>(<span class="keyword token">self</span>)
    }

    <span class="comment token">/// Converts `self` to another representation.
    /// - Parameter style: The format for formatting `self`
    /// - Returns: A representations of `self` using the given `style`. The type of the return is determined by the FormatStyle.FormatOutput</span>
    <span class="keyword token">func</span> formatted&lt;F: <span class="type token">Foundation</span>.<span class="type token">FormatStyle</span>&gt;(<span class="keyword token">_</span> style: <span class="type token">F</span>) -&gt; <span class="type token">F</span>.<span class="type token">FormatOutput</span> <span class="keyword token">where</span> <span class="type token">F</span>.<span class="type token">FormatInput</span> == <span class="type token">ISBN</span> {
        style.<span class="call token">format</span>(<span class="keyword token">self</span>)
    }
}

<span class="comment token">// MARK: Convenience FormatStyle extensions to ease access</span>

<span class="keyword token">@available</span>(macOS <span class="number token">12.0</span>, iOS <span class="number token">15.0</span>, tvOS <span class="number token">15.0</span>, watchOS <span class="number token">8.0</span>, *)
<span class="keyword token">public extension</span> <span class="type token">FormatStyle</span> <span class="keyword token">where</span> <span class="type token">Self</span> == <span class="type token">ISBN</span>.<span class="type token">FormatStyle</span> {

    <span class="keyword token">static var</span> isbn13: <span class="type token">Self</span> { .<span class="keyword token">init</span>(.<span class="dotAccess token">isbn13</span>, separator: .<span class="dotAccess token">hyphen</span>) }
    <span class="keyword token">static var</span> isbn10: <span class="type token">Self</span> { .<span class="keyword token">init</span>(.<span class="dotAccess token">isbn10</span>, separator: .<span class="dotAccess token">hyphen</span>) }

    <span class="keyword token">static func</span> isbn(
        standard: <span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>.<span class="type token">Standard</span> = .<span class="dotAccess token">isbn13</span>,
        separator: <span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>.<span class="type token">Separator</span> = .<span class="dotAccess token">hyphen</span>
    ) -&gt; <span class="type token">Self</span> {
        .<span class="keyword token">init</span>(standard, separator: separator)
    }
}

<span class="comment token">// MARK: - Debug Methods on ISBN</span>

<span class="keyword token">extension</span> <span class="type token">ISBN</span>: <span class="type token">CustomDebugStringConvertible</span> {
    <span class="keyword token">public var</span> debugDescription: <span class="type token">String</span> {
        <span class="string token">"ISBN:</span> \(<span class="call token">formatted</span>())<span class="string token">"</span>
    }
}
</code></pre></div>

<p>We can now do the following:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> isbn = <span class="type token">ISBN</span>(
    prefix: <span class="string token">"978"</span>,
    registrationGroup: <span class="string token">"17"</span>,
    registrant: <span class="string token">"85889"</span>,
    publication: <span class="string token">"01"</span>,
    checkDigit: <span class="string token">"1"</span>
)

isbn.<span class="call token">formatted</span>() <span class="comment token">// "978-17-85889-01-1"</span>
isbn.<span class="call token">formatted</span>(.<span class="call token">isbn</span>()) <span class="comment token">// "978-17-85889-01-1"</span>
isbn.<span class="call token">formatted</span>(.<span class="call token">isbn</span>(standard: .<span class="dotAccess token">isbn13</span>, separator: .<span class="dotAccess token">hyphen</span>)) <span class="comment token">// "978-17-85889-01-1"</span>
isbn.<span class="call token">formatted</span>(.<span class="call token">isbn</span>(standard: .<span class="dotAccess token">isbn13</span>, separator: .<span class="dotAccess token">space</span>)) <span class="comment token">// "978 17 85889 01 1"</span>
isbn.<span class="call token">formatted</span>(.<span class="call token">isbn</span>(standard: .<span class="dotAccess token">isbn13</span>, separator: .<span class="dotAccess token">none</span>)) <span class="comment token">// "9781785889011"</span>

isbn.<span class="call token">formatted</span>(.<span class="call token">isbn</span>(standard: .<span class="dotAccess token">isbn10</span>, separator: .<span class="dotAccess token">hyphen</span>)) <span class="comment token">// "17-85889-01-1"</span>
isbn.<span class="call token">formatted</span>(.<span class="call token">isbn</span>(standard: .<span class="dotAccess token">isbn10</span>, separator: .<span class="dotAccess token">space</span>)) <span class="comment token">// "17 85889 01 1"</span>
isbn.<span class="call token">formatted</span>(.<span class="call token">isbn</span>(standard: .<span class="dotAccess token">isbn10</span>, separator: .<span class="dotAccess token">none</span>)) <span class="comment token">// "1785889011"</span>

isbn.<span class="call token">formatted</span>(.<span class="dotAccess token">isbn13</span>.<span class="call token">separator</span>(.<span class="dotAccess token">none</span>)) <span class="comment token">// "9781785889011"</span>
isbn.<span class="call token">formatted</span>(.<span class="dotAccess token">isbn13</span>.<span class="call token">separator</span>(.<span class="dotAccess token">hyphen</span>)) <span class="comment token">// "978-17-85889-01-1"</span>
isbn.<span class="call token">formatted</span>(.<span class="dotAccess token">isbn13</span>.<span class="call token">separator</span>(.<span class="dotAccess token">space</span>)) <span class="comment token">// "978 17 85889 01 1"</span>

isbn.<span class="call token">formatted</span>(.<span class="dotAccess token">isbn10</span>.<span class="call token">separator</span>(.<span class="dotAccess token">none</span>)) <span class="comment token">// "1785889011"</span>
isbn.<span class="call token">formatted</span>(.<span class="dotAccess token">isbn10</span>.<span class="call token">separator</span>(.<span class="dotAccess token">hyphen</span>)) <span class="comment token">// "17-85889-01-1"</span>
isbn.<span class="call token">formatted</span>(.<span class="dotAccess token">isbn10</span>.<span class="call token">separator</span>(.<span class="dotAccess token">space</span>)) <span class="comment token">// "17 85889 01 1"</span>
</code></pre></div>

<h1 id="attributedstring-output">AttributedString Output</h1>

<p>Adding AttributedString support is as easy as creating a new struct that conforms to <code class="language-plaintext highlighter-rouge">FormatStyle</code> whose <code class="language-plaintext highlighter-rouge">FormatOutput</code> type is <code class="language-plaintext highlighter-rouge">AttributedString</code>.</p>

<p>But why do this?</p>

<p>At the heart of it, an AttributedString is just a regular String but with metadata. Some of that metadata is used by our UI APIs to modify how they’re shown on screen (think colours, letter case, or font weight), while other metadata is just for reference. Since our <code class="language-plaintext highlighter-rouge">ISBN</code> type has structured data, it could be useful for others to have each part of the identifier be marked up.</p>

<p>Individual pieces of metadata are defined as <em>attributes</em>, and those attributes are contained in <em>scopes</em>. <a href="https://nilcoalescing.com/blog/AttributedStringAttributeScopes/">There’s a great article on NilCoalescing about if you’d like to read more</a>.</p>

<p>The first step is to define our scope and our attribute, and also extend the dynamic lookup system to add support for it.</p>

<h2 id="creating-a-custom-attributedscope">Creating a Custom AttributedScope</h2>

<div class="highlight"><pre class="splash"><code><span class="comment token">// We need to create a new AttributedScope to contain our new attributes.</span>
<span class="keyword token">public extension</span> <span class="type token">AttributeScopes</span> {

    <span class="comment token">/// Represents the parts of an ISBN which we will be adding attributes to.</span>
    <span class="keyword token">enum</span> ISBNPart: <span class="type token">Hashable</span> {
        <span class="keyword token">case</span> prefix
        <span class="keyword token">case</span> registrationGroup
        <span class="keyword token">case</span> registrant
        <span class="keyword token">case</span> publication
        <span class="keyword token">case</span> checkDigit
        <span class="keyword token">case</span> separator
    }

    <span class="comment token">// Define our new AttributeScope</span>
    <span class="keyword token">struct</span> ISBNAttributes: <span class="type token">AttributeScope</span> {
        <span class="comment token">// Our property value to access it.</span>
        <span class="keyword token">let</span> isbnPart: <span class="type token">ISBNAttributeKey</span>
    }

    <span class="comment token">// We follow the AttributeStringKey protocol to define our new attribute.</span>
    <span class="keyword token">enum</span> ISBNAttributeKey: <span class="type token">AttributedStringKey</span> {
        <span class="keyword token">public typealias</span> Value = <span class="type token">ISBNPart</span>
        <span class="keyword token">public static let</span> name = <span class="string token">"isbnPart"</span>
    }

    <span class="comment token">// This extends AttributeScope to allow us to access our new ISBNPart type quickly.</span>
    <span class="keyword token">var</span> isbnPart: <span class="type token">ISBNPart</span>.<span class="type token">Type</span> { <span class="type token">ISBNPart</span>.<span class="keyword token">self</span> }
}

<span class="comment token">// We extend AttributeDynamicLookup to know about our custom type.</span>
<span class="keyword token">public extension</span> <span class="type token">AttributeDynamicLookup</span> {
    <span class="keyword token">subscript</span>&lt;T: <span class="type token">AttributedStringKey</span>&gt;(dynamicMember keyPath: <span class="type token">KeyPath</span>&lt;<span class="type token">AttributeScopes</span>.<span class="type token">ISBNAttributes</span>, <span class="type token">T</span>&gt;) -&gt; <span class="type token">T</span> {
        <span class="keyword token">self</span>[<span class="type token">T</span>.<span class="keyword token">self</span>]
    }
}
</code></pre></div>

<h2 id="creating-our-isbnattributedstringformatstyle">Creating Our ISBN.AttributedStringFormatStyle</h2>

<p>With those defined, we can now build our <code class="language-plaintext highlighter-rouge">ISBN.AttributedStringFormatStyle</code> struct that will convert our ISBN to the <code class="language-plaintext highlighter-rouge">AttributedString</code>. Notice that after creating attributed string versions of each part, we then set the <code class="language-plaintext highlighter-rouge">.isbnPart</code> attribute of each. That specifically is why we’ve created this type.</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">public extension</span> <span class="type token">ISBN</span> {

    <span class="comment token">/// An ISBN FormatStyle for outputting AttributedString values.</span>
    <span class="keyword token">struct</span> AttributedStringFormatStyle: <span class="type token">Codable</span>, <span class="type token">Foundation</span>.<span class="type token">FormatStyle</span> {

        <span class="keyword token">private let</span> standard: <span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>.<span class="type token">Standard</span>
        <span class="keyword token">private let</span> separator: <span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>.<span class="type token">Separator</span>

        <span class="comment token">/// Initialize an ISBN FormatStyle with the given Standard
        /// - Parameter standard: Standard (required)</span>
        <span class="keyword token">public init</span>(standard: <span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>.<span class="type token">Standard</span>, separator: <span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>.<span class="type token">Separator</span>) {
            <span class="keyword token">self</span>.<span class="property token">standard</span> = standard
            <span class="keyword token">self</span>.<span class="property token">separator</span> = separator
        }

        <span class="comment token">// The format method required by the FormatStyle protocol.</span>
        <span class="keyword token">public func</span> format(<span class="keyword token">_</span> value: <span class="type token">ISBN</span>) -&gt; <span class="type token">AttributedString</span> {

            <span class="comment token">// Creates AttributedString representations of each part of the ISBN</span>
            <span class="keyword token">var</span> prefix = <span class="type token">AttributedString</span>(value.<span class="property token">prefix</span>)
            <span class="keyword token">var</span> group = <span class="type token">AttributedString</span>(value.<span class="property token">registrationGroup</span>)
            <span class="keyword token">var</span> registrant = <span class="type token">AttributedString</span>(value.<span class="property token">registrant</span>)
            <span class="keyword token">var</span> publication = <span class="type token">AttributedString</span>(value.<span class="property token">publication</span>)
            <span class="keyword token">var</span> checkDigit = <span class="type token">AttributedString</span>(value.<span class="property token">checkDigit</span>)

            <span class="comment token">// Assigns our custom attribute scope attribute to each part.</span>
            prefix.<span class="property token">isbnPart</span> = .<span class="dotAccess token">prefix</span>
            group.<span class="property token">isbnPart</span> = .<span class="dotAccess token">registrationGroup</span>
            registrant.<span class="property token">isbnPart</span> = .<span class="dotAccess token">registrant</span>
            publication.<span class="property token">isbnPart</span> = .<span class="dotAccess token">publication</span>
            checkDigit.<span class="property token">isbnPart</span> = .<span class="dotAccess token">checkDigit</span>

            <span class="comment token">// Collect all parts in an array to allow for simple AttributedString concatenation using reduce</span>
            <span class="keyword token">let</span> parts = [
                prefix,
                group,
                registrant,
                publication,
                checkDigit,
            ]

            <span class="comment token">// Create the final AttributedString by using the reduce method. We define the</span>
            <span class="keyword token">switch</span> standard {
            <span class="keyword token">case</span> .<span class="dotAccess token">isbn13</span> <span class="keyword token">where</span> separator == .<span class="dotAccess token">none</span>:
                <span class="comment token">// Merge all parts into one string.</span>
                <span class="keyword token">return</span> parts.<span class="call token">reduce</span>(<span class="type token">AttributedString</span>(), +)
            <span class="keyword token">case</span> .<span class="dotAccess token">isbn13</span>:
                <span class="comment token">// Define the delimiter</span>
                <span class="keyword token">var</span> separator = <span class="type token">AttributedString</span>(separator.<span class="property token">rawValue</span>)
                separator.<span class="property token">isbnPart</span> = .<span class="dotAccess token">separator</span>
                <span class="comment token">// Starting with the .prefix, use reduce to build the final AttributedString.</span>
                <span class="keyword token">return</span> parts.<span class="call token">dropFirst</span>().<span class="call token">reduce</span>(prefix) { $0 + separator + $1 }
            <span class="keyword token">case</span> .<span class="dotAccess token">isbn10</span> <span class="keyword token">where</span> separator == .<span class="dotAccess token">none</span>:
                <span class="comment token">// Drop the prefix, merge all parts.</span>
                <span class="keyword token">return</span> parts.<span class="call token">dropFirst</span>().<span class="call token">reduce</span>(group, +)
            <span class="keyword token">case</span> .<span class="dotAccess token">isbn10</span>:
                <span class="comment token">// Define the delimiter</span>
                <span class="keyword token">var</span> separator = <span class="type token">AttributedString</span>(separator.<span class="property token">rawValue</span>)
                separator.<span class="property token">isbnPart</span> = .<span class="dotAccess token">separator</span>
                <span class="comment token">// Drop the first two elements (prefix and group), then build the final AttributedString</span>
                <span class="keyword token">return</span> parts.<span class="call token">dropFirst</span>(<span class="number token">2</span>).<span class="call token">reduce</span>(group) { $0 + separator + $1 }
            }
        }
    }
}

<span class="comment token">// Extend</span> 
<span class="keyword token">public extension</span> <span class="type token">ISBN</span>.<span class="type token">FormatStyle</span> {
    <span class="keyword token">var</span> attributed: <span class="type token">ISBN</span>.<span class="type token">AttributedStringFormatStyle</span> {
        .<span class="keyword token">init</span>(standard: standard, separator: separator)
    }
}
</code></pre></div>

<p>Now when we’re using our ISBN, we can use these attributes to customize their display on screen:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">struct</span> AttributedStringExample: <span class="type token">View</span> {

    <span class="keyword token">let</span> exampleISBN = <span class="type token">ISBN</span>(
        prefix: <span class="string token">"978"</span>,
        registrationGroup: <span class="string token">"17"</span>,
        registrant: <span class="string token">"85889"</span>,
        publication: <span class="string token">"01"</span>,
        checkDigit: <span class="string token">"1"</span>
    )

    <span class="keyword token">var</span> attributedString: <span class="type token">AttributedString</span> {
        <span class="keyword token">var</span> attributedISBN = exampleISBN.<span class="call token">formatted</span>(.<span class="dotAccess token">isbn13</span>.<span class="property token">attributed</span>)
        <span class="keyword token">for</span> run <span class="keyword token">in</span> attributedISBN.<span class="property token">runs</span> {
            <span class="keyword token">if let</span> isbnRun = run.<span class="property token">isbnPart</span> {
                <span class="keyword token">switch</span> isbnRun {
                <span class="keyword token">case</span> .<span class="dotAccess token">prefix</span>:
                    attributedISBN[run.<span class="property token">range</span>].foregroundColor = .<span class="dotAccess token">magenta</span>
                <span class="keyword token">case</span> .<span class="dotAccess token">registrationGroup</span>:
                    attributedISBN[run.<span class="property token">range</span>].foregroundColor = .<span class="dotAccess token">blue</span>
                <span class="keyword token">case</span> .<span class="dotAccess token">registrant</span>:
                    attributedISBN[run.<span class="property token">range</span>].foregroundColor = .<span class="dotAccess token">green</span>
                <span class="keyword token">case</span> .<span class="dotAccess token">publication</span>:
                    attributedISBN[run.<span class="property token">range</span>].foregroundColor = .<span class="dotAccess token">purple</span>
                <span class="keyword token">case</span> .<span class="dotAccess token">checkDigit</span>:
                    attributedISBN[run.<span class="property token">range</span>].foregroundColor = .<span class="dotAccess token">orange</span>
                <span class="keyword token">case</span> .<span class="dotAccess token">separator</span>:
                    attributedISBN[run.<span class="property token">range</span>].foregroundColor = .<span class="dotAccess token">red</span>
                }
            }
        }
        <span class="keyword token">return</span> attributedISBN
    }

    <span class="keyword token">var</span> body: <span class="keyword token">some</span> <span class="type token">View</span> {
        <span class="type token">Text</span>(attributedString)
            .<span class="call token">padding</span>(<span class="number token">20</span>)
    }
}
</code></pre></div>

<p>Which is going to put the following on screen:</p>

<center><img src="/images/2022/Jun/isbn-attributed-stirng-customization.png" style="margin-bottom: 10px;" /></center>

<h1 id="parsing-strings-into-isbns">Parsing Strings Into ISBNs</h1>

<p>The last piece of functionality we want to add is the ability to parse a string and convert it into an ISBN. <a href="https://ampersandsoftworks.com/posts/from-strings-to-data-using-parseableformatstyle/">I did a <code class="language-plaintext highlighter-rouge">ParseableFormatStyle</code> write-up that can give you more information about the details of this protocol</a>.</p>

<p>Our implementation is going to rely on separators to delimitate each part of the ISBN. While you can use the <a href="https://en.wikipedia.org/wiki/ISBN#ISBN-13_check_digit_calculation">ISBN check digit calculation</a> to validate any 13 digit number as a valid ISBN, there’s no real way to take a 13 digit number and chop it up into known parts. This is because each part of the ISBN could take up any arbitrary amount of those 13 digits, and we can’t effectively make those assumptions without knowing <em>a lot</em> more detail about how they’re used in the real world.</p>

<h2 id="isbn-validation-implementation">ISBN Validation Implementation</h2>

<p>First up, let’s add the check digit calculation validation to our ISBN:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">public extension</span> <span class="type token">ISBN</span> {

    <span class="comment token">// Define our validation errors</span>
    <span class="keyword token">enum</span> ValidationError: <span class="type token">Error</span> {
        <span class="keyword token">case</span> emptyInput
        <span class="keyword token">case</span> noGroupsPresent
        <span class="keyword token">case</span> invalidStringLength
        <span class="keyword token">case</span> invalidCharacters
        <span class="keyword token">case</span> checksumFailed
    }

    <span class="comment token">// Define our valid character set. We avoid using CharacterSet.decimalDigit since that includes
    // all unicode characters which represents digits. ISBN values only use the Arabic numerals,
    // hyphens, or spaces.</span>
    <span class="keyword token">static let</span> validCharacterSet = <span class="type token">CharacterSet</span>(charactersIn: <span class="string token">"0123456789"</span>).<span class="call token">union</span>(validSeparatorsSet)

    <span class="comment token">// Define our valid separators.</span>
    <span class="keyword token">static let</span> validSeparatorsSet = <span class="type token">CharacterSet</span>(charactersIn: <span class="string token">"- "</span>)

    <span class="comment token">// Define the "Bookland" prefix (https://en.wikipedia.org/wiki/Bookland) to convert ISBN-10 values to ISBN-13</span>
    <span class="keyword token">static let</span> booklandPrefix = <span class="string token">"978"</span>

    <span class="comment token">/// Returns a validated, 13 digit ISBN string.
    /// https://en.wikipedia.org/wiki/ISBN#ISBN-13_check_digit_calculation
    /// - Parameter value: A string representation of an ISBN
    /// - Returns: String, the valid String that passed the check.</span>
    <span class="keyword token">static func</span> validate(<span class="keyword token">_</span> candidate: <span class="type token">String</span>?) <span class="keyword token">throws</span> -&gt; <span class="type token">String</span> {

        <span class="comment token">// Unwrap the value passed in.</span>
        <span class="keyword token">guard let</span> candidate = candidate <span class="keyword token">else</span> { <span class="keyword token">throw</span> <span class="type token">ValidationError</span>.<span class="property token">emptyInput</span> }

        <span class="comment token">// Validate that we have spacers present, otherwise we're not going to be able to parse out
        // any ISBN values</span>
        <span class="keyword token">guard</span> candidate.<span class="call token">rangeOfCharacter</span>(from: <span class="type token">Self</span>.<span class="property token">validSeparatorsSet</span>) != <span class="keyword token">nil else</span> {
            <span class="keyword token">throw</span> <span class="type token">ValidationError</span>.<span class="property token">noGroupsPresent</span>
        }

        <span class="comment token">// Trim any leading and trailing whitespace and newlines.
        // Newlines will fail on the next check.</span>
        <span class="keyword token">let</span> trimmedString = candidate.<span class="call token">trimmingCharacters</span>(in: .<span class="dotAccess token">whitespaces</span>)

        <span class="comment token">// Check for the existence of any invalid characters.
        // We invert validCharacterSet to represent every other character in unicode than what is valid.
        // If rangeOfCharacter returns a value, we know that those characters exist (and therefore fails)</span>
        <span class="keyword token">guard</span> trimmedString.<span class="call token">rangeOfCharacter</span>(from: <span class="type token">Self</span>.<span class="property token">validCharacterSet</span>.<span class="property token">inverted</span>) == <span class="keyword token">nil else</span> {
            <span class="comment token">// So we throw the appropriate error</span>
            <span class="keyword token">throw</span> <span class="type token">ValidationError</span>.<span class="property token">invalidCharacters</span>
        }

        <span class="comment token">// Convert any ISBN-10 values into ISBN13 values by adding
        // the "Bookland" prefix (https://en.wikipedia.org/wiki/Bookland)</span>
        <span class="keyword token">let</span> isbn13String = trimmedString.<span class="property token">count</span> == <span class="number token">10</span> ? <span class="type token">Self</span>.<span class="property token">booklandPrefix</span> + trimmedString : trimmedString

        <span class="comment token">// Run the ISBN 13 checksum calculation
        // https://en.wikipedia.org/wiki/ISBN#ISBN-13_check_digit_calculation
        // Use the reduce method to run the checksum, starting with 0
        // We enumerate the string because we need the position (it's offset) for each character, as
        // well as the number itself.

        // Start by removing all of the hyphens</span>
        <span class="keyword token">let</span> isbnString = isbn13String.<span class="call token">components</span>(separatedBy: .<span class="dotAccess token">decimalDigits</span>.<span class="property token">inverted</span>).<span class="call token">joined</span>()

        <span class="comment token">// Verify that we have either 10 or 13 characters at this point.</span>
        <span class="keyword token">guard</span> [<span class="number token">10</span>, <span class="number token">13</span>].<span class="call token">contains</span>(isbnString.<span class="property token">count</span>) <span class="keyword token">else</span> {
            <span class="keyword token">throw</span> <span class="type token">ValidationError</span>.<span class="property token">invalidStringLength</span>
        }

        <span class="comment token">// First, we take the sum of the number. Multiplying each digit by either 1 or 3.</span>
        <span class="keyword token">let</span> sum = isbnString.<span class="call token">enumerated</span>().<span class="call token">reduce</span>(<span class="number token">0</span>) { partialResult, character <span class="keyword token">in</span>

            <span class="comment token">// Safely convert the character into an integer.</span>
            <span class="keyword token">guard let</span> number = character.<span class="property token">element</span>.<span class="property token">wholeNumberValue</span> <span class="keyword token">else</span> {
                <span class="keyword token">return</span> partialResult
            }
            <span class="comment token">// We alternate multiplying each character by 1 or 3</span>
            <span class="keyword token">let</span> multiplier = character.<span class="property token">offset</span> % <span class="number token">2</span> == <span class="number token">0</span> ? <span class="number token">1</span> : <span class="number token">3</span>
            <span class="comment token">// We then multiply the number by the multiplier, and add it to the previous result</span>
            <span class="keyword token">return</span> partialResult + (number * multiplier)
        }
        <span class="comment token">// We then  make sure that the number is cleanly divisible by 10 by using the modulo function.</span>
        <span class="keyword token">guard</span> sum % <span class="number token">10</span> == <span class="number token">0</span> <span class="keyword token">else</span> {
            <span class="keyword token">throw</span> <span class="type token">ValidationError</span>.<span class="property token">checksumFailed</span>
        }

        <span class="comment token">// Success. Return the original ISBN-10 or ISBN-13 string</span>
        <span class="keyword token">return</span> trimmedString
    }
}
</code></pre></div>

<h2 id="creating-our-parsestrategy">Creating our ParseStrategy</h2>

<p>We can then create our <code class="language-plaintext highlighter-rouge">ParseStrategy</code>, extend <code class="language-plaintext highlighter-rouge">ISBN.FormatStyle</code> to conform to <code class="language-plaintext highlighter-rouge">ParseableFormatStyle</code>, add some new initializers to <code class="language-plaintext highlighter-rouge">ISBN</code> that will parse a <code class="language-plaintext highlighter-rouge">String</code>, and extend <code class="language-plaintext highlighter-rouge">ParseableFormatStyle</code> with our new style to allow for simple access.</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">public extension</span> <span class="type token">ISBN</span>.<span class="type token">FormatStyle</span> {

    <span class="keyword token">enum</span> DecodingError: <span class="type token">Error</span> {
        <span class="keyword token">case</span> invalidInput
    }

    <span class="keyword token">struct</span> ParseStrategy: <span class="type token">Foundation</span>.<span class="type token">ParseStrategy</span> {

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

        <span class="keyword token">public func</span> parse(<span class="keyword token">_</span> value: <span class="type token">String</span>) <span class="keyword token">throws</span> -&gt; <span class="type token">ISBN</span> {
            <span class="comment token">// Trim the input string any leading or trailing whitespaces</span>
            <span class="keyword token">let</span> trimmedValue = value.<span class="call token">trimmingCharacters</span>(in: .<span class="dotAccess token">whitespaces</span>)

            <span class="comment token">// Attempt to validate our trimmed string</span>
            <span class="keyword token">let</span> validISBN = <span class="keyword token">try</span> <span class="type token">ISBN</span>.<span class="call token">validate</span>(trimmedValue)

            <span class="comment token">// Create an array of strings based on the separator used.</span>
            <span class="keyword token">let</span> components = validISBN.<span class="call token">components</span>(separatedBy: <span class="type token">ISBN</span>.<span class="property token">validSeparatorsSet</span>)

            <span class="comment token">// Having 4 components means that we were given an ISBN-10 number.
            // Therefore we need to convert it.</span>
            <span class="keyword token">let</span> finalComponents = components.<span class="property token">count</span> == <span class="number token">4</span> ? [<span class="type token">ISBN</span>.<span class="property token">booklandPrefix</span>] + components : components

            <span class="comment token">// Since we're going to use subscripts to access each value in the array, it's a good
            // idea to verify that all values are present to avoid crashing.</span>
            <span class="keyword token">guard</span> finalComponents.<span class="property token">count</span> == <span class="number token">5</span> <span class="keyword token">else</span> {
                <span class="keyword token">throw</span> <span class="type token">DecodingError</span>.<span class="property token">invalidInput</span>
            }

            <span class="comment token">// Build the final ISBN from the component parts.</span>
            <span class="keyword token">return</span> <span class="type token">ISBN</span>(
                prefix: finalComponents[<span class="number token">0</span>],
                registrationGroup: finalComponents[<span class="number token">1</span>],
                registrant: finalComponents[<span class="number token">2</span>],
                publication: finalComponents[<span class="number token">3</span>],
                checkDigit: finalComponents[<span class="number token">4</span>]
            )
        }
    }
}
</code></pre></div>

<h2 id="parseableformatstyle-conformance--convenience-extensions">ParseableFormatStyle Conformance &amp; Convenience Extensions</h2>

<div class="highlight"><pre class="splash"><code><span class="comment token">// MARK: ParseableFormatStyle conformance on ISBN.FormatStyle</span>

<span class="keyword token">extension</span> <span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>: <span class="type token">ParseableFormatStyle</span> {
    <span class="keyword token">public var</span> parseStrategy: <span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>.<span class="type token">ParseStrategy</span> {
        .<span class="keyword token">init</span>()
    }
}

<span class="comment token">// MARK: Convenience members on ISBN to simplify access to the ParseStrategy</span>

<span class="keyword token">public extension</span> <span class="type token">ISBN</span> {

    <span class="keyword token">init</span>(<span class="keyword token">_</span> string: <span class="type token">String</span>) <span class="keyword token">throws</span> {
        <span class="keyword token">self</span> = <span class="keyword token">try</span> <span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>().<span class="property token">parseStrategy</span>.<span class="call token">parse</span>(string)
    }

    <span class="keyword token">init</span>&lt;T, Value&gt;(<span class="keyword token">_</span> value: <span class="type token">Value</span>, standard: <span class="type token">T</span>) <span class="keyword token">throws where</span> <span class="type token">T</span>: <span class="type token">ParseStrategy</span>, <span class="type token">Value</span>: <span class="type token">StringProtocol</span>, <span class="type token">T</span>.<span class="type token">ParseInput</span> == <span class="type token">String</span>, <span class="type token">T</span>.<span class="type token">ParseOutput</span> == <span class="type token">ISBN</span> {
        <span class="keyword token">self</span> = <span class="keyword token">try</span> standard.<span class="call token">parse</span>(value.<span class="property token">description</span>)
    }
}

<span class="comment token">// MARK: Extend ParseableFormatStyle to simplify access to the format style</span>

<span class="keyword token">@available</span>(macOS <span class="number token">12.0</span>, iOS <span class="number token">15.0</span>, tvOS <span class="number token">15.0</span>, watchOS <span class="number token">8.0</span>, *)
<span class="keyword token">public extension</span> <span class="type token">ParseableFormatStyle</span> <span class="keyword token">where</span> <span class="type token">Self</span> == <span class="type token">ISBN</span>.<span class="type token">FormatStyle</span> {
    <span class="keyword token">static var</span> isbn: <span class="type token">Self</span> { .<span class="keyword token">init</span>() }
}
</code></pre></div>

<p>Which gives us the power to do the following:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">try</span>? <span class="type token">ISBN</span>(<span class="string token">"978-17-85889-01-1"</span>) <span class="comment token">// ISBN: 978-17-85889-01-1</span>
<span class="keyword token">try</span>? <span class="type token">ISBN</span>(<span class="string token">"978 17 85889 01 1"</span>) <span class="comment token">// ISBN: 978-17-85889-01-1</span>
<span class="keyword token">try</span>? <span class="type token">ISBN</span>(<span class="string token">" 978-17-85889-01-1 "</span>) <span class="comment token">// ISBN: 978-17-85889-01-1</span>
<span class="keyword token">try</span>? <span class="type token">ISBN</span>(<span class="string token">"978 17-85889-01-1"</span>) <span class="comment token">// ISBN: 978-17-85889-01-1</span>
<span class="keyword token">try</span>? <span class="type token">ISBN</span>(<span class="string token">"978-1-84356-028-9"</span>) <span class="comment token">// ISBN: 978-1-84356-028-9</span>
<span class="keyword token">try</span>? <span class="type token">ISBN</span>(<span class="string token">"978-0-684-84328-5"</span>) <span class="comment token">// ISBN: 978-0-684-84328-5</span>
<span class="keyword token">try</span>? <span class="type token">ISBN</span>(<span class="string token">"978-0-8044-2957-3"</span>) <span class="comment token">// ISBN: 978-0-8044-2957-3</span>
<span class="keyword token">try</span>? <span class="type token">ISBN</span>(<span class="string token">"978-0-85131-041-1"</span>) <span class="comment token">// ISBN: 978-0-85131-041-1</span>
<span class="keyword token">try</span>? <span class="type token">ISBN</span>(<span class="string token">"978-0-943396-04-0"</span>) <span class="comment token">// ISBN: 978-0-943396-04-0</span>
<span class="keyword token">try</span>? <span class="type token">ISBN</span>(<span class="string token">"978-0-9752298-0-4"</span>) <span class="comment token">// ISBN: 978-0-9752298-0-4</span>
</code></pre></div>

<h1 id="bonus-round-unit-testing">Bonus Round: Unit Testing</h1>

<p>Because we’re dealing with a checksum validation, we might also want to write some unit test cases to verify that our implementation is correct.</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">final class</span> ISBNTests: <span class="type token">XCTestCase</span> {

    <span class="keyword token">let</span> isbn = <span class="type token">ISBN</span>(
        prefix: <span class="string token">"978"</span>,
        registrationGroup: <span class="string token">"17"</span>,
        registrant: <span class="string token">"85889"</span>,
        publication: <span class="string token">"01"</span>,
        checkDigit: <span class="string token">"1"</span>
    )

    <span class="keyword token">func</span> testISBN13Output() <span class="keyword token">throws</span> {
        <span class="keyword token">let</span> expectedHyphen = <span class="string token">"978-17-85889-01-1"</span>
        <span class="keyword token">let</span> expectedSpace = <span class="string token">"978 17 85889 01 1"</span>
        <span class="keyword token">let</span> expectedNone = <span class="string token">"9781785889011"</span>

        <span class="call token">XCTAssertEqual</span>(isbn.<span class="call token">formatted</span>(), expectedHyphen)
        <span class="call token">XCTAssertEqual</span>(isbn.<span class="call token">formatted</span>(.<span class="dotAccess token">isbn13</span>), expectedHyphen)
        <span class="call token">XCTAssertEqual</span>(isbn.<span class="call token">formatted</span>(.<span class="dotAccess token">isbn13</span>.<span class="call token">separator</span>(.<span class="dotAccess token">hyphen</span>)), expectedHyphen)
        <span class="call token">XCTAssertEqual</span>(isbn.<span class="call token">formatted</span>(.<span class="dotAccess token">isbn13</span>.<span class="call token">separator</span>(.<span class="dotAccess token">space</span>)), expectedSpace)
        <span class="call token">XCTAssertEqual</span>(isbn.<span class="call token">formatted</span>(.<span class="dotAccess token">isbn13</span>.<span class="call token">separator</span>(.<span class="dotAccess token">none</span>)), expectedNone)

        <span class="call token">XCTAssertEqual</span>(<span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>().<span class="call token">format</span>(isbn), expectedHyphen)
        <span class="call token">XCTAssertEqual</span>(<span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>(.<span class="dotAccess token">isbn13</span>, separator: .<span class="dotAccess token">hyphen</span>).<span class="call token">format</span>(isbn), expectedHyphen)
        <span class="call token">XCTAssertEqual</span>(<span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>(.<span class="dotAccess token">isbn13</span>, separator: .<span class="dotAccess token">space</span>).<span class="call token">format</span>(isbn), expectedSpace)
        <span class="call token">XCTAssertEqual</span>(<span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>(.<span class="dotAccess token">isbn13</span>, separator: .<span class="dotAccess token">none</span>).<span class="call token">format</span>(isbn), expectedNone)
    }

    <span class="keyword token">func</span> testISBN10Output() <span class="keyword token">throws</span> {
        <span class="keyword token">let</span> expectedHyphen = <span class="string token">"17-85889-01-1"</span>
        <span class="keyword token">let</span> expectedSpace = <span class="string token">"17 85889 01 1"</span>
        <span class="keyword token">let</span> expectedNone = <span class="string token">"1785889011"</span>

        <span class="call token">XCTAssertEqual</span>(isbn.<span class="call token">formatted</span>(.<span class="dotAccess token">isbn10</span>), expectedHyphen)
        <span class="call token">XCTAssertEqual</span>(isbn.<span class="call token">formatted</span>(.<span class="dotAccess token">isbn10</span>.<span class="call token">separator</span>(.<span class="dotAccess token">hyphen</span>)), expectedHyphen)
        <span class="call token">XCTAssertEqual</span>(isbn.<span class="call token">formatted</span>(.<span class="dotAccess token">isbn10</span>.<span class="call token">separator</span>(.<span class="dotAccess token">space</span>)), expectedSpace)
        <span class="call token">XCTAssertEqual</span>(isbn.<span class="call token">formatted</span>(.<span class="dotAccess token">isbn10</span>.<span class="call token">separator</span>(.<span class="dotAccess token">none</span>)), expectedNone)

        <span class="call token">XCTAssertEqual</span>(<span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>(.<span class="dotAccess token">isbn10</span>, separator: .<span class="dotAccess token">hyphen</span>).<span class="call token">format</span>(isbn), expectedHyphen)
        <span class="call token">XCTAssertEqual</span>(<span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>(.<span class="dotAccess token">isbn10</span>, separator: .<span class="dotAccess token">space</span>).<span class="call token">format</span>(isbn), expectedSpace)
        <span class="call token">XCTAssertEqual</span>(<span class="type token">ISBN</span>.<span class="type token">FormatStyle</span>(.<span class="dotAccess token">isbn10</span>, separator: .<span class="dotAccess token">none</span>).<span class="call token">format</span>(isbn), expectedNone)
    }

    <span class="keyword token">func</span> testISBNParsing() <span class="keyword token">throws</span> {
        <span class="call token">XCTAssertNoThrow</span>(<span class="keyword token">try</span> <span class="type token">ISBN</span>(<span class="string token">"978-17-85889-01-1"</span>))
        <span class="call token">XCTAssertNoThrow</span>(<span class="keyword token">try</span> <span class="type token">ISBN</span>(<span class="string token">"978 17 85889 01 1"</span>))
        <span class="call token">XCTAssertNoThrow</span>(<span class="keyword token">try</span> <span class="type token">ISBN</span>(<span class="string token">" 978-17-85889-01-1 "</span>))
        <span class="call token">XCTAssertNoThrow</span>(<span class="keyword token">try</span> <span class="type token">ISBN</span>(<span class="string token">"978 17-85889-01-1"</span>))
        <span class="call token">XCTAssertNoThrow</span>(<span class="keyword token">try</span> <span class="type token">ISBN</span>(<span class="string token">"978-1-84356-028-9"</span>))
        <span class="call token">XCTAssertNoThrow</span>(<span class="keyword token">try</span> <span class="type token">ISBN</span>(<span class="string token">"978-0-684-84328-5"</span>))
        <span class="call token">XCTAssertNoThrow</span>(<span class="keyword token">try</span> <span class="type token">ISBN</span>(<span class="string token">"978-0-8044-2957-3"</span>))
        <span class="call token">XCTAssertNoThrow</span>(<span class="keyword token">try</span> <span class="type token">ISBN</span>(<span class="string token">"978-0-85131-041-1"</span>))
        <span class="call token">XCTAssertNoThrow</span>(<span class="keyword token">try</span> <span class="type token">ISBN</span>(<span class="string token">"978-0-943396-04-0"</span>))
        <span class="call token">XCTAssertNoThrow</span>(<span class="keyword token">try</span> <span class="type token">ISBN</span>(<span class="string token">"978-0-9752298-0-4"</span>))

        <span class="call token">XCTAssertNoThrow</span>(<span class="keyword token">try</span> <span class="type token">ISBN</span>(<span class="string token">"17-85889-01-1"</span>))
        <span class="call token">XCTAssertNoThrow</span>(<span class="keyword token">try</span> <span class="type token">ISBN</span>(<span class="string token">"17 85889 01 1"</span>))

        <span class="call token">XCTAssertThrowsError</span>(<span class="keyword token">try</span> <span class="type token">ISBN</span>(<span class="string token">"9780975229804"</span>))
        <span class="call token">XCTAssertThrowsError</span>(<span class="keyword token">try</span> <span class="type token">ISBN</span>(<span class="string token">"0"</span>))
        <span class="call token">XCTAssertThrowsError</span>(<span class="keyword token">try</span> <span class="type token">ISBN</span>(<span class="string token">"98 17 85889 01 1"</span>))
    }
}
</code></pre></div>

<p><a href="https://github.com/brettohland/ampersandsoftworks.com-examples/tree/main/%5B2022-06-30%5D%20ISBN-FormatStyle">Download the Xcode Playground</a> or <a href="https://gist.github.com/brettohland/744fcbd2a8aa77907ec84a286e8da3b0">See everything as a Gist</a></p>]]></content><author><name>brett ohland</name></author><category term="ios" /><category term="formatstyle" /><category term="development" /><category term="swift" /><summary type="html"><![CDATA[A full example of adding all of the bells and whistles of ParseableFormatStyle onto your own types, including AttributedString output.]]></summary></entry><entry><title type="html">From Strings to Data using ParsableFormatStyle</title><link href="https://ampersandsoftworks.com/posts/from-strings-to-data-using-parseableformatstyle/index.html" rel="alternate" type="text/html" title="From Strings to Data using ParsableFormatStyle" /><published>2022-06-14T00:00:00-06:00</published><updated>2022-06-14T00:00:00-06:00</updated><id>https://ampersandsoftworks.com/posts/from-strings-to-data-using-parseableformatstyle/from-strings-to-data-using-parseableformatstyle</id><content type="html" xml:base="https://ampersandsoftworks.com/posts/from-strings-to-data-using-parseableformatstyle/index.html"><![CDATA[<p><strong>TL;DR</strong> <a href="https://github.com/brettohland/ampersandsoftworks.com-examples/tree/main/%5B2022-06-14%5D%20ParseableFormatStyle">Xcode Playground</a> or <a href="https://gist.github.com/brettohland/f07fa1069e495d96dda098f13adaefae">Examples as Gist</a></p>

<p>The venerable <a href="https://developer.apple.com/documentation/foundation/formatter/">(NS)Formatter class (and Apple’s various subclasses)</a> are an Objective-C based API that is most well known as the go-to method for converting data types into strings. One of the lesser-known features of the APIs are that these same formatters can do the reverse: parse strings into their respective data types.</p>

<p>Apple’s modern Swift replacement system for <code class="language-plaintext highlighter-rouge">Formatter</code> is a set of protocols: <code class="language-plaintext highlighter-rouge">FormatStyle</code> and <code class="language-plaintext highlighter-rouge">ParseableFormatStyle</code>. The former handles the conversion to strings, and the latter strings to data.</p>

<blockquote>
  <p>One small thing. I mention conversion to and from strings here specifically. But these two protocols are completely type agnostic. You can convert to and from any data type. Follow your dreams.</p>
</blockquote>

<p><code class="language-plaintext highlighter-rouge">FormatStyle</code> and it’s various implementations is it’s own beast. Apple’s various implementations to support the built-in Foundation data types is quite extensive but spottily documented. <a href="https://goshdarnformatstyle.com">I made a whole site to help you use them</a>.</p>

<p>But that’s not what we’re going to talk about today.</p>

<p>Today we’re going to talk about <code class="language-plaintext highlighter-rouge">ParseableFormatStyle</code> and it’s implementations. How can we convert some strings into data?</p>

<h1 id="what-is-parseableformatstyle-anyway">What is ParseableFormatStyle Anyway?</h1>

<p>The <code class="language-plaintext highlighter-rouge">ParseableFormatStyle</code> protocol is quite simple, it inherits from <code class="language-plaintext highlighter-rouge">FormatStyle</code> and simply defines a <code class="language-plaintext highlighter-rouge">ParseStrategy</code> property:</p>

<div class="highlight"><pre class="splash"><code><span class="comment token">/// A type that can convert a given data type into a representation.</span>
<span class="keyword token">@available</span>(macOS <span class="number token">12.0</span>, iOS <span class="number token">15.0</span>, tvOS <span class="number token">15.0</span>, watchOS <span class="number token">8.0</span>, *)
<span class="keyword token">public protocol</span> ParseableFormatStyle : <span class="type token">FormatStyle</span> {

    <span class="keyword token">associatedtype</span> Strategy : <span class="type token">ParseStrategy</span> <span class="keyword token">where</span> <span class="type token">Self</span>.<span class="type token">FormatInput</span> == <span class="type token">Self</span>.<span class="type token">Strategy</span>.<span class="type token">ParseOutput</span>, <span class="type token">Self</span>.<span class="type token">FormatOutput</span> == <span class="type token">Self</span>.<span class="type token">Strategy</span>.<span class="type token">ParseInput</span>

    <span class="comment token">/// A `ParseStrategy` that can be used to parse this `FormatStyle`'s output</span>
    <span class="keyword token">var</span> parseStrategy: <span class="type token">Self</span>.<span class="type token">Strategy</span> { <span class="keyword token">get</span> }
}
</code></pre></div>

<p><a href="https://developer.apple.com/documentation/foundation/parseableformatstyle/">Apple’s Documentation for ParseableFormatStyle</a></p>

<p>Okay, so what’s <code class="language-plaintext highlighter-rouge">ParseStrategy</code> then?</p>

<div class="highlight"><pre class="splash"><code><span class="comment token">/// A type that can parse a representation of a given data type.</span>
<span class="keyword token">@available</span>(macOS <span class="number token">12.0</span>, iOS <span class="number token">15.0</span>, tvOS <span class="number token">15.0</span>, watchOS <span class="number token">8.0</span>, *)
<span class="keyword token">public protocol</span> ParseStrategy : <span class="type token">Decodable</span>, <span class="type token">Encodable</span>, <span class="type token">Hashable</span> {

    <span class="comment token">/// The type of the representation describing the data.</span>
    <span class="keyword token">associatedtype</span> ParseInput

    <span class="comment token">/// The type of the data type.</span>
    <span class="keyword token">associatedtype</span> ParseOutput

    <span class="comment token">/// Creates an instance of the `ParseOutput` type from `value`.</span>
    <span class="keyword token">func</span> parse(<span class="keyword token">_</span> value: <span class="type token">Self</span>.<span class="type token">ParseInput</span>) <span class="keyword token">throws</span> -&gt; <span class="type token">Self</span>.<span class="type token">ParseOutput</span>
}
</code></pre></div>

<p><a href="https://developer.apple.com/documentation/foundation/parsestrategy">Apple’s Documentation for ParseStrategy</a></p>

<p>The protocols themselves are concise, and to the point. You can very easily use them to bolt this functionality onto your own custom types.</p>

<h1 id="how-do-i-use-it">How Do I Use It?</h1>

<p>The most direct way of parsing a string into it’s respective data type is to create an instance of a <code class="language-plaintext highlighter-rouge">ParseableFormatStyle</code> that’s set up to understand the structure of the incoming string. From there you access it’s <code class="language-plaintext highlighter-rouge">parseStrategy</code> property, and call the <code class="language-plaintext highlighter-rouge">parse()</code> method on it.</p>

<p>This is a bit cumbersome, so Apple has included custom initializers onto each of the supported data types that take the string and either a <code class="language-plaintext highlighter-rouge">ParseableFormatStyle</code> or a <code class="language-plaintext highlighter-rouge">ParseStrategy</code> instance to do the parsing. What’s interesting is that Apple includes initializers that can accept <em>any</em> input type, as long as you provide a <code class="language-plaintext highlighter-rouge">ParseStrategy</code> that informs the type how to parse it. Aren’t constrained generics neat?</p>

<h1 id="what-types-are-supported">What Types Are Supported?</h1>

<p>You can parse:</p>

<ul>
  <li>Dates</li>
  <li>Decimals (Numbers, Percentages, Currency)</li>
  <li>Person Names</li>
  <li>URLs (iOS 16 only)</li>
</ul>

<p>In general, you have two ways of accessing the parsing code:</p>

<h2 id="parsing-numbers">Parsing Numbers</h2>

<p>All of Swift’s numerical styles are supported with a new initializer.</p>

<div class="highlight"><pre class="splash"><code><span class="comment token">// MARK: Parsing Integers</span>
<span class="keyword token">try</span>? <span class="type token">Int</span>(<span class="string token">"120"</span>, format: .<span class="dotAccess token">number</span>) <span class="comment token">// 120</span>
<span class="keyword token">try</span>? <span class="type token">Int</span>(<span class="string token">"0.25"</span>, format: .<span class="dotAccess token">number</span>) <span class="comment token">// 0</span>
<span class="keyword token">try</span>? <span class="type token">Int</span>(<span class="string token">"1E5"</span>, format: .<span class="dotAccess token">number</span>.<span class="call token">notation</span>(.<span class="dotAccess token">scientific</span>)) <span class="comment token">// 100000

// MARK: Parsing Floating Point Numbers</span>
<span class="keyword token">try</span>? <span class="type token">Double</span>(<span class="string token">"0.0025"</span>, format: .<span class="dotAccess token">number</span>) <span class="comment token">// 0.0025</span>
<span class="keyword token">try</span>? <span class="type token">Double</span>(<span class="string token">"95%"</span>, format: .<span class="dotAccess token">number</span>) <span class="comment token">// 95</span>
<span class="keyword token">try</span>? <span class="type token">Double</span>(<span class="string token">"95%"</span>, format: .<span class="dotAccess token">percent</span>) <span class="comment token">// 95</span>
<span class="keyword token">try</span>? <span class="type token">Double</span>(<span class="string token">"1E5"</span>, format: .<span class="dotAccess token">number</span>.<span class="call token">notation</span>(.<span class="dotAccess token">scientific</span>)) <span class="comment token">// 100000</span>

<span class="keyword token">try</span>? <span class="type token">Float</span>(<span class="string token">"0.0025"</span>, format: .<span class="dotAccess token">number</span>) <span class="comment token">// 0.0025</span>
<span class="keyword token">try</span>? <span class="type token">Float</span>(<span class="string token">"95%"</span>, format: .<span class="dotAccess token">number</span>) <span class="comment token">// 95</span>
<span class="keyword token">try</span>? <span class="type token">Float</span>(<span class="string token">"1E5"</span>, format: .<span class="dotAccess token">number</span>.<span class="call token">notation</span>(.<span class="dotAccess token">scientific</span>)) <span class="comment token">// 100000

// MARK: Parsing Decimals</span>
<span class="keyword token">try</span>? <span class="type token">Decimal</span>(<span class="string token">"0.0025"</span>, format: .<span class="dotAccess token">number</span>) <span class="comment token">// 0.0025</span>
<span class="keyword token">try</span>? <span class="type token">Decimal</span>(<span class="string token">"95%"</span>, format: .<span class="dotAccess token">number</span>) <span class="comment token">// 95</span>
<span class="keyword token">try</span>? <span class="type token">Decimal</span>(<span class="string token">"1E5"</span>, format: .<span class="dotAccess token">number</span>.<span class="call token">notation</span>(.<span class="dotAccess token">scientific</span>)) <span class="comment token">// 100000

// MARK: Parsing Percentages</span>
<span class="keyword token">try</span>? <span class="type token">Int</span>(<span class="string token">"98%"</span>, format: .<span class="dotAccess token">percent</span>) <span class="comment token">// 98</span>
<span class="keyword token">try</span>? <span class="type token">Float</span>(<span class="string token">"95%"</span>, format: .<span class="dotAccess token">percent</span>) <span class="comment token">// 0.95</span>
<span class="keyword token">try</span>? <span class="type token">Decimal</span>(<span class="string token">"95%"</span>, format: .<span class="dotAccess token">percent</span>) <span class="comment token">// 0.95

// MARK: Parsing Currencies</span>
<span class="keyword token">try</span>? <span class="type token">Decimal</span>(<span class="string token">"$100.25"</span>, format: .<span class="call token">currency</span>(code: <span class="string token">"USD"</span>)) <span class="comment token">// 100.25</span>
<span class="keyword token">try</span>? <span class="type token">Decimal</span>(<span class="string token">"100.25 British Points"</span>, format: .<span class="call token">currency</span>(code: <span class="string token">"GBP"</span>)) <span class="comment token">// 100.25</span>
</code></pre></div>

<h2 id="parsing-dates">Parsing Dates</h2>

<p>While there’s <a href="https://goshdarnformatstyle.com/#date-and-time-single-date">a myriad of different ways to format a <code class="language-plaintext highlighter-rouge">Date</code> object for display using the various included format styles</a>. The only two that conform to <code class="language-plaintext highlighter-rouge">ParseableFormatStyle</code> are <code class="language-plaintext highlighter-rouge">Date.FormatStyle</code> and <code class="language-plaintext highlighter-rouge">Date.ISO8601FormatStyle</code>.</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">try</span>? <span class="type token">Date</span>.<span class="type token">FormatStyle</span>()
    .<span class="call token">day</span>()
    .<span class="call token">month</span>()
    .<span class="call token">year</span>()
    .<span class="call token">hour</span>()
    .<span class="call token">minute</span>()
    .<span class="call token">second</span>()
    .<span class="call token">parse</span>(<span class="string token">"Feb 22, 2022, 2:22:22 AM"</span>) <span class="comment token">// Feb 22, 2022, 2:22:22 AM</span>

<span class="keyword token">try</span>? <span class="type token">Date</span>.<span class="type token">FormatStyle</span>()
    .<span class="call token">day</span>()
    .<span class="call token">month</span>()
    .<span class="call token">year</span>()
    .<span class="call token">hour</span>()
    .<span class="call token">minute</span>()
    .<span class="call token">second</span>()
    .<span class="dotAccess token">parseStrategy</span>.<span class="call token">parse</span>(<span class="string token">"Feb 22, 2022, 2:22:22 AM"</span>) <span class="comment token">// Feb 22, 2022, 2:22:22 AM</span>

<span class="keyword token">try</span>? <span class="type token">Date</span>.<span class="type token">ISO8601FormatStyle</span>(timeZone: <span class="type token">TimeZone</span>(secondsFromGMT: <span class="number token">0</span>)!)
    .<span class="call token">year</span>()
    .<span class="call token">day</span>()
    .<span class="call token">month</span>()
    .<span class="call token">dateSeparator</span>(.<span class="dotAccess token">dash</span>)
    .<span class="call token">dateTimeSeparator</span>(.<span class="dotAccess token">standard</span>)
    .<span class="call token">timeSeparator</span>(.<span class="dotAccess token">colon</span>)
    .<span class="call token">timeZoneSeparator</span>(.<span class="dotAccess token">colon</span>)
    .<span class="call token">time</span>(includingFractionalSeconds: <span class="keyword token">true</span>)
    .<span class="call token">parse</span>(<span class="string token">"2022-02-22T09:22:22.000"</span>) <span class="comment token">// Feb 22, 2022, 2:22:22 AM</span>

<span class="keyword token">try</span>? <span class="type token">Date</span>.<span class="type token">ISO8601FormatStyle</span>(timeZone: <span class="type token">TimeZone</span>(secondsFromGMT: <span class="number token">0</span>)!)
    .<span class="call token">year</span>()
    .<span class="call token">day</span>()
    .<span class="call token">month</span>()
    .<span class="call token">dateSeparator</span>(.<span class="dotAccess token">dash</span>)
    .<span class="call token">dateTimeSeparator</span>(.<span class="dotAccess token">standard</span>)
    .<span class="call token">timeSeparator</span>(.<span class="dotAccess token">colon</span>)
    .<span class="call token">timeZoneSeparator</span>(.<span class="dotAccess token">colon</span>)
    .<span class="call token">time</span>(includingFractionalSeconds: <span class="keyword token">true</span>)
    .<span class="dotAccess token">parseStrategy</span>.<span class="call token">parse</span>(<span class="string token">"2022-02-22T09:22:22.000"</span>) <span class="comment token">// Feb 22, 2022, 2:22:22 AM</span>

<span class="keyword token">try</span>? <span class="type token">Date</span>(
    <span class="string token">"Feb 22, 2022, 2:22:22 AM"</span>,
    strategy: <span class="type token">Date</span>.<span class="type token">FormatStyle</span>().<span class="call token">day</span>().<span class="call token">month</span>().<span class="call token">year</span>().<span class="call token">hour</span>().<span class="call token">minute</span>().<span class="call token">second</span>().<span class="property token">parseStrategy</span>
) <span class="comment token">// Feb 22, 2022 at 2:22 AM</span>

<span class="keyword token">try</span>? <span class="type token">Date</span>(
    <span class="string token">"2022-02-22T09:22:22.000"</span>,
    strategy: <span class="type token">Date</span>.<span class="type token">ISO8601FormatStyle</span>(timeZone: <span class="type token">TimeZone</span>(secondsFromGMT: <span class="number token">0</span>)!)
        .<span class="call token">year</span>()
        .<span class="call token">day</span>()
        .<span class="call token">month</span>()
        .<span class="call token">dateSeparator</span>(.<span class="dotAccess token">dash</span>)
        .<span class="call token">dateTimeSeparator</span>(.<span class="dotAccess token">standard</span>)
        .<span class="call token">timeSeparator</span>(.<span class="dotAccess token">colon</span>)
        .<span class="call token">timeZoneSeparator</span>(.<span class="dotAccess token">colon</span>)
        .<span class="call token">time</span>(includingFractionalSeconds: <span class="keyword token">true</span>)
        .<span class="dotAccess token">parseStrategy</span>
) <span class="comment token">// Feb 22, 2022 at 2:22 AM</span>
</code></pre></div>

<h2 id="parsing-names">Parsing Names</h2>

<p>Parsing Names is helpful when you just don’t want to think about how various locals handle the order and display of given and family names.</p>

<div class="highlight"><pre class="splash"><code><span class="comment token">// namePrefix: Dr givenName: Elizabeth middleName: Jillian familyName: Smith nameSuffix: Esq.</span>
<span class="keyword token">try</span>? <span class="type token">PersonNameComponents</span>.<span class="type token">FormatStyle</span>()
    .<span class="dotAccess token">parseStrategy</span>.<span class="call token">parse</span>(<span class="string token">"Dr Elizabeth Jillian Smith Esq."</span>)

<span class="comment token">// namePrefix: Dr givenName: Elizabeth middleName: Jillian familyName: Smith nameSuffix: Esq.</span>
<span class="keyword token">try</span>? <span class="type token">PersonNameComponents</span>.<span class="type token">FormatStyle</span>(style: .<span class="dotAccess token">long</span>)
    .<span class="dotAccess token">parseStrategy</span>.<span class="call token">parse</span>(<span class="string token">"Dr Elizabeth Jillian Smith Esq."</span>)

<span class="comment token">// namePrefix: Dr givenName: Elizabeth middleName: Jillian familyName: Smith nameSuffix: Esq.</span>
<span class="keyword token">try</span>? <span class="type token">PersonNameComponents</span>.<span class="type token">FormatStyle</span>(style: .<span class="dotAccess token">long</span>, locale: <span class="type token">Locale</span>(identifier: <span class="string token">"zh_CN"</span>))
    .<span class="dotAccess token">parseStrategy</span>.<span class="call token">parse</span>(<span class="string token">"Dr Smith Elizabeth Jillian Esq."</span>)

<span class="comment token">// namePrefix: Dr givenName: Elizabeth middleName: Jillian familyName: Smith nameSuffix: Esq.</span>
<span class="keyword token">try</span>? <span class="type token">PersonNameComponents</span>.<span class="type token">FormatStyle</span>(style: .<span class="dotAccess token">long</span>)
    .<span class="call token">locale</span>(<span class="type token">Locale</span>(identifier: <span class="string token">"zh_CN"</span>))
    .<span class="dotAccess token">parseStrategy</span>.<span class="call token">parse</span>(<span class="string token">"Dr Smith Elizabeth Jillian Esq."</span>)

<span class="comment token">// namePrefix: Dr givenName: Elizabeth middleName: Jillian familyName: Smith nameSuffix: Esq.</span>
<span class="keyword token">try</span>? <span class="type token">PersonNameComponents</span>(
    <span class="string token">"Dr Elizabeth Jillian Smith Esq."</span>,
    strategy: <span class="type token">PersonNameComponents</span>.<span class="type token">FormatStyle</span>(style: .<span class="dotAccess token">long</span>).<span class="property token">parseStrategy</span>
)
</code></pre></div>

<h2 id="urls-ios-16xcode-14-only">URLs (iOS 16/Xcode 14 only)</h2>

<p>Xcode 14, you can now use the new <code class="language-plaintext highlighter-rouge">URL.FormatStyle.ParseStrategy</code> struct to parse URLs (as an alternative to using the venerable <code class="language-plaintext highlighter-rouge">URL(string:relativeTo)</code> initializer).</p>

<p>You can set as options for each component to be required, optional, or default to a set value:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">try</span> <span class="type token">URL</span>.<span class="type token">FormatStyle</span>.<span class="type token">Strategy</span>(port: .<span class="call token">defaultValue</span>(<span class="number token">80</span>)).<span class="call token">parse</span>(<span class="string token">"http://www.apple.com"</span>) <span class="comment token">// http://www.apple.com:80</span>
<span class="keyword token">try</span> <span class="type token">URL</span>.<span class="type token">FormatStyle</span>.<span class="type token">Strategy</span>(port: .<span class="dotAccess token">optional</span>).<span class="call token">parse</span>(<span class="string token">"http://www.apple.com"</span>) <span class="comment token">// http://www.apple.com</span>
<span class="keyword token">try</span> <span class="type token">URL</span>.<span class="type token">FormatStyle</span>.<span class="type token">Strategy</span>(port: .<span class="dotAccess token">required</span>).<span class="call token">parse</span>(<span class="string token">"http://www.apple.com"</span>) <span class="comment token">// throws an error

// This returns a valid URL</span>
<span class="keyword token">try</span> <span class="type token">URL</span>.<span class="type token">FormatStyle</span>.<span class="type token">Strategy</span>()
    .<span class="call token">scheme</span>(.<span class="dotAccess token">required</span>)
    .<span class="call token">user</span>(.<span class="dotAccess token">required</span>)
    .<span class="call token">password</span>(.<span class="dotAccess token">required</span>)
    .<span class="call token">host</span>(.<span class="dotAccess token">required</span>)
    .<span class="call token">port</span>(.<span class="dotAccess token">required</span>)
    .<span class="call token">path</span>(.<span class="dotAccess token">required</span>)
    .<span class="call token">query</span>(.<span class="dotAccess token">required</span>)
    .<span class="call token">fragment</span>(.<span class="dotAccess token">required</span>)
    .<span class="call token">parse</span>(<span class="string token">"https://jAppleseed:Test1234@apple.com:80/macbook-pro?get-free#someFragmentOfSomething"</span>)

<span class="comment token">// This throws an error (the port is missing)</span>
<span class="keyword token">try</span> <span class="type token">URL</span>.<span class="type token">FormatStyle</span>.<span class="type token">Strategy</span>()
    .<span class="call token">scheme</span>(.<span class="dotAccess token">required</span>)
    .<span class="call token">user</span>(.<span class="dotAccess token">required</span>)
    .<span class="call token">password</span>(.<span class="dotAccess token">required</span>)
    .<span class="call token">host</span>(.<span class="dotAccess token">required</span>)
    .<span class="call token">port</span>(.<span class="dotAccess token">required</span>)
    .<span class="call token">path</span>(.<span class="dotAccess token">required</span>)
    .<span class="call token">query</span>(.<span class="dotAccess token">required</span>)
    .<span class="call token">fragment</span>(.<span class="dotAccess token">required</span>)
    .<span class="call token">parse</span>(<span class="string token">"https://jAppleseed:Test1234@apple.com/macbook-pro?get-free#someFragmentOfSomething"</span>)
</code></pre></div>

<p>By default, only the scheme and host are required.</p>

<hr />

<p><a href="https://github.com/brettohland/ParseableFormatStyle-Examples">Xcode Playground</a> or <a href="https://gist.github.com/brettohland/f07fa1069e495d96dda098f13adaefae">Examples as Gist</a></p>]]></content><author><name>brett ohland</name></author><category term="ios" /><category term="formatstyle" /><category term="development" /><category term="swift" /><summary type="html"><![CDATA[The other side of the FormatStyle coin, getting our data from strings.]]></summary></entry><entry><title type="html">What’s new with FormatStyles in iOS 16</title><link href="https://ampersandsoftworks.com/posts/iOS-16-formatstyle/index.html" rel="alternate" type="text/html" title="What’s new with FormatStyles in iOS 16" /><published>2022-06-09T00:00:00-06:00</published><updated>2022-06-09T00:00:00-06:00</updated><id>https://ampersandsoftworks.com/posts/iOS-16-formatstyle/iOS-16-formatstyle</id><content type="html" xml:base="https://ampersandsoftworks.com/posts/iOS-16-formatstyle/index.html"><![CDATA[<p>Another year, another WWDC. The yearly developer conference was firing on all cylinders this year, with some nice additions and one big fix on the <code class="language-plaintext highlighter-rouge">FormatStyle</code> front.</p>

<blockquote>
  <p>This only scratches the surface on the topic! I built a whole website to cover format styles in excruciating detail: <a href="https://fuckingformatstyle.com">fuckingformatstyle.com</a>/<a href="https://goshdarnformatstyle.com">goshdarnformatstyle.com</a></p>
</blockquote>

<hr />

<h2 id="see-some-examples-as-a-gist"><a href="https://gist.github.com/brettohland/fcda4acd2c80d04de866e4ed332b8483">See some examples as a Gist</a></h2>

<hr />

<h2 id="1-bytecountformatstyle-doesnt-crash-anymore">1. ByteCountFormatStyle Doesn’t Crash Anymore</h2>

<p>The first good news is that Apple has fixed an issue I found in my research.</p>

<p>The formatter gives you the ability to take a byte count and convert to other orders of magnatude. For example, you could take a count in kilobytes and convert it to terabytes.</p>

<p>Previously, converting any count to any unit of gigabyte or above would result in a <code class="language-plaintext highlighter-rouge">fatalError</code> crash:</p>

<div class="highlight"><pre class="splash"><code><span class="comment token">// .gb, .tb, .pb, .eb, .zb, and .ybOrHigher cause a FatalError (Feedback FB10031442)</span>
terabyte.<span class="call token">formatted</span>(.<span class="call token">byteCount</span>(style: .<span class="dotAccess token">file</span>, allowedUnits: .<span class="dotAccess token">gb</span>))
</code></pre></div>

<p>I’m happy to report that as of 14.0 beta (14A5228q), this now works as expected.</p>

<div class="highlight"><pre class="splash"><code>terabyte.<span class="call token">formatted</span>(.<span class="call token">byteCount</span>(style: .<span class="dotAccess token">file</span>, allowedUnits: .<span class="dotAccess token">gb</span>)) <span class="comment token">// "1,000 GB"</span>
</code></pre></div>

<h2 id="2-new-measurement-style-for-unitinformatstorage">2. New Measurement style for UnitInformatStorage</h2>

<p>I mentioned on the <a href="https://goshdarnsyntaxstyle.com">gosh darned site</a> that you could use the Measurement format style to convert between units. The issue is that you aren’t able the same customization options as the <code class="language-plaintext highlighter-rouge">ByteCountFormatStyle</code> using that option.</p>

<p>That’s changed now.</p>

<p>We now have an API for the byte count format style on top of the Measurement framework when using the <code class="language-plaintext highlighter-rouge">UnitInformationStorage</code> unit.</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> severalTerabytes = <span class="type token">Measurement</span>(value: <span class="number token">3</span>, unit: <span class="type token">UnitInformationStorage</span>.<span class="property token">terabytes</span>)

severalTerabytes.<span class="call token">formatted</span>() <span class="comment token">// "3 TB"</span>
severalTerabytes.<span class="call token">formatted</span>(.<span class="call token">byteCount</span>(style: .<span class="dotAccess token">binary</span>)) <span class="comment token">// "2.73 TB"</span>
severalTerabytes.<span class="call token">formatted</span>(.<span class="call token">byteCount</span>(style: .<span class="dotAccess token">decimal</span>)) <span class="comment token">// "3 TB"</span>
severalTerabytes.<span class="call token">formatted</span>(.<span class="call token">byteCount</span>(style: .<span class="dotAccess token">file</span>)) <span class="comment token">// "3 TB"</span>
severalTerabytes.<span class="call token">formatted</span>(
    .<span class="call token">byteCount</span>(
        style: .<span class="dotAccess token">binary</span>,
        allowedUnits: .<span class="dotAccess token">tb</span>,
        spellsOutZero: <span class="keyword token">true</span>,
        includesActualByteCount: <span class="keyword token">true</span>
    )
) <span class="comment token">// "2.73 TB (3,000,000,000,000 bytes)"</span>
severalTerabytes.<span class="call token">formatted</span>(
    .<span class="call token">byteCount</span>(
        style: .<span class="dotAccess token">binary</span>,
        allowedUnits: .<span class="dotAccess token">tb</span>,
        spellsOutZero: <span class="keyword token">true</span>,
        includesActualByteCount: <span class="keyword token">false</span>
    )
) <span class="comment token">// "2.73 TB"</span>

severalTerabytes.<span class="call token">formatted</span>(
    .<span class="call token">byteCount</span>(
        style: .<span class="dotAccess token">binary</span>,
        allowedUnits: .<span class="dotAccess token">tb</span>,
        spellsOutZero: <span class="keyword token">true</span>,
        includesActualByteCount: <span class="keyword token">true</span>
    )
    .<span class="call token">locale</span>(<span class="type token">Locale</span>(identifier: <span class="string token">"fr_FR"</span>))
) <span class="comment token">// "2,73 To (3 000 000 000 000 octets)"</span>

severalTerabytes.<span class="call token">formatted</span>(
    .<span class="call token">byteCount</span>(
        style: .<span class="dotAccess token">binary</span>,
        allowedUnits: .<span class="dotAccess token">tb</span>,
        spellsOutZero: <span class="keyword token">true</span>,
        includesActualByteCount: <span class="keyword token">true</span>
    )
    .<span class="dotAccess token">attributed</span>
)

<span class="keyword token">let</span> byteCountMeasurementStyle = <span class="type token">Measurement</span>&lt;<span class="type token">UnitInformationStorage</span>&gt;.<span class="type token">FormatStyle</span>.<span class="type token">ByteCount</span>(
    style: .<span class="dotAccess token">binary</span>,
    allowedUnits: .<span class="dotAccess token">mb</span>,
    spellsOutZero: <span class="keyword token">true</span>,
    includesActualByteCount: <span class="keyword token">true</span>,
    locale: <span class="type token">Locale</span>(identifier: <span class="string token">"fr_FR"</span>)
)

severalTerabytes.<span class="call token">formatted</span>(byteCountMeasurementStyle) <span class="comment token">// "2 861 022,9 Mo (3 000 000 000 000 octets)"

// This no longer results in a crash.</span>
<span class="keyword token">let</span> threeTerabytes = <span class="type token">Int64</span>(<span class="number token">3_000_000_000_000</span>)
threeTerabytes.<span class="call token">formatted</span>(
    .<span class="call token">byteCount</span>(
        style: .<span class="dotAccess token">binary</span>,
        allowedUnits: .<span class="dotAccess token">tb</span>,
        spellsOutZero: <span class="keyword token">false</span>,
        includesActualByteCount: <span class="keyword token">true</span>
    )
) <span class="comment token">// "2.73 TB (3,000,000,000,000 bytes)"</span>
</code></pre></div>

<p>Very handy.</p>

<h2 id="3-new-duration-unit-support">3. New <code class="language-plaintext highlighter-rouge">Duration</code> Unit Support</h2>

<p>iOS 16 introduces the new <code class="language-plaintext highlighter-rouge">Duration</code> unit, which is purpose built to deal with very accurate time measurements. There’s two new styles to support it.</p>

<h3 id="timeformatstyle">TimeFormatStyle</h3>

<p>The simpler of the two, this one is the default</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> coupleOfSeconds: <span class="type token">Duration</span> = .<span class="call token">seconds</span>(<span class="number token">3</span>)
</code></pre></div>

<p>With it, they’ve added a new build in format style to allow us to output the values in a nice way:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> thousandSeconds: <span class="type token">Duration</span> = .<span class="call token">seconds</span>(<span class="number token">1000</span>)

thousandSeconds.<span class="call token">formatted</span>() <span class="comment token">// "0:16:40"</span>
thousandSeconds.<span class="call token">formatted</span>(.<span class="call token">time</span>(pattern: .<span class="dotAccess token">hourMinute</span>)) <span class="comment token">// "0:17"</span>
thousandSeconds.<span class="call token">formatted</span>(.<span class="call token">time</span>(pattern: .<span class="dotAccess token">hourMinute</span>).<span class="call token">locale</span>(<span class="type token">Locale</span>(identifier: <span class="string token">"fr_FR"</span>))) <span class="comment token">// "0:17"</span>
thousandSeconds.<span class="call token">formatted</span>(.<span class="call token">time</span>(pattern: .<span class="call token">hourMinute</span>(padHourToLength: <span class="number token">10</span>, roundSeconds: .<span class="dotAccess token">awayFromZero</span>))) <span class="comment token">// "0,000,000,000:17"</span>
thousandSeconds.<span class="call token">formatted</span>(.<span class="call token">time</span>(pattern: .<span class="dotAccess token">hourMinuteSecond</span>)) <span class="comment token">// "0:16:40"</span>
thousandSeconds.<span class="call token">formatted</span>(.<span class="call token">time</span>(pattern: .<span class="call token">hourMinuteSecond</span>(padHourToLength: <span class="number token">3</span>, fractionalSecondsLength: <span class="number token">3</span>,  roundFractionalSeconds: .<span class="dotAccess token">awayFromZero</span>))) <span class="comment token">// "000:16:40.000"</span>
thousandSeconds.<span class="call token">formatted</span>(.<span class="call token">time</span>(pattern: .<span class="dotAccess token">minuteSecond</span>)) <span class="comment token">// "16:40"</span>
thousandSeconds.<span class="call token">formatted</span>(.<span class="call token">time</span>(pattern: .<span class="call token">minuteSecond</span>(padMinuteToLength: <span class="number token">3</span>, fractionalSecondsLength: <span class="number token">3</span>, roundFractionalSeconds: .<span class="dotAccess token">awayFromZero</span>))) <span class="comment token">// "016:40.000"</span>
</code></pre></div>

<h3 id="unitsformatstyle">UnitsFormatStyle</h3>

<p>You can also use the <code class="language-plaintext highlighter-rouge">UnitsFormatStyle</code> to show the <code class="language-plaintext highlighter-rouge">Duration</code> as a different unit.</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> halfSecond: <span class="type token">Duration</span> = .<span class="call token">milliseconds</span>(<span class="number token">500</span>)
halfSecond.<span class="call token">formatted</span>(.<span class="call token">units</span>()) <span class="comment token">// "0 sec"</span>
halfSecond.<span class="call token">formatted</span>(
    .<span class="call token">units</span>(allowed: [.<span class="dotAccess token">milliseconds</span>])
) <span class="comment token">// "500 ms"</span>
halfSecond.<span class="call token">formatted</span>(
    .<span class="call token">units</span>(
        allowed: [.<span class="dotAccess token">milliseconds</span>],
        width: .<span class="dotAccess token">abbreviated</span>
    )
) <span class="comment token">// "500 ms"</span>
halfSecond.<span class="call token">formatted</span>(
    .<span class="call token">units</span>(
        allowed: [.<span class="dotAccess token">milliseconds</span>],
        width: .<span class="dotAccess token">condensedAbbreviated</span>
    )
) <span class="comment token">// "500ms"</span>
halfSecond.<span class="call token">formatted</span>(
    .<span class="call token">units</span>(
        allowed: [.<span class="dotAccess token">milliseconds</span>],
        width: .<span class="dotAccess token">narrow</span>
    )
)
halfSecond.<span class="call token">formatted</span>(
    .<span class="call token">units</span>(
        allowed: [.<span class="dotAccess token">milliseconds</span>],
        width: .<span class="dotAccess token">wide</span>
    )
) <span class="comment token">// "500 milliseconds"</span>
halfSecond.<span class="call token">formatted</span>(
    .<span class="call token">units</span>(
        allowed: [.<span class="dotAccess token">seconds</span>, .<span class="dotAccess token">milliseconds</span>],
        width: .<span class="dotAccess token">wide</span>,
        maximumUnitCount: <span class="number token">2</span>,
        zeroValueUnits: .<span class="call token">show</span>(length: <span class="number token">2</span>),
        valueLength: <span class="number token">5</span>,
        fractionalPart: .<span class="call token">show</span>(length: <span class="number token">2</span>, rounded: .<span class="dotAccess token">awayFromZero</span>, increment: <span class="number token">0.000025</span>)
    )
) <span class="comment token">// "00,000.000000 seconds, 00,500.000000 milliseconds"</span>
</code></pre></div>

<h2 id="4-url-support">4. URL Support</h2>
<p>There’s a new, and surprisingly deep, format style for URLs that start simple:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> appleURL = <span class="type token">URL</span>(string: <span class="string token">"https://apple.com"</span>)!
appleURL.<span class="call token">formatted</span>() <span class="comment token">// "https://apple.com"</span>
appleURL.<span class="call token">formatted</span>(.<span class="dotAccess token">url</span>) <span class="comment token">// "https://apple.com"</span>
appleURL.<span class="call token">formatted</span>(.<span class="dotAccess token">url</span>.<span class="call token">locale</span>(<span class="type token">Locale</span>(identifier: <span class="string token">"fr_FR"</span>))) <span class="comment token">// "https://apple.com"</span>
</code></pre></div>

<p>And quickly descend into some nice complexity:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">var</span> httpComponents = <span class="type token">URLComponents</span>(url: appleURL, resolvingAgainstBaseURL: <span class="keyword token">false</span>)!
httpComponents.<span class="property token">scheme</span> = <span class="string token">"https"</span>
httpComponents.<span class="property token">user</span> = <span class="string token">"jAppleseed"</span>
httpComponents.<span class="property token">password</span> = <span class="string token">"Test1234"</span>
httpComponents.<span class="property token">host</span> = <span class="string token">"apple.com"</span>
httpComponents.<span class="property token">port</span> = <span class="number token">80</span>
httpComponents.<span class="property token">path</span> = <span class="string token">"/macbook-pro"</span>
httpComponents.<span class="property token">query</span> = <span class="string token">"get-free"</span>
httpComponents.<span class="property token">fragment</span> = <span class="string token">"someFragmentOfSomething"</span>

<span class="keyword token">let</span> complexURL = httpComponents.<span class="property token">url</span>!
<span class="keyword token">let</span> everythingStyle = <span class="type token">URL</span>.<span class="type token">FormatStyle</span>(
    scheme: .<span class="dotAccess token">always</span>,
    user: .<span class="dotAccess token">always</span>,
    password: .<span class="dotAccess token">always</span>,
    host: .<span class="dotAccess token">always</span>,
    port: .<span class="dotAccess token">always</span>,
    path: .<span class="dotAccess token">always</span>,
    query: .<span class="dotAccess token">always</span>,
    fragment: .<span class="dotAccess token">always</span>
)

everythingStyle.<span class="call token">format</span>(complexURL) <span class="comment token">// "https://jAppleseed:Test1234@apple.com:80/macbook-pro?get-free#someFragmentOfSomething"</span>

<span class="keyword token">let</span> omitStyle = <span class="type token">URL</span>.<span class="type token">FormatStyle</span>(
    scheme: .<span class="dotAccess token">omitIfHTTPFamily</span>,
    user: .<span class="dotAccess token">omitIfHTTPFamily</span>,
    password: .<span class="dotAccess token">omitIfHTTPFamily</span>,
    host: .<span class="dotAccess token">omitIfHTTPFamily</span>,
    port: .<span class="dotAccess token">omitIfHTTPFamily</span>,
    path: .<span class="dotAccess token">omitIfHTTPFamily</span>,
    query: .<span class="dotAccess token">omitIfHTTPFamily</span>,
    fragment: .<span class="dotAccess token">omitIfHTTPFamily</span>
)

<span class="keyword token">var</span> httpsComponent = httpComponents
httpsComponent.<span class="property token">scheme</span> = <span class="string token">"https"</span>
<span class="keyword token">let</span> httpsURL = httpsComponent.<span class="property token">url</span>!

<span class="keyword token">var</span> ftpComponents = httpComponents
ftpComponents.<span class="property token">scheme</span> = <span class="string token">"ftp"</span>
<span class="keyword token">let</span> ftpURL = ftpComponents.<span class="property token">url</span>!

omitStyle.<span class="call token">format</span>(complexURL) <span class="comment token">// ""</span>
omitStyle.<span class="call token">format</span>(httpsURL) <span class="comment token">// ""</span>
omitStyle.<span class="call token">format</span>(ftpURL) <span class="comment token">// "ftp://jAppleseed@apple.com:80/macbook-pro?get-free#someFragmentOfSomething"</span>
</code></pre></div>

<h2 id="5-dateverbatimformatstyle-changes">5. Date.VerbatimFormatStyle Changes</h2>

<p>There’s a couple of significant changes to the Verbatim style.</p>

<ol>
  <li>The style can now have the <code class="language-plaintext highlighter-rouge">locale</code> set.</li>
  <li>You can access it using the <code class="language-plaintext highlighter-rouge">.verbatim()</code> extension on <code class="language-plaintext highlighter-rouge">FormatStyle</code></li>
</ol>

<div class="highlight"><pre class="splash"><code><span class="keyword token">let</span> twosday = <span class="type token">Calendar</span>(identifier: .<span class="dotAccess token">gregorian</span>).<span class="call token">date</span>(from: twosdayDateComponents)!

twosday.<span class="call token">formatted</span>(
    .<span class="call token">verbatim</span>(
        <span class="string token">"</span>\(hour: .<span class="call token">defaultDigits</span>(clock: .<span class="dotAccess token">twentyFourHour</span>, hourCycle: .<span class="dotAccess token">oneBased</span>))<span class="string token">:</span>\(minute: .<span class="dotAccess token">defaultDigits</span>)<span class="string token">:</span>\(minute: .<span class="dotAccess token">defaultDigits</span>) \(dayPeriod: .<span class="call token">standard</span>(.<span class="dotAccess token">wide</span>))<span class="string token">"</span>,
        locale: <span class="type token">Locale</span>(identifier: <span class="string token">"zh_CN"</span>),
        timeZone: .<span class="dotAccess token">current</span>,
        calendar: .<span class="dotAccess token">current</span>
    )
) <span class="comment token">// "2:22:22 上午"</span>
</code></pre></div>

<h2 id="conclusion">Conclusion</h2>
<p>Not a bad set of updates. It’s nice to see new units getting new format styles immediately.</p>]]></content><author><name>brett ohland</name></author><category term="ios" /><category term="formatstyle" /><category term="development" /><category term="swift" /><category term="swiftui" /><category term="formatstyle" /><summary type="html"><![CDATA[Guess documenting FormatStyle is my life now]]></summary></entry><entry><title type="html">FormatSyle Deep Dive</title><link href="https://ampersandsoftworks.com/posts/format-style-deep-dive/index.html" rel="alternate" type="text/html" title="FormatSyle Deep Dive" /><published>2022-03-12T09:44:41-07:00</published><updated>2022-03-12T09:44:41-07:00</updated><id>https://ampersandsoftworks.com/posts/format-style-deep-dive/format-style-deep-dive</id><content type="html" xml:base="https://ampersandsoftworks.com/posts/format-style-deep-dive/index.html"><![CDATA[<p>Hello there!</p>

<p>What started as a deep dive into Format Styles in iOS 15 and iOS 16 quickly outgrew a series of posts on a blog. In the grande tradition of <a href="http://fuckingsyntaxsite.com">cuss including titled informational websites who have a singular goal</a>. I present to you:</p>

<h3 id="fuckingformatstylecom"><a href="https://fuckingformatstyle.com">fuckingformatstyle.com</a></h3>

<p>or (for those needing less cussing in their life)</p>

<h3 id="goshdarnformatstylecom"><a href="https://goshdarnformatstyle.com">goshdarnformatstyle.com</a></h3>

<hr />

<p>It’s a single page, and includes much more detail than the previous posts.</p>

<h2 id="theres-a-quiz">There’s a quiz</h2>

<p>In all of my research, the biggest hurdle I ran into was not knowing what I could even do with with these format styles.</p>

<p><a href="https://fuckingformatstyle.com/#how-do-i-even-know-where-to-start">I built a quiz to help you discover exactly what you would want to do.</a></p>

<h2 id="theres-an-faq">There’s an FAQ</h2>

<p><a href="https://fuckingformatstyle.com/#faq">Encompassing the most common questions others have asked, and more.</a></p>

<h2 id="everything-else">Everything Else</h2>

<p>Everything has been organized by their use cases and includes styles available for use in both Xcode 13 and Xcode 14.</p>

<ul>
  <li><a href="https://fuckingformatstyle.com/#minimum-requirements">Minimum Requirements</a></li>
  <li><a href="https://fuckingformatstyle.com/#the-basics">The Basics</a></li>
  <li><a href="https://fuckingformatstyle.com/numeric-styles">Numeric Styles</a>
    <ul>
      <li><a href="https://fuckingformatstyle.com/numeric-styles/#number-style">Number Style</a></li>
      <li><a href="https://fuckingformatstyle.com/numeric-styles/#percent-style">Percent Style</a></li>
      <li><a href="https://fuckingformatstyle.com/numeric-styles/#currency-style">Currency Style</a></li>
    </ul>
  </li>
  <li><a href="https://fuckingformatstyle.com/date-styles/">Single Date Styles</a>
    <ul>
      <li><a href="https://fuckingformatstyle.com/date-styles/#compositing">Compositing</a></li>
      <li><a href="https://fuckingformatstyle.com/date-styles/#date-and-time-single-date">Date and Time Style</a></li>
      <li><a href="https://fuckingformatstyle.com/date-styles/#iso-8601-date-style-single-date">ISO 8601 Style</a></li>
      <li><a href="https://fuckingformatstyle.com/date-styles/#relative-date-style-single-date">Relative Date Style</a></li>
      <li><a href="https://fuckingformatstyle.com/date-styles/#verbatim-date-style-single-date">Verbatim Style</a> (Updated for Xcode 14)</li>
    </ul>
  </li>
  <li><a href="https://fuckingformatstyle.com/date-range-styles/">Date Range Styles</a>
    <ul>
      <li><a href="https://fuckingformatstyle.com/date-range-styles/#interval-date-style-date-range">Interval Style</a></li>
      <li><a href="https://fuckingformatstyle.com/date-range-styles/#components-date-style-date-range">Components Style</a></li>
    </ul>
  </li>
  <li><a href="https://fuckingformatstyle.com/duration-styles/">Duration Styles</a> (New for Xcode 14)
    <ul>
      <li><a href="https://fuckingformatstyle.com/duration-styles/#time-style">Time Style</a> (New for Xcode 14)</li>
      <li><a href="https://fuckingformatstyle.com/duration-styles/#units-style">Units Style</a> (New for Xcode 14)</li>
    </ul>
  </li>
  <li><a href="https://fuckingformatstyle.com/measurement-style/">Measurement Style</a></li>
  <li><a href="https://fuckingformatstyle.com/list-style/">List Style</a></li>
  <li><a href="https://fuckingformatstyle.com/person-name-style/">Person Name Style</a></li>
  <li><a href="https://fuckingformatstyle.com/byte-count-style/">Byte Count Style</a> (Updated for Xcode 14)</li>
  <li><a href="https://fuckingformatstyle.com/url-style/">URL Style</a> (New in Xcode 14)</li>
  <li><a href="https://fuckingformatstyle.com/custom-styles/">Custom FormatStyle</a></li>
  <li><a href="https://fuckingformatstyle.com/swiftui/">SwiftUI Integration</a></li>
  <li><a href="https://fuckingformatstyle.com/attributed-string-output/">AttributedString Output</a></li>
</ul>

<h2 id="downloads">Downloads</h2>

<ul>
  <li><a href="https://github.com/brettohland/FormatStylesDeepDive">Xcode Playground</a></li>
  <li><a href="https://gist.github.com/brettohland/ac2fbd1446bc7bb64da491587b010e3c">Everything as a Gist</a></li>
</ul>]]></content><author><name>brett ohland</name></author><category term="ios" /><category term="formatstyle" /><category term="development" /><category term="swift" /><category term="swiftui" /><category term="formatstyle" /><summary type="html"><![CDATA[Apple failed to document it, so I built a whole site for it: fuckingformatstyle.com]]></summary></entry><entry><title type="html">UIFont Fun</title><link href="https://ampersandsoftworks.com/posts/UIFont-fun/index.html" rel="alternate" type="text/html" title="UIFont Fun" /><published>2021-11-24T10:18:52-07:00</published><updated>2021-11-24T10:18:52-07:00</updated><id>https://ampersandsoftworks.com/posts/UIFont-fun/UIFont-fun</id><content type="html" xml:base="https://ampersandsoftworks.com/posts/UIFont-fun/index.html"><![CDATA[<p>In your career, do you have a technical problem that you know you’ve solved multiple times, but it’s infrequent enough that the lessons of how to actually implement never seem to stick around in your brain?</p>

<p>For me, it’s custom fonts in your iOS apps.</p>

<p>In summary, to add a custom font to your app you need to:</p>

<ol>
  <li>Add the font file to your project and make sure that it’s included in your build target.</li>
  <li>Register your font file in the <code class="language-plaintext highlighter-rouge">Info.plist</code> in an array under the <code class="language-plaintext highlighter-rouge">UIAppFonts</code> key.</li>
  <li>Use the font by referencing it’s name.</li>
</ol>

<p>Unsurprisingly, it’s #3 that always seems to mess me up.</p>

<p>In this example, I was using the font <code class="language-plaintext highlighter-rouge">Inter</code> from Google Fonts, and I added the <code class="language-plaintext highlighter-rouge">light</code> variant to the project.</p>

<p>Now, the project already includes the <code class="language-plaintext highlighter-rouge">Inter-Medium</code> and <code class="language-plaintext highlighter-rouge">Inter-Regular</code>, and their “real” names are (unsurprisingly) <code class="language-plaintext highlighter-rouge">Inter-Regular</code> and <code class="language-plaintext highlighter-rouge">Inter-Medium</code>.</p>

<p>The code that references this is exceptionally simple:</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">public enum</span> CustomFontVariant {
  <span class="keyword token">case</span> regular
  <span class="keyword token">case</span> medium
}

<span class="keyword token">extension</span> <span class="type token">UIFont</span> {
  <span class="keyword token">static func</span> customFont(
    variant: <span class="type token">CustomFontVariant</span>, 
    size: <span class="type token">CGFloat</span>
  ) -&gt; <span class="type token">UIFont</span>? {
    <span class="keyword token">switch</span> variant {
      <span class="keyword token">let</span> fontName: <span class="type token">String</span>
      case: .<span class="dotAccess token">regular</span>:
        fontName = <span class="string token">"Inter-Regular"</span>
      case: .<span class="dotAccess token">medium</span>:
        fontName = <span class="string token">"Inter-Medium"</span>
    }
    <span class="keyword token">return</span> <span class="type token">UIFont</span>(name: fontName, size: size)
  }
}
</code></pre></div>

<p>Adding the <code class="language-plaintext highlighter-rouge">light</code> variant was simple (I thought)</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">public enum</span> CustomFontVariant {
  <span class="keyword token">case</span> light
  <span class="keyword token">case</span> regular
  <span class="keyword token">case</span> medium
}

<span class="keyword token">extension</span> <span class="type token">UIFont</span> {
  <span class="keyword token">static func</span> customFont(
    variant: <span class="type token">CustomFontVariant</span>, 
    size: <span class="type token">CGFloat</span>
  ) -&gt; <span class="type token">UIFont</span>? {
    <span class="keyword token">switch</span> variant {
      <span class="keyword token">let</span> fontName: <span class="type token">String</span>
      case: .<span class="dotAccess token">light</span>:
        fontName = <span class="string token">"Inter-Light"</span>
      case: .<span class="dotAccess token">regular</span>:
        fontName = <span class="string token">"Inter-Regular"</span>
      case: .<span class="dotAccess token">medium</span>:
        fontName = <span class="string token">"Inter-Medium"</span>
    }
    <span class="keyword token">return</span> <span class="type token">UIFont</span>(name: fontName, size: size)
  }
}
</code></pre></div>

<p>The issue was, that the <code class="language-plaintext highlighter-rouge">UIFont</code> being returned by the method was always <code class="language-plaintext highlighter-rouge">nil</code>.</p>

<p>The issue, after an embarrassingly long amount of debugging, was the fact that while the font file was named <code class="language-plaintext highlighter-rouge">Inter-Light</code> the “real name” of the font was actually <code class="language-plaintext highlighter-rouge">Inter-Regular_light</code>…</p>

<p>And how do you discover this “real name”? Well you follow <a href="https://developer.apple.com/documentation/uikit/text_display_and_fonts/adding_a_custom_font_to_your_app"><em>the code that Apple includes in the documentation</em></a>.</p>

<div class="highlight"><pre class="splash"><code><span class="keyword token">for</span> family <span class="keyword token">in</span> <span class="type token">UIFont</span>.<span class="property token">familyNames</span>.<span class="call token">sorted</span>() {
    <span class="keyword token">let</span> names = <span class="type token">UIFont</span>.<span class="call token">fontNames</span>(forFamilyName: family)
    <span class="call token">print</span>(<span class="string token">"Family:</span> \(family) <span class="string token">Font names:</span> \(names)<span class="string token">"</span>)
}
</code></pre></div>

<p>Just more proof that even though you’ve been developing mobile apps since iOS 5, you still should check the docs.</p>]]></content><author><name>brett ohland</name></author><category term="development" /><category term="swift" /><category term="swiftui" /><summary type="html"><![CDATA[In your career, do you have a technical problem that you know you've solved multiple times, but it's infrequent enough that the lessons of how to actually implement never seem to stick around in your brain?]]></summary></entry></feed>