<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://otbivnoe.ru/feed.xml" rel="self" type="application/atom+xml" /><link href="https://otbivnoe.ru/" rel="alternate" type="text/html" /><updated>2025-09-15T09:43:00+03:00</updated><id>https://otbivnoe.ru/feed.xml</id><title type="html">otbivnoe’s blog</title><subtitle>iOS evangelist | lover of music, bikes and photography | ex-Yandex</subtitle><author><name>Nikita Ermolenko</name></author><entry><title type="html">Alerts in iOS: Simple but Tricky</title><link href="https://otbivnoe.ru/2025/04/17/Alerts-Simple-but-Tricky.html" rel="alternate" type="text/html" title="Alerts in iOS: Simple but Tricky" /><published>2025-04-17T16:00:00+03:00</published><updated>2025-04-17T16:00:00+03:00</updated><id>https://otbivnoe.ru/2025/04/17/Alerts-Simple-but-Tricky</id><content type="html" xml:base="https://otbivnoe.ru/2025/04/17/Alerts-Simple-but-Tricky.html"><![CDATA[<p>Alerts are a standard UI element in iOS apps, used to inform users or request decisions. Though they seem simple, alerts involve many subtle guidelines around button order, roles, and colors. Mistakes here can confuse users. In this article, we’ll break down the correct use of alerts, highlight common pitfalls, and focus on Apple’s Human Interface Guidelines (HIG) to help you design clear and safe interfaces.</p>

<p><br /></p>

<p>This article is relevant for iOS 18 and uses the latest SwiftUI API for <a href="https://developer.apple.com/documentation/swiftui/view/alert(_:ispresented:presenting:actions:)-4rhk6">alerts</a>. If you are supporting older versions or using UIKit, some details may differ, but the general principles remain the same.</p>

<hr />

<h3 id="alert-vs-actionsheet">Alert vs ActionSheet</h3>
<p>Before we dive into alerts, let’s talk about <code class="language-plaintext highlighter-rouge">ActionSheet</code> and where to use it instead of <code class="language-plaintext highlighter-rouge">Alert</code>. An action sheet is a UI element that presents a set of choices related to an action the user has started. According to the Human Interface Guidelines (<a href="https://developer.apple.com/design/human-interface-guidelines/components/feedback/alerts/">HIG</a>):</p>

<blockquote>
  <p>Use an action sheet — not an alert — to offer choices related to an intentional action. For example, when people cancel the Mail message they’re editing, an action sheet provides three choices: delete the edits (or the entire draft), save the draft, or return to editing. Although an alert can also help people confirm or cancel an action that has destructive consequences, it doesn’t provide additional choices related to the action. More importantly, an alert is usually unexpected, generally telling people about a problem or a change in the current situation that might require them to act.</p>
</blockquote>

<p>It seems obvious, <strong>but even Apple is not always consistent</strong>. For example, when you delete a note in the Notes app, you see an action sheet, but when you delete a reminder in the Reminders app, you see an alert. Why?</p>

<p><img class="centered post-img" srcset="/assets/img/articles/alert/action-sheet-vs-alert.png" alt="" /></p>

<p>According to HIG, in this case, an <strong>alert would be more appropriate</strong>, because the user is being warned about a destructive action that cannot be undone. This highlights the importance of understanding the intent behind each component and choosing the right one for your scenario.</p>

<hr />

<h3 id="diving-into-alert">Diving into Alert</h3>
<p>Since we decided that an alert is the right choice in the previous example, let’s see how to use it correctly and what options we have. We will not talk about the title and message, as they are straightforward. Instead, let’s focus on the buttons, their placement, and style.</p>

<p>In the example above, the destructive button “Delete” is on the right, and the cancel button is on the left. This is the current HIG recommendation:</p>

<blockquote>
  <p>Place buttons where people expect. In general, place the button people are most likely to choose on the trailing side in a row of buttons. Always place the default button on the trailing side of a row. Cancel buttons are typically on the leading side of a row.</p>
</blockquote>

<p>You don’t need to worry about the button order — just assign the correct role (such as <code class="language-plaintext highlighter-rouge">.cancel</code> or <code class="language-plaintext highlighter-rouge">.destructive</code>) to each button, and SwiftUI will automatically arrange them according to the latest HIG. This not only simplifies your code, but also guarantees a consistent and native experience for your users.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="nf">alert</span><span class="p">(</span><span class="s">"Delete Reminder"</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">Button</span><span class="p">(</span><span class="s">"Cancel"</span><span class="p">,</span> <span class="nv">role</span><span class="p">:</span> <span class="o">.</span><span class="n">cancel</span><span class="p">)</span> <span class="p">{}</span>
    <span class="kt">Button</span><span class="p">(</span><span class="s">"Delete"</span><span class="p">,</span> <span class="nv">role</span><span class="p">:</span> <span class="o">.</span><span class="n">destructive</span><span class="p">)</span> <span class="p">{}</span>
<span class="p">}</span> <span class="nv">message</span><span class="p">:</span> <span class="p">{</span>
    <span class="kt">Text</span><span class="p">(</span><span class="s">"This action can't be undone."</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Even better, you do not need to add the cancel button explicitly. The system will add it for you, making your code even cleaner:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="nf">alert</span><span class="p">(</span><span class="s">"Delete Reminder"</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">Button</span><span class="p">(</span><span class="s">"Delete"</span><span class="p">,</span> <span class="nv">role</span><span class="p">:</span> <span class="o">.</span><span class="n">destructive</span><span class="p">)</span> <span class="p">{}</span>
<span class="p">}</span> <span class="nv">message</span><span class="p">:</span> <span class="p">{</span>
    <span class="kt">Text</span><span class="p">(</span><span class="s">"This action can't be undone."</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>But it was not always like this. In the past, HIG said the opposite:</p>

<blockquote>
  <p>In a two-button alert that proposes a potentially risky action, the button that cancels the action <strong>should be on the right</strong> and light-colored. In a two-button alert that proposes a benign action that people are likely to want, the button that cancels the action should be on the left and dark-colored.</p>
</blockquote>

<p>I think this is why you can still find alerts in iOS where the button order is different.</p>

<p><img class="centered post-img" srcset="/assets/img/articles/alert/delete-left-side.png" alt="" /></p>

<p>Apple made it easier for developers, and forgot about themself.</p>

<hr />

<h3 id="should-the-destructive-button-always-be-red">Should the Destructive Button Always Be Red?</h3>

<blockquote>
  <p>Use the destructive style to identify a button that performs a destructive action people didn’t deliberately choose. For example, when people deliberately choose a destructive action — such as Empty Trash — the resulting alert doesn’t apply the destructive style to the Empty Trash button because the button performs the person’s original intent. In this scenario, the convenience of pressing Return to confirm the deliberately chosen Empty Trash action outweighs the benefit of reaffirming that the button is destructive. In contrast, people appreciate an alert that draws their attention to a button that can perform a destructive action they didn’t originally intend.</p>
</blockquote>

<p><strong>In simple words:</strong> If the user already chose a destructive action (for example, pressed a red “Sign Out” button in a list), the confirmation button in the alert does not need to be red again. It can be blue, because the user already made a conscious choice.</p>

<p><img class="centered post-img" srcset="/assets/img/articles/alert/alert-red-twice.png" alt="" /></p>

<p>But if the alert is warning about a destructive action that <strong>the user did not choose directly</strong>, the destructive button <strong>should be red to draw attention</strong>. This subtle distinction helps prevent accidental data loss while keeping the interface clean.</p>

<p>If I understand HIG correctly, in these scenarios the button should be blue. Always consider the user’s intent and the context of the action when choosing button styles.</p>

<hr />

<h2 id="the-cancel-button-is-bold">The Cancel Button is Bold?</h2>
<p>You may have noticed that the “Cancel” button above is bold. Apple made this change in iOS 8.3. HIG recommends making the “Cancel” button the bold button in an alert with a destructive action. This makes the safe option more visible and reduces the risk of accidentally choosing the destructive action.</p>

<p>However, it’s worth noting that even in Apple’s own examples (<em>again</em>), the Cancel button is not always bolded. This inconsistency can be confusing, and it shows that even Apple sometimes deviates from its own guidelines.</p>

<p><img class="centered post-img" srcset="/assets/img/articles/alert/cancel-not-bold.png" alt="" /></p>

<p>Sometimes (I hope never), you may want to do the opposite — not just remove the bold emphasis from “Cancel”, but actually make the destructive button stand out even more. For example, if you want to draw extra attention to a destructive action, you can use <code class="language-plaintext highlighter-rouge">.keyboardShortcut(.defaultAction)</code> on the destructive button.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">Button</span><span class="p">(</span><span class="s">"Delete Reminder"</span><span class="p">,</span> <span class="nv">role</span><span class="p">:</span> <span class="o">.</span><span class="n">destructive</span><span class="p">)</span> <span class="p">{}</span>
    <span class="o">.</span><span class="nf">keyboardShortcut</span><span class="p">(</span><span class="o">.</span><span class="n">defaultAction</span><span class="p">)</span>

<span class="kt">Button</span><span class="p">(</span><span class="s">"Cancel"</span><span class="p">,</span> <span class="nv">role</span><span class="p">:</span> <span class="o">.</span><span class="n">cancel</span><span class="p">)</span> <span class="p">{}</span>
</code></pre></div></div>

<p>This will make the destructive button bold, increasing its prominence in the alert. Cancel button will not be bold anymore:</p>

<p><img class="centered post-img" srcset="/assets/img/articles/alert/delete-bold.png" alt="" /></p>

<p>‼️ Use this carefully, as making the destructive action more noticeable can increase the risk of accidental data loss.</p>

<hr />

<h2 id="but-what-does-default-action-mean">But What Does Default Action Mean?</h2>
<p>As the name suggests, you can trigger the action using a keyboard shortcut. <code class="language-plaintext highlighter-rouge">defaultAction</code> means this is the default button in your OS. Since SwiftUI works on different platforms, the system will decide what this means. On iOS, keyboard shortcuts are not very useful, unless you use an iPad with a keyboard. It can also be helpful for developers to use the keyboard in the simulator.</p>

<p>By default, the system treats destructive buttons as default actions in alerts with two buttons, so you usually don’t need to add <code class="language-plaintext highlighter-rouge">.keyboardShortcut(.defaultAction)</code> explicitly:</p>

<p>However, in alerts with three or more buttons (<strong>one of which is destructive</strong>), no button is set as the default. In such cases, you can assign <code class="language-plaintext highlighter-rouge">.keyboardShortcut(.defaultAction)</code>, this approach encourages users to pay closer attention before making a choice.</p>

<blockquote>
  <p>If you want to encourage people to read an alert and not just automatically press ↩ to dismiss it, avoid making any button the default button.</p>
</blockquote>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">Button</span><span class="p">(</span><span class="s">"Delete"</span><span class="p">,</span> <span class="nv">role</span><span class="p">:</span> <span class="o">.</span><span class="n">destructive</span><span class="p">)</span> <span class="p">{}</span>
    <span class="o">.</span><span class="nf">keyboardShortcut</span><span class="p">(</span><span class="o">.</span><span class="n">defaultAction</span><span class="p">)</span>
<span class="kt">Button</span><span class="p">(</span><span class="s">"Cancel"</span><span class="p">,</span> <span class="nv">role</span><span class="p">:</span> <span class="o">.</span><span class="n">cancel</span><span class="p">)</span> <span class="p">{}</span>
<span class="kt">Button</span><span class="p">(</span><span class="s">"Info"</span><span class="p">)</span> <span class="p">{}</span>
</code></pre></div></div>

<p>In alerts with a single button — regardless of its role — pressing the default key (↩) will dismiss the alert and trigger that button’s action:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">alert</span> <span class="p">{</span>
    <span class="kt">Button</span><span class="p">(</span><span class="s">"OK"</span><span class="p">)</span> <span class="p">{}</span> <span class="c1">// default action</span>
<span class="p">}</span>

<span class="n">alert</span> <span class="p">{</span>
    <span class="kt">Button</span><span class="p">(</span><span class="s">"Delete"</span><span class="p">,</span> <span class="nv">role</span><span class="p">:</span> <span class="o">.</span><span class="n">destructive</span><span class="p">)</span> <span class="p">{}</span> <span class="c1">// default action</span>
<span class="p">}</span>

<span class="n">alert</span> <span class="p">{</span>
    <span class="kt">Button</span><span class="p">(</span><span class="s">"Cancel"</span><span class="p">,</span> <span class="nv">role</span><span class="p">:</span> <span class="o">.</span><span class="n">cancel</span><span class="p">)</span> <span class="p">{}</span> <span class="c1">// default action</span>
<span class="p">}</span>

</code></pre></div></div>

<p>In the context of alert example, this modifier also makes the button text bold. This visual cue helps users quickly identify the primary action, but use it thoughtfully to avoid confusion.</p>

<hr />

<h3 id="conclusion">Conclusion</h3>
<ul>
  <li>Always use the correct component: Alert for unexpected warnings, ActionSheet for intentional choices.</li>
  <li>Assign proper roles to buttons (<code class="language-plaintext highlighter-rouge">.cancel</code>, <code class="language-plaintext highlighter-rouge">.destructive</code>) and let the system handle their order and style.</li>
  <li>Destructive buttons do not always need to be red — consider the user’s intent.</li>
  <li>Follow the Human Interface Guidelines, but remember that even Apple sometimes makes exceptions — strive for clarity and user safety in your own apps.</li>
</ul>

<p>By paying attention to these details, you can create alerts that are not only functional but also intuitive and safe for your users. Thoughtful design and thorough testing will help your app stand out and provide a better experience for everyone.</p>

<hr />

<h3 id="references">References</h3>
<ul>
  <li><a href="https://developer.apple.com/design/human-interface-guidelines/components/feedback/alerts/">Apple Human Interface Guidelines: Alerts</a></li>
  <li><a href="https://developer.apple.com/documentation/swiftui/view/alert(_:ispresented:actions:message:)">SwiftUI .alert documentation</a></li>
</ul>]]></content><author><name>Nikita Ermolenko</name></author><category term="SwiftUI" /><summary type="html"><![CDATA[Alerts are a standard UI element in iOS apps, used to inform users or request decisions. Though they seem simple, alerts involve many subtle guidelines around button order, roles, and colors. Mistakes here can confuse users. In this article, we’ll break down the correct use of alerts, highlight common pitfalls, and focus on Apple’s Human Interface Guidelines (HIG) to help you design clear and safe interfaces.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://otbivnoe.ru/assets/img/articles/alert/logo.png" /><media:content medium="image" url="https://otbivnoe.ru/assets/img/articles/alert/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Making Customizable SwiftUI Components</title><link href="https://otbivnoe.ru/2024/02/25/Making-Customizable-SwiftUI-Components.html" rel="alternate" type="text/html" title="Making Customizable SwiftUI Components" /><published>2024-02-25T06:00:00+03:00</published><updated>2024-02-25T06:00:00+03:00</updated><id>https://otbivnoe.ru/2024/02/25/Making-Customizable-SwiftUI-Components</id><content type="html" xml:base="https://otbivnoe.ru/2024/02/25/Making-Customizable-SwiftUI-Components.html"><![CDATA[<p>In this article, we’re going to explore different ways of creating customizable components in SwifUI.</p>

<p>Many of us have encountered loader indicators in our work, making it a familiar starting point. We’ll begin with the most basic component and progressively enhance its flexibility.</p>

<p>Imagine a basic circle that fills up as the progress increases. This is our starting point:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">LoaderView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>

    <span class="k">let</span> <span class="nv">progress</span><span class="p">:</span> <span class="kt">Double</span>
    <span class="kd">@State</span> <span class="kd">private</span> <span class="k">var</span> <span class="nv">degree</span><span class="p">:</span> <span class="kt">Double</span> <span class="o">=</span> <span class="mi">0</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">Circle</span><span class="p">()</span>
            <span class="o">.</span><span class="nf">trim</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span> <span class="nv">to</span><span class="p">:</span> <span class="n">progress</span><span class="p">)</span>
            <span class="o">.</span><span class="nf">stroke</span><span class="p">(</span><span class="kt">Color</span><span class="o">.</span><span class="n">red</span><span class="p">,</span> <span class="nv">style</span><span class="p">:</span> <span class="kt">StrokeStyle</span><span class="p">(</span><span class="nv">lineWidth</span><span class="p">:</span> <span class="mf">8.0</span><span class="p">,</span> <span class="nv">lineCap</span><span class="p">:</span> <span class="o">.</span><span class="n">round</span><span class="p">))</span>
            <span class="o">.</span><span class="nf">rotationEffect</span><span class="p">(</span><span class="o">.</span><span class="nf">degrees</span><span class="p">(</span><span class="n">degree</span><span class="p">))</span>
            <span class="o">.</span><span class="nf">animation</span><span class="p">(</span><span class="kt">Animation</span><span class="o">.</span><span class="nf">linear</span><span class="p">(</span><span class="nv">duration</span><span class="p">:</span> <span class="mi">1</span><span class="p">)</span><span class="o">.</span><span class="nf">repeatForever</span><span class="p">(</span><span class="nv">autoreverses</span><span class="p">:</span> <span class="kc">false</span><span class="p">),</span> <span class="nv">value</span><span class="p">:</span> <span class="n">degree</span><span class="p">)</span>
            <span class="o">.</span><span class="n">onAppear</span> <span class="p">{</span>
                <span class="n">degree</span> <span class="o">=</span> <span class="mi">360</span>
            <span class="p">}</span>
            <span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">80</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><em>It spins and spins all the time, nothing more.</em></p>

<p><img class="centered post-img" srcset="/assets/img/articles/loader-styles/loader.gif" alt="" /></p>

<hr />

<h3 id="progress-color">Progress color</h3>

<p>Let’s start the improvements by making the progress color adjustable.  Initially, you might consider passing a tint color directly into the initializer:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">LoaderView</span><span class="p">(</span><span class="nv">progress</span><span class="p">:</span> <span class="mf">0.6</span><span class="p">,</span> <span class="nv">tintColor</span><span class="p">:</span> <span class="o">.</span><span class="n">purple</span><span class="p">)</span>
</code></pre></div></div>

<p>However, having numerous configuration parameters can get messy eventually, so I’d suggest to use a more SwiftUI approach - using the <code class="language-plaintext highlighter-rouge">tint</code> modifier:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">LoaderView</span><span class="p">(</span><span class="nv">progress</span><span class="p">:</span> <span class="mf">0.6</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">purple</span><span class="p">)</span>
</code></pre></div></div>

<p>If you are used to using environments, you may want to consider using <code class="language-plaintext highlighter-rouge">@Environment(\.tint)</code>.<br />
But the problem it doesn’t exist (technically it’s internal), so we can’t access it directly.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">LoaderView_Env</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
    <span class="c1">// Error: 'tint' is inaccessible due to 'internal' protection level</span>
    <span class="kd">@Environment</span><span class="p">(\</span><span class="o">.</span><span class="n">tint</span><span class="p">)</span> <span class="k">var</span> <span class="nv">tint</span>
<span class="p">}</span>
</code></pre></div></div>

<p>By looking at the <code class="language-plaintext highlighter-rouge">stroke</code> signature we can see it accepts <a href="https://developer.apple.com/documentation/swiftui/shapestyle">ShapeStyle</a> protocol as a first argument, not a specific color as we might though:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="n">stroke</span><span class="o">&lt;</span><span class="kt">S</span><span class="o">&gt;</span><span class="p">(</span><span class="n">_</span> <span class="nv">content</span><span class="p">:</span> <span class="kt">S</span><span class="p">,</span> <span class="nv">style</span><span class="p">:</span> <span class="kt">StrokeStyle</span><span class="p">,</span> <span class="nv">antialiased</span><span class="p">:</span> <span class="kt">Bool</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="o">...</span> <span class="k">where</span> <span class="kt">S</span> <span class="p">:</span> <span class="kt">ShapeStyle</span>
</code></pre></div></div>

<p>The nice thing for us is that SwiftUI provides <a href="https://developer.apple.com/documentation/swiftui/tintshapestyle">TintShapeStyle</a> that conforms that protocol and has an underlying access to the passed tint color.
Thus, we don’t need an environment at all, just pass <code class="language-plaintext highlighter-rouge">.tint</code> as an argument:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="nf">stroke</span><span class="p">(</span><span class="o">.</span><span class="n">tint</span><span class="p">,</span> <span class="nv">style</span><span class="p">:</span> <span class="kt">StrokeStyle</span><span class="p">(</span><span class="nv">lineWidth</span><span class="p">:</span> <span class="mf">8.0</span><span class="p">,</span> <span class="nv">lineCap</span><span class="p">:</span> <span class="o">.</span><span class="n">round</span><span class="p">))</span>
</code></pre></div></div>

<p><strong>NOTE:</strong> the similar approach can be achieved for <code class="language-plaintext highlighter-rouge">.foreground</code> by using <code class="language-plaintext highlighter-rouge">ForegroundStyle</code>.</p>

<hr />

<h3 id="loader-size">Loader size</h3>

<p>Now the color is configured, let’s make the loader view flexible in size.</p>

<p>With the current implementation it has a fixed size 80x80. To adjust this, we set the ideal size - similar to <code class="language-plaintext highlighter-rouge">intrinsicContentSize</code> in UIKit:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">Circle</span><span class="p">()</span>
    <span class="o">...</span>
    <span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">idealWidth</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span> <span class="nv">idealHeight</span><span class="p">:</span> <span class="mi">80</span><span class="p">)</span>
</code></pre></div></div>

<p>By default our loader grows as long as a parent allows, but if we want to have a default size - we apply <code class="language-plaintext highlighter-rouge">fixedSize</code> modifier:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">ContentView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="kt">HStack</span><span class="p">(</span><span class="nv">spacing</span><span class="p">:</span> <span class="mi">20</span><span class="p">)</span> <span class="p">{</span>
            <span class="kt">LoaderView</span><span class="p">(</span><span class="nv">progress</span><span class="p">:</span> <span class="mf">0.6</span><span class="p">)</span>
                <span class="o">.</span><span class="nf">fixedSize</span><span class="p">()</span>

            <span class="kt">LoaderView</span><span class="p">(</span><span class="nv">progress</span><span class="p">:</span> <span class="mf">0.6</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><img class="centered post-img" srcset="/assets/img/articles/loader-styles/fixedsize.png" alt="" /></p>

<p>You can experiment with the <code class="language-plaintext highlighter-rouge">Color</code> the same way - it expands as much as possible and the <code class="language-plaintext highlighter-rouge">fixedSize</code> makes tiny.</p>

<p><em>If you’re not familiar with these modifiers, I’d recommend to read this article about <a href="https://betterprogramming.pub/swiftui-layout-the-mystery-of-size-b82ce99e61d8">SwiftUI Layout</a>.</em></p>

<p>So far so good:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">LoaderView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>

    <span class="k">let</span> <span class="nv">progress</span><span class="p">:</span> <span class="kt">Double</span>
    <span class="kd">@State</span> <span class="kd">private</span> <span class="k">var</span> <span class="nv">degree</span><span class="p">:</span> <span class="kt">Double</span> <span class="o">=</span> <span class="mi">0</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">Circle</span><span class="p">()</span>
            <span class="o">.</span><span class="nf">trim</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span> <span class="nv">to</span><span class="p">:</span> <span class="n">progress</span><span class="p">)</span>
            <span class="o">.</span><span class="nf">stroke</span><span class="p">(</span><span class="o">.</span><span class="n">tint</span><span class="p">,</span> <span class="nv">style</span><span class="p">:</span> <span class="kt">StrokeStyle</span><span class="p">(</span><span class="nv">lineWidth</span><span class="p">:</span> <span class="mf">8.0</span><span class="p">,</span> <span class="nv">lineCap</span><span class="p">:</span> <span class="o">.</span><span class="n">round</span><span class="p">))</span>
            <span class="o">.</span><span class="nf">rotationEffect</span><span class="p">(</span><span class="o">.</span><span class="nf">degrees</span><span class="p">(</span><span class="n">degree</span><span class="p">))</span>
            <span class="o">.</span><span class="nf">animation</span><span class="p">(</span><span class="kt">Animation</span><span class="o">.</span><span class="nf">linear</span><span class="p">(</span><span class="nv">duration</span><span class="p">:</span> <span class="mi">1</span><span class="p">)</span><span class="o">.</span><span class="nf">repeatForever</span><span class="p">(</span><span class="nv">autoreverses</span><span class="p">:</span> <span class="kc">false</span><span class="p">),</span> <span class="nv">value</span><span class="p">:</span> <span class="n">degree</span><span class="p">)</span>
            <span class="o">.</span><span class="n">onAppear</span> <span class="p">{</span>
                <span class="n">degree</span> <span class="o">=</span> <span class="mi">360</span>
            <span class="p">}</span>
            <span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">idealWidth</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span> <span class="nv">idealHeight</span><span class="p">:</span> <span class="mi">80</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h3 id="styling">Styling</h3>

<p>It’s time to add different styling options - for simplicity, we’ll consider two variants:</p>
<ul>
  <li><em>Primary</em> (80x80 size, purple progress color, and progress value inside)</li>
  <li><em>Secondary</em> (40x40, pink, without any progress text)</li>
</ul>

<p>In a simple scenario you’d just pass a <code class="language-plaintext highlighter-rouge">style</code> directly to an initializer:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">LoaderView</span><span class="p">(</span><span class="nv">style</span><span class="p">:</span> <span class="o">.</span><span class="n">primary</span><span class="p">)</span>
</code></pre></div></div>

<p>and applied different configurations for each style by having many <code class="language-plaintext highlighter-rouge">switch</code> and <code class="language-plaintext highlighter-rouge">if</code> conditions. Probably in most cases it’s not a big deal and doesn’t look messy, but sometimes it complicates the code a lot that it’s better to use the approach SwiftUI uses <a href="https://developer.apple.com/documentation/swiftui/view-styles">a lot</a> (style protocol).</p>

<p>First step is creating a <code class="language-plaintext highlighter-rouge">LoaderStyle</code> and <code class="language-plaintext highlighter-rouge">LoaderStyleConfiguration</code>. Different styles should implement this protocol and use the provided values by configuration:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">protocol</span> <span class="kt">LoaderStyle</span><span class="p">:</span> <span class="kt">DynamicProperty</span> <span class="p">{</span>
    <span class="kd">typealias</span> <span class="kt">Configuration</span> <span class="o">=</span> <span class="kt">LoaderStyleConfiguration</span>
    <span class="kd">associatedtype</span> <span class="kt">Body</span><span class="p">:</span> <span class="kt">View</span>

    <span class="kd">@ViewBuilder</span> <span class="kd">func</span> <span class="nf">makeBody</span><span class="p">(</span><span class="nv">configuration</span><span class="p">:</span> <span class="kt">Configuration</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Body</span>
<span class="p">}</span>
</code></pre></div></div>

<p>It’s up to you how many information your configuration’s needed. For simplicity the progress and the loader bar will be enough for us:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">LoaderStyleConfiguration</span> <span class="p">{</span>
    <span class="kd">struct</span> <span class="kt">Indicator</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="kd">public</span> <span class="k">let</span> <span class="nv">body</span><span class="p">:</span> <span class="kt">AnyView</span>
    <span class="p">}</span>

    <span class="k">let</span> <span class="nv">progress</span><span class="p">:</span> <span class="kt">Double</span>
    <span class="k">let</span> <span class="nv">loadingIndicator</span><span class="p">:</span> <span class="kt">Indicator</span>

    <span class="nf">init</span><span class="p">(</span><span class="nv">progress</span><span class="p">:</span> <span class="kt">Double</span><span class="p">,</span> <span class="kd">@ViewBuilder</span> <span class="nv">loadingIndicator</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="kd">some</span> <span class="kt">View</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="n">progress</span> <span class="o">=</span> <span class="n">progress</span>
        <span class="k">self</span><span class="o">.</span><span class="n">loadingIndicator</span> <span class="o">=</span> <span class="kt">Indicator</span><span class="p">(</span><span class="nv">body</span><span class="p">:</span> <span class="kt">AnyView</span><span class="p">(</span><span class="nf">loadingIndicator</span><span class="p">()))</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Then we need to define a custom <code class="language-plaintext highlighter-rouge">EnvironmentKey</code> with the default <strong>primary</strong> option for passing a needed style to our component:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">LoaderStyleEnvironmentKey</span><span class="p">:</span> <span class="kt">EnvironmentKey</span> <span class="p">{</span>
    <span class="kd">static</span> <span class="k">var</span> <span class="nv">defaultValue</span><span class="p">:</span> <span class="n">any</span> <span class="kt">LoaderStyle</span> <span class="o">=</span> <span class="kt">PrimaryLoaderStyle</span><span class="p">()</span>
<span class="p">}</span>

<span class="kd">extension</span> <span class="kt">EnvironmentValues</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">loaderStyle</span><span class="p">:</span> <span class="n">any</span> <span class="kt">LoaderStyle</span> <span class="p">{</span>
        <span class="k">get</span> <span class="p">{</span> <span class="k">self</span><span class="p">[</span><span class="kt">LoaderStyleEnvironmentKey</span><span class="o">.</span><span class="k">self</span><span class="p">]</span> <span class="p">}</span>
        <span class="k">set</span> <span class="p">{</span> <span class="k">self</span><span class="p">[</span><span class="kt">LoaderStyleEnvironmentKey</span><span class="o">.</span><span class="k">self</span><span class="p">]</span> <span class="o">=</span> <span class="n">newValue</span> <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">extension</span> <span class="kt">View</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">loaderStyle</span><span class="p">(</span><span class="n">_</span> <span class="nv">style</span><span class="p">:</span> <span class="n">any</span> <span class="kt">LoaderStyle</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="nf">environment</span><span class="p">(\</span><span class="o">.</span><span class="n">loaderStyle</span><span class="p">,</span> <span class="n">style</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Finally, two implementations of our styles:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">PrimaryLoaderStyle</span><span class="p">:</span> <span class="kt">LoaderStyle</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">makeBody</span><span class="p">(</span><span class="nv">configuration</span><span class="p">:</span> <span class="kt">Configuration</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="n">configuration</span><span class="o">.</span><span class="n">loadingIndicator</span>
            <span class="o">.</span><span class="nf">tint</span><span class="p">(</span><span class="kt">Color</span><span class="o">.</span><span class="n">purple</span><span class="p">)</span>                        
            <span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">80</span><span class="p">)</span>
            <span class="o">.</span><span class="n">overlay</span> <span class="p">{</span>
                <span class="kt">Text</span><span class="p">(</span><span class="kt">String</span><span class="p">(</span><span class="kt">Int</span><span class="p">(</span><span class="n">configuration</span><span class="o">.</span><span class="n">progress</span> <span class="o">*</span> <span class="mi">100</span><span class="p">))</span> <span class="o">+</span> <span class="s">"%"</span><span class="p">)</span>
                    <span class="o">.</span><span class="nf">font</span><span class="p">(</span><span class="o">.</span><span class="n">subheadline</span><span class="p">)</span>
                    <span class="o">.</span><span class="nf">foregroundStyle</span><span class="p">(</span><span class="kt">Color</span><span class="o">.</span><span class="n">secondary</span><span class="p">)</span>
            <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">struct</span> <span class="kt">SecondaryLoaderStyle</span><span class="p">:</span> <span class="kt">LoaderStyle</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">makeBody</span><span class="p">(</span><span class="nv">configuration</span><span class="p">:</span> <span class="kt">Configuration</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="n">configuration</span><span class="o">.</span><span class="n">loadingIndicator</span>
            <span class="o">.</span><span class="nf">tint</span><span class="p">(</span><span class="kt">Color</span><span class="o">.</span><span class="n">red</span><span class="p">)</span>
            <span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mi">40</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">40</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>I also highly recommend to add some readability improvements by adding convenience getters for our styles:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">LoaderStyle</span> <span class="k">where</span> <span class="k">Self</span> <span class="o">==</span> <span class="kt">PrimaryLoaderStyle</span> <span class="p">{</span>
    <span class="kd">static</span> <span class="k">var</span> <span class="nv">primary</span><span class="p">:</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="kt">PrimaryLoaderStyle</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">extension</span> <span class="kt">LoaderStyle</span> <span class="k">where</span> <span class="k">Self</span> <span class="o">==</span> <span class="kt">SecondaryLoaderStyle</span> <span class="p">{</span>
    <span class="kd">static</span> <span class="k">var</span> <span class="nv">secondary</span><span class="p">:</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="kt">SecondaryLoaderStyle</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>To make it all works we need to adjust our main component as well. Create a configuration with a progress and loading indicator and pass it to the loader style:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">LoaderView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>

    <span class="k">let</span> <span class="nv">progress</span><span class="p">:</span> <span class="kt">Double</span>

    <span class="kd">@State</span> <span class="kd">private</span> <span class="k">var</span> <span class="nv">degree</span><span class="p">:</span> <span class="kt">Double</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="kd">@Environment</span><span class="p">(\</span><span class="o">.</span><span class="n">loaderStyle</span><span class="p">)</span> <span class="kd">private</span> <span class="k">var</span> <span class="nv">loaderStyle</span>

    <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">configuration</span> <span class="o">=</span> <span class="kt">LoaderStyleConfiguration</span><span class="p">(</span>
            <span class="nv">progress</span><span class="p">:</span> <span class="n">progress</span><span class="p">,</span>
            <span class="nv">loadingIndicator</span><span class="p">:</span> <span class="p">{</span>
                <span class="kt">Circle</span><span class="p">()</span>
                    <span class="o">.</span><span class="nf">trim</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span> <span class="nv">to</span><span class="p">:</span> <span class="n">progress</span><span class="p">)</span>
                    <span class="o">.</span><span class="nf">stroke</span><span class="p">(</span><span class="o">.</span><span class="n">tint</span><span class="p">,</span> <span class="nv">style</span><span class="p">:</span> <span class="kt">StrokeStyle</span><span class="p">(</span><span class="nv">lineWidth</span><span class="p">:</span> <span class="mf">8.0</span><span class="p">,</span> <span class="nv">lineCap</span><span class="p">:</span> <span class="o">.</span><span class="n">round</span><span class="p">))</span>
                    <span class="o">.</span><span class="nf">rotationEffect</span><span class="p">(</span><span class="o">.</span><span class="nf">degrees</span><span class="p">(</span><span class="n">degree</span><span class="p">))</span>
                    <span class="o">.</span><span class="nf">animation</span><span class="p">(</span><span class="kt">Animation</span><span class="o">.</span><span class="nf">linear</span><span class="p">(</span><span class="nv">duration</span><span class="p">:</span> <span class="mi">1</span><span class="p">)</span><span class="o">.</span><span class="nf">repeatForever</span><span class="p">(</span><span class="nv">autoreverses</span><span class="p">:</span> <span class="kc">false</span><span class="p">),</span> <span class="nv">value</span><span class="p">:</span> <span class="n">degree</span><span class="p">)</span>
                    <span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">idealWidth</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span> <span class="nv">idealHeight</span><span class="p">:</span> <span class="mi">80</span><span class="p">)</span>
                    <span class="o">.</span><span class="n">onAppear</span> <span class="p">{</span>
                        <span class="n">degree</span> <span class="o">=</span> <span class="mi">360</span>
                    <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">)</span>
        <span class="k">let</span> <span class="nv">resolvedView</span> <span class="o">=</span> <span class="n">loaderStyle</span><span class="o">.</span><span class="nf">makeBody</span><span class="p">(</span><span class="nv">configuration</span><span class="p">:</span> <span class="n">configuration</span><span class="p">)</span>
        <span class="kt">AnyView</span><span class="p">(</span><span class="n">resolvedView</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><em>For a more detailed guide into custom view styles I recommend to read this <a href="https://www.fivestars.blog/articles/custom-view-styles/">article</a>.</em></p>

<hr />

<p>Finally we can use it as follows:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">VStack</span><span class="p">(</span><span class="nv">spacing</span><span class="p">:</span> <span class="mi">40</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">LoaderView</span><span class="p">(</span><span class="nv">progress</span><span class="p">:</span> <span class="mf">0.6</span><span class="p">)</span>
        <span class="o">.</span><span class="nf">loaderStyle</span><span class="p">(</span><span class="o">.</span><span class="n">secondary</span><span class="p">)</span>
    
    <span class="kt">LoaderView</span><span class="p">(</span><span class="nv">progress</span><span class="p">:</span> <span class="mf">0.6</span><span class="p">)</span>
        <span class="o">.</span><span class="nf">loaderStyle</span><span class="p">(</span><span class="o">.</span><span class="n">primary</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p><img class="centered post-img" srcset="/assets/img/articles/loader-styles/styles.gif" alt="" /></p>

<hr />

<p>Sure, you don’t have to stick to all the rules for every single component. So, just use this know-how wisely and don’t make your code more complicated than it needs to be. 😉</p>]]></content><author><name>Nikita Ermolenko</name></author><category term="SwiftUI" /><summary type="html"><![CDATA[In this article, we’re going to explore different ways of creating customizable components in SwifUI.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://otbivnoe.ru/assets/img/articles/loader-styles/logo.png" /><media:content medium="image" url="https://otbivnoe.ru/assets/img/articles/loader-styles/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Automating RawRepresentable Conformance with Swift Macros</title><link href="https://otbivnoe.ru/2023/06/13/Automating-RawRepresentable-Conformance-with-Swift-Macros.html" rel="alternate" type="text/html" title="Automating RawRepresentable Conformance with Swift Macros" /><published>2023-06-13T17:00:00+03:00</published><updated>2023-06-13T17:00:00+03:00</updated><id>https://otbivnoe.ru/2023/06/13/Automating-RawRepresentable-Conformance-with-Swift-Macros</id><content type="html" xml:base="https://otbivnoe.ru/2023/06/13/Automating-RawRepresentable-Conformance-with-Swift-Macros.html"><![CDATA[<p>When creating a travel-planner app, it’s essential for users to provide information about their trips. One of the required fields is the visa type. To handle various visa types, we can utilize an enumeration called Visa:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">enum</span> <span class="kt">Visa</span> <span class="p">{</span>
    <span class="k">case</span> <span class="n">tourist</span>
    <span class="k">case</span> <span class="n">business</span>
    <span class="k">case</span> <span class="n">student</span>
<span class="p">}</span>
</code></pre></div></div>

<p>However, considering the possibility of new visa types being introduced in the future, we need a fallback a case that can accommodate any unrecognized visa types. This is where the <code class="language-plaintext highlighter-rouge">other</code> case comes into play:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">enum</span> <span class="kt">Visa</span> <span class="p">{</span>
    <span class="c1">// ...</span>
    <span class="k">case</span> <span class="nf">other</span><span class="p">(</span><span class="kt">String</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now, here comes the problem: how do we store this enum in a persistence storage solution like Core Data? Unfortunately, Core Data doesn’t provide built-in support for enums with associated values. One approach that comes to mind is making the Visa enum conform to <code class="language-plaintext highlighter-rouge">RawRepresentable</code> and save an underlying value. However, since enums with associated values don’t offer a default implementation for <code class="language-plaintext highlighter-rouge">RawRepresentable</code>, we need to manually implement it ourselves:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">enum</span> <span class="kt">Visa</span><span class="p">:</span> <span class="kt">RawRepresentable</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">rawValue</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span>
        <span class="k">switch</span> <span class="k">self</span> <span class="p">{</span>
        <span class="k">case</span> <span class="o">.</span><span class="nv">tourist</span><span class="p">:</span>
            <span class="k">return</span> <span class="s">"tourist"</span>
        <span class="k">case</span> <span class="o">.</span><span class="nv">business</span><span class="p">:</span>
            <span class="k">return</span> <span class="s">"business"</span>
        <span class="k">case</span> <span class="o">.</span><span class="nv">student</span><span class="p">:</span>
            <span class="k">return</span> <span class="s">"student"</span>
        <span class="k">case</span> <span class="o">.</span><span class="nf">other</span><span class="p">(</span><span class="k">let</span> <span class="nv">value</span><span class="p">):</span>
            <span class="k">return</span> <span class="n">value</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="nf">init</span><span class="p">?(</span><span class="nv">rawValue</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">switch</span> <span class="n">rawValue</span> <span class="p">{</span>
        <span class="k">case</span> <span class="s">"tourist"</span><span class="p">:</span>
            <span class="k">self</span> <span class="o">=</span> <span class="o">.</span><span class="n">tourist</span>
        <span class="k">case</span> <span class="s">"business"</span><span class="p">:</span>
            <span class="k">self</span> <span class="o">=</span> <span class="o">.</span><span class="n">business</span>
        <span class="k">case</span> <span class="s">"student"</span><span class="p">:</span>
            <span class="k">self</span> <span class="o">=</span> <span class="o">.</span><span class="n">student</span>
        <span class="k">default</span><span class="p">:</span>
            <span class="k">self</span> <span class="o">=</span> <span class="o">.</span><span class="nf">other</span><span class="p">(</span><span class="n">rawValue</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>With this implementation, we can now store and retrieve visa types seamlessly using Core Data or any other persistence storage mechanism.</p>

<hr />

<p>We all know developers are clever at finding shortcuts. But what happens when we need to duplicate the same logic for different types? That’s why Swift has introduced a feature called Macros. It automates the whole process, saving us lazy developers tons of time and effort. If you’re not familiar with Macros, I suggest checking out this <a href="https://developer.apple.com/wwdc23/10166">WWDC session</a> or this <a href="https://www.avanderlee.com/swift/macros/">introduction article</a> by Antoine before diving into the rest of this article.</p>

<p>Today we’ll discuss the attached macros:</p>

<p><em>Attached macros provide a way to extend Swift by creating and extending declarations based on arbitrary syntactic transformations on their arguments. They make it possible to extend Swift in ways that were only previously possible by introducing new language features, helping developers build more expressive libraries and eliminate extraneous boilerplate.</em></p>

<p>Before we dive into the implementation details, let’s start by adding the Macro itself:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">enum</span> <span class="kt">StringRepresentationMacro</span> <span class="p">{}</span>
</code></pre></div></div>

<p>With that in place, we can move on to the first step, which is adding protocol conformance to our type. To accomplish this, we’ll utilize the <code class="language-plaintext highlighter-rouge">ConformanceMacro</code> protocol. This protocol describes a macro that can add conformance to the declaration it’s attached to.</p>

<p>In the following code snippet, we’ll use the <code class="language-plaintext highlighter-rouge">expansion</code> function to add the <code class="language-plaintext highlighter-rouge">RawRepresentable</code> conformance to our type. We keep it simple and only specify a single conformance to <code class="language-plaintext highlighter-rouge">RawRepresentable</code>, omitting any additional constraints (where clause) by returning <code class="language-plaintext highlighter-rouge">nil</code>.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">StringRepresentationMacro</span><span class="p">:</span> <span class="kt">ConformanceMacro</span> <span class="p">{</span>
    <span class="kd">static</span> <span class="kd">func</span> <span class="n">expansion</span><span class="o">&lt;</span><span class="kt">Declaration</span><span class="p">,</span> <span class="kt">Context</span><span class="o">&gt;</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="p">[(</span><span class="kt">TypeSyntax</span><span class="p">,</span> <span class="kt">GenericWhereClauseSyntax</span><span class="p">?)]</span> <span class="nf">where</span> <span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="p">[</span>
            <span class="p">(</span><span class="kt">TypeSyntax</span><span class="p">(</span><span class="s">"RawRepresentable"</span><span class="p">),</span> <span class="kc">nil</span><span class="p">)</span>
        <span class="p">]</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>To make our macro accessible, we need to add the <code class="language-plaintext highlighter-rouge">@attached(conformance)</code> attribute to the public declaration:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@attached</span><span class="p">(</span><span class="n">conformance</span><span class="p">)</span>
<span class="kd">public</span> <span class="n">macro</span> <span class="kt">StringRawRepresentation</span><span class="p">()</span> <span class="o">=</span> <span class="err">#</span><span class="nf">externalMacro</span><span class="p">(</span><span class="nv">module</span><span class="p">:</span> <span class="s">"MyMacros"</span><span class="p">,</span> <span class="nv">type</span><span class="p">:</span> <span class="s">"StringRepresentationMacro"</span><span class="p">)</span>
</code></pre></div></div>

<p>If we try to expand our Macro in Xcode using <code class="language-plaintext highlighter-rouge">Expand Macro</code> option we’ll see the following generated code:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">Visa</span> <span class="p">:</span> <span class="kt">RawRepresentable</span>  <span class="p">{}</span>
</code></pre></div></div>

<p>Now that we’ve added the conformance, the next step is to implement the <code class="language-plaintext highlighter-rouge">RawRepresentable</code> protocol itself. To achieve this, we’ll utilize the <code class="language-plaintext highlighter-rouge">MemberMacro</code> protocol, which defines new members for the declaration it’s attached to. Similar to the <code class="language-plaintext highlighter-rouge">ConformanceMacro</code>, there’s a single function for implementing:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">StringRepresentationMacro</span><span class="p">:</span> <span class="kt">MemberMacro</span> <span class="p">{</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">func</span> <span class="n">expansion</span><span class="o">&lt;</span><span class="kt">Declaration</span><span class="p">,</span> <span class="kt">Context</span><span class="o">&gt;</span><span class="p">(</span><span class="n">of</span> <span class="nv">node</span><span class="p">:</span> <span class="kt">AttributeSyntax</span><span class="p">,</span>
                                                       <span class="n">providingMembersOf</span> <span class="nv">declaration</span><span class="p">:</span> <span class="kt">Declaration</span><span class="p">,</span>
                                                       <span class="k">in</span> <span class="nv">context</span><span class="p">:</span> <span class="kt">Context</span><span class="p">)</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="p">[</span><span class="kt">DeclSyntax</span><span class="p">]</span>
    <span class="k">where</span> <span class="kt">Declaration</span> <span class="p">:</span> <span class="kt">DeclGroupSyntax</span><span class="p">,</span> <span class="kt">Context</span> <span class="p">:</span> <span class="kt">MacroExpansionContext</span> <span class="p">{</span>
        <span class="o">...</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>First, we must ensure that the type we’re adding the macro to is an enum. If it’s not, we need to throw an error about the unsupported declaration:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">enum</span> <span class="kt">MacroError</span><span class="p">:</span> <span class="kt">Error</span><span class="p">,</span> <span class="kt">CustomStringConvertible</span> <span class="p">{</span>
    <span class="k">case</span> <span class="nf">message</span><span class="p">(</span><span class="kt">String</span><span class="p">)</span>

    <span class="k">var</span> <span class="nv">description</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span>
        <span class="k">switch</span> <span class="k">self</span> <span class="p">{</span>
        <span class="k">case</span> <span class="o">.</span><span class="nf">message</span><span class="p">(</span><span class="k">let</span> <span class="nv">text</span><span class="p">):</span>
            <span class="k">return</span> <span class="n">text</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">guard</span> <span class="k">let</span> <span class="nv">enumDeclaration</span> <span class="o">=</span> <span class="n">declaration</span><span class="o">.</span><span class="nf">as</span><span class="p">(</span><span class="kt">EnumDeclSyntax</span><span class="o">.</span><span class="k">self</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>    
    <span class="k">throw</span> <span class="kt">MacroError</span><span class="o">.</span><span class="nf">message</span><span class="p">(</span><span class="s">"@StringRawRepresentation only works with Enums"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Next, we obtain all the enum cases to iterate through:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">cases</span> <span class="o">=</span> <span class="n">enumDeclaration</span><span class="o">.</span><span class="n">memberBlock</span><span class="o">.</span><span class="n">members</span>
    <span class="o">.</span><span class="n">compactMap</span> <span class="p">{</span>
        <span class="nv">$0</span><span class="o">.</span><span class="n">decl</span><span class="o">.</span><span class="nf">as</span><span class="p">(</span><span class="kt">EnumCaseDeclSyntax</span><span class="o">.</span><span class="k">self</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="o">.</span><span class="n">flatMap</span> <span class="p">{</span>
        <span class="nv">$0</span><span class="o">.</span><span class="n">elements</span>
    <span class="p">}</span>
</code></pre></div></div>

<p>To generate an initializer conformance, we need to iterate through each case, and add a mapping to a string representation. To keep things simple for this article, we’ll assume that there’s only a single case with a String associated type. However, it’s important to note that you can customize and handle different edge cases and present a validation error for a developer as we did before:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">initializer</span> <span class="o">=</span> <span class="k">try</span> <span class="kt">InitializerDeclSyntax</span><span class="p">(</span><span class="s">"init?(rawValue: String)"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">try</span> <span class="kt">SwitchExprSyntax</span><span class="p">(</span><span class="s">"switch rawValue"</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">for</span> <span class="n">element</span> <span class="k">in</span> <span class="n">cases</span> <span class="p">{</span>
            <span class="k">if</span> <span class="n">element</span><span class="o">.</span><span class="n">associatedValue</span> <span class="o">==</span> <span class="kc">nil</span> <span class="p">{</span>
                <span class="kt">SwitchCaseSyntax</span><span class="p">(</span>
                    <span class="s">"""
                    case "</span><span class="p">\(</span><span class="n">element</span><span class="o">.</span><span class="n">identifier</span><span class="p">)</span><span class="s">":
                        self = .</span><span class="se">\(</span><span class="n">element</span><span class="o">.</span><span class="n">identifier</span><span class="se">)</span><span class="s">
                    """</span>
                <span class="p">)</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="kt">SwitchCaseSyntax</span><span class="p">(</span>
                    <span class="s">"""
                    default:
                        self = .</span><span class="se">\(</span><span class="n">element</span><span class="o">.</span><span class="n">identifier</span><span class="se">)</span><span class="s">(rawValue)
                    """</span>
                <span class="p">)</span>
            <span class="p">}</span>
        <span class="p">}</span>

    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Using the similar techniques we’ll generate a variable conformance:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">variable</span> <span class="o">=</span> <span class="k">try</span> <span class="kt">VariableDeclSyntax</span><span class="p">(</span><span class="s">"var rawValue: String"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">try</span> <span class="kt">SwitchExprSyntax</span><span class="p">(</span><span class="s">"switch self"</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">for</span> <span class="n">element</span> <span class="k">in</span> <span class="n">cases</span> <span class="p">{</span>
            <span class="k">if</span> <span class="n">element</span><span class="o">.</span><span class="n">associatedValue</span> <span class="o">==</span> <span class="kc">nil</span> <span class="p">{</span>
                <span class="kt">SwitchCaseSyntax</span><span class="p">(</span>
                <span class="s">"""
                case .</span><span class="se">\(</span><span class="n">element</span><span class="o">.</span><span class="n">identifier</span><span class="se">)</span><span class="s">:
                    return "</span><span class="p">\(</span><span class="n">element</span><span class="o">.</span><span class="n">identifier</span><span class="p">)</span><span class="s">"
                """</span>
                <span class="p">)</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="kt">SwitchCaseSyntax</span><span class="p">(</span>
                    <span class="s">"""
                    case .</span><span class="se">\(</span><span class="n">element</span><span class="o">.</span><span class="n">identifier</span><span class="se">)</span><span class="s">(let value):
                        return value
                    """</span>
                <span class="p">)</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Lastly, we can simply return these two declarations:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">return</span> <span class="p">[</span>
    <span class="kt">DeclSyntax</span><span class="p">(</span><span class="n">variable</span><span class="p">),</span>
    <span class="kt">DeclSyntax</span><span class="p">(</span><span class="n">initializer</span><span class="p">)</span>
<span class="p">]</span>
</code></pre></div></div>

<p>and add a second <code class="language-plaintext highlighter-rouge">attached</code> attribute:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@attached</span><span class="p">(</span><span class="n">member</span><span class="p">,</span> <span class="nv">names</span><span class="p">:</span> <span class="nf">named</span><span class="p">(</span><span class="n">rawValue</span><span class="p">),</span> <span class="nf">named</span><span class="p">(</span><span class="kd">init</span><span class="p">))</span>
<span class="kd">@attached</span><span class="p">(</span><span class="n">conformance</span><span class="p">)</span>
<span class="kd">public</span> <span class="n">macro</span> <span class="kt">StringRawRepresentation</span><span class="p">()</span> <span class="o">=</span> <span class="err">#</span><span class="nf">externalMacro</span><span class="p">(</span><span class="nv">module</span><span class="p">:</span> <span class="s">"MyMacros"</span><span class="p">,</span> <span class="nv">type</span><span class="p">:</span> <span class="s">"StringRepresentationMacro"</span><span class="p">)</span>
</code></pre></div></div>

<p>That’s it! Take a moment to appreciate the beauty of this code:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@StringRawRepresentation</span>
<span class="kd">enum</span> <span class="kt">Visa</span> <span class="p">{</span>
    <span class="k">case</span> <span class="n">tourist</span>
    <span class="k">case</span> <span class="n">business</span>
    <span class="k">case</span> <span class="n">student</span>
    <span class="k">case</span> <span class="nf">other</span><span class="p">(</span><span class="kt">String</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>With the implementation of attached macros, all the tedious work is now handled seamlessly under the hood. As developers, we can truly appreciate the time and effort saved by leveraging these powerful features of Swift. Our code is now more elegant, concise, and maintainable, allowing us to focus on the core logic of our applications.</p>

<hr />

<h3 id="references">References</h3>

<ul>
  <li><a href="https://developer.apple.com/wwdc23/10166">Write Swift Macros, WWDC23</a></li>
  <li><a href="https://www.avanderlee.com/swift/macros/">Swift Macros: Extend Swift with New Kinds of Expressions</a></li>
  <li><a href="https://github.com/apple/swift-evolution/blob/main/proposals/0389-attached-macros.md">Proposal: Attached Macros</a></li>
  <li><a href="https://github.com/artemnovichkov/awesome-swift-macros">Awesome Swift Macros</a></li>
</ul>]]></content><author><name>Nikita Ermolenko</name></author><category term="Swift" /><category term="Macros" /><summary type="html"><![CDATA[When creating a travel-planner app, it’s essential for users to provide information about their trips. One of the required fields is the visa type. To handle various visa types, we can utilize an enumeration called Visa:]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://otbivnoe.ru/assets/img/articles/swift-macros/logo.png" /><media:content medium="image" url="https://otbivnoe.ru/assets/img/articles/swift-macros/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">UITouch Me If You Can</title><link href="https://otbivnoe.ru/2022/07/19/UITouch-Me-If-You-Can.html" rel="alternate" type="text/html" title="UITouch Me If You Can" /><published>2022-07-19T10:00:00+03:00</published><updated>2022-07-19T10:00:00+03:00</updated><id>https://otbivnoe.ru/2022/07/19/UITouch-Me-If-You-Can</id><content type="html" xml:base="https://otbivnoe.ru/2022/07/19/UITouch-Me-If-You-Can.html"><![CDATA[<p>During several interview processes, I’ve been asked many times about <a href="https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/using_responders_and_the_responder_chain_to_handle_events">UIResponder</a> and what the beast it is. I guess everybody can somehow explain this concept in iOS, which I did, but lack of in-depth knowledge frustrated me. So I decided to spend a few evenings to clarify it for myself. In this article I won’t cover the everything about <code class="language-plaintext highlighter-rouge">UIResponder</code> at once. Instead we’ll figure out how our app handles touches and how responder chain helps us in that process. Later on we can look at the target-action logic as well, so stay tuned to my <a href="/feed.xml">RSS</a> feed.</p>

<hr />

<p>In short, <code class="language-plaintext highlighter-rouge">UIResponder</code> is an abstract interface for responding and handling events. All known <code class="language-plaintext highlighter-rouge">UIView</code>, <code class="language-plaintext highlighter-rouge">UIViewController</code>, <code class="language-plaintext highlighter-rouge">UIWindow</code>, <code class="language-plaintext highlighter-rouge">UIApplication</code> and <code class="language-plaintext highlighter-rouge">UIApplicationDelegate</code> are <code class="language-plaintext highlighter-rouge">UIResponders</code>.</p>

<p>For touch events the system uses hit-testing to determine where touch events occur. Specifically, UIKit compares the touch location to the bounds of view objects in the view hierarchy. The <a href="https://developer.apple.com/documentation/uikit/uiview/1622469-hittest">hitTest</a> method of <code class="language-plaintext highlighter-rouge">UIView</code> traverses the view hierarchy, looking for the deepest subview that contains the specified touch, which becomes the <strong>first responder</strong> for the touch event. Then the system creates a <code class="language-plaintext highlighter-rouge">UITouchEvent</code> object, associates it with a view and dispatch the event to the system event queue by calling <code class="language-plaintext highlighter-rouge">UIApplication.sendEvent</code>.</p>

<p>For a clear demonstration we’ll use a simple screen with two coloured views. By tapping on one of them we’ll explore the whole process of touching.</p>

<p>The investigation starts with tapping the screen, so let’s do that. As I mentioned earlier, the system uses hit-testing approach to determine who owns the touch. Thereby, by checking the stack trace of the starting point, we can see that system initiates a touch-test on the window layer, by enumerating each of them.</p>

<p><img class="centered post-img" srcset="/assets/img/articles/touch-responder/hit-test-stacktrace.png" alt="" /></p>

<p>For every window we enumerate on we should find a deepest subview that contains (or not) the specified touch. Having the approximate implementation of <code class="language-plaintext highlighter-rouge">hitTest</code> following the <a href="https://developer.apple.com/documentation/uikit/uiview/1622469-hittest">documentation</a></p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">hitTest</span><span class="p">(</span><span class="n">_</span> <span class="nv">point</span><span class="p">:</span> <span class="kt">CGPoint</span><span class="p">,</span> <span class="n">with</span> <span class="nv">event</span><span class="p">:</span> <span class="kt">UIEvent</span><span class="p">?)</span> <span class="o">-&gt;</span> <span class="kt">UIView</span><span class="p">?</span> <span class="p">{</span>	
    <span class="k">guard</span> <span class="n">isUserInteractionEnabled</span><span class="p">,</span> <span class="o">!</span><span class="n">isHidden</span><span class="p">,</span> <span class="n">alpha</span> <span class="o">&gt;=</span> <span class="mf">0.01</span><span class="p">,</span> <span class="nf">point</span><span class="p">(</span><span class="nv">inside</span><span class="p">:</span> <span class="n">point</span><span class="p">,</span> <span class="nv">with</span><span class="p">:</span> <span class="n">event</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span> 
        <span class="k">return</span> <span class="kc">nil</span> 
    <span class="p">}</span>        	

    <span class="c1">// First, checks the subviews with the highest Z-coordinate value. </span>
    <span class="k">for</span> <span class="n">subview</span> <span class="k">in</span> <span class="n">subviews</span><span class="o">.</span><span class="nf">reversed</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">convertedPoint</span> <span class="o">=</span> <span class="n">subview</span><span class="o">.</span><span class="nf">convert</span><span class="p">(</span><span class="n">point</span><span class="p">,</span> <span class="nv">from</span><span class="p">:</span> <span class="k">self</span><span class="p">)</span>
        <span class="k">if</span> <span class="k">let</span> <span class="nv">view</span> <span class="o">=</span> <span class="n">subview</span><span class="o">.</span><span class="nf">hitTest</span><span class="p">(</span><span class="n">convertedPoint</span><span class="p">,</span> <span class="nv">with</span><span class="p">:</span> <span class="n">event</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="n">view</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="k">self</span>
<span class="p">}</span>
</code></pre></div></div>

<p>we can see the iterating process over every subview to find the needed one:</p>

<p><img class="centered post-img" srcset="/assets/img/articles/touch-responder/traverse-hittest.png" alt="" /></p>

<p><strong>Note.</strong> In some cases the <code class="language-plaintext highlighter-rouge">hitTest</code> maybe <a href="https://lists.apple.com/archives/cocoa-dev/2014/Feb/msg00118.html">called twice</a> - the system may tweak the point being hit tested between the calls. Since <code class="language-plaintext highlighter-rouge">hitTest</code> should be a pure function with no side-effects, this should be fine.</p>

<p>The first responder is found (green view), the next step is to detect a <a href="https://developer.apple.com/documentation/uikit/uitouch/1618126-window">window</a> to which the view is attached for. The system detects the view’s <code class="language-plaintext highlighter-rouge">_responderWindow</code> right before sending a touch event using <a href="https://developer.apple.com/documentation/uikit/uiapplication/1623043-sendevent">UIApplication.sendEvent</a>.</p>

<p><img class="centered post-img" srcset="/assets/img/articles/touch-responder/responder-window.png" alt="" /></p>

<p><strong>UIResponder chain</strong> comes here to the rescue. Starting with the green view, we’ll iterate through the responders up to the <code class="language-plaintext highlighter-rouge">UIWindow</code> using the <a href="https://developer.apple.com/documentation/uikit/uiresponder/1621099-next">next</a> property.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">ChildView</span> <span class="o">-&gt;</span> <span class="kt">RootView</span> <span class="o">-&gt;</span> <span class="kt">ViewController</span> <span class="o">-&gt;</span> <span class="kt">UIDropShadowView</span> <span class="o">-&gt;</span> <span class="kt">UITransitionView</span> <span class="o">-&gt;</span> <span class="kt">UIWindow</span>
</code></pre></div></div>

<p>Having the window and first responder view, the system is ready to send an event:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">SENT</span> <span class="kt">EVENT</span><span class="p">:</span> 

<span class="o">&lt;</span><span class="kt">UITouchesEvent</span><span class="p">:</span> <span class="mh">0x600002108180</span><span class="o">&gt;</span> 
<span class="nv">timestamp</span><span class="p">:</span> <span class="mf">37554.7</span> 
<span class="nv">touches</span><span class="p">:</span> <span class="p">{(</span>
    <span class="o">&lt;</span><span class="kt">UITouch</span><span class="p">:</span> <span class="mh">0x7fe8ebb0be20</span><span class="o">&gt;</span> 
    <span class="nv">phase</span><span class="p">:</span> <span class="kt">Began</span> 
    <span class="n">tap</span> <span class="nv">count</span><span class="p">:</span> <span class="mi">1</span> 
    <span class="nv">force</span><span class="p">:</span> <span class="mf">0.000</span> 
    <span class="nv">window</span><span class="p">:</span> <span class="p">(</span><span class="kt">UIWindow</span> <span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">390.0</span><span class="p">,</span> <span class="mf">844.0</span><span class="p">))</span> 
    <span class="nv">view</span><span class="p">:</span> <span class="p">(</span><span class="kt">ChildView</span> <span class="p">(</span><span class="mf">150.0</span><span class="p">,</span> <span class="mf">300.0</span><span class="p">,</span> <span class="mf">80.0</span><span class="p">,</span> <span class="mf">80.0</span><span class="p">))</span> 
    <span class="n">location</span> <span class="k">in</span> <span class="nv">window</span><span class="p">:</span> <span class="p">{</span><span class="mf">188.33332824707031</span><span class="p">,</span> <span class="mi">361</span><span class="p">}</span> 
    <span class="n">previous</span> <span class="n">location</span> <span class="k">in</span> <span class="nv">window</span><span class="p">:</span> <span class="p">{</span><span class="mf">188.33332824707031</span><span class="p">,</span> <span class="mi">361</span><span class="p">}</span> 
    <span class="n">location</span> <span class="k">in</span> <span class="nv">view</span><span class="p">:</span> <span class="p">{</span><span class="mf">38.333328247070312</span><span class="p">,</span> <span class="mi">61</span><span class="p">}</span> 
    <span class="n">previous</span> <span class="n">location</span> <span class="k">in</span> <span class="nv">view</span><span class="p">:</span> <span class="p">{</span><span class="mf">38.333328247070312</span><span class="p">,</span> <span class="mi">61</span><span class="p">}</span>
<span class="p">)}</span>
</code></pre></div></div>

<p>later on the following method will be called in <code class="language-plaintext highlighter-rouge">ChildView</code>:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">touchesBegan</span><span class="p">(</span><span class="n">_</span> <span class="nv">touches</span><span class="p">:</span> <span class="kt">Set</span><span class="o">&lt;</span><span class="kt">UITouch</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">with</span> <span class="nv">event</span><span class="p">:</span> <span class="kt">UIEvent</span><span class="p">?)</span>
</code></pre></div></div>

<p><strong>Note.</strong> The hit-test view is linked with the <code class="language-plaintext highlighter-rouge">UITouch</code> object for all phases of the touch event (began, moved, ended and canceled). The system won’t start the hit-testing logic for further events again. It’ll take the associated view and send the events right in.</p>

<hr />

<h3 id="references">References</h3>
<ul>
  <li><a href="https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/using_responders_and_the_responder_chain_to_handle_events">Using Responders and the Responder Chain to Handle Events</a></li>
  <li><a href="https://khanlou.com/2018/09/hacking-hit-tests/">Hacking Hit Tests</a></li>
  <li><a href="https://rolandleth.com/tech/blog/increasing-the-tap-area-of-a-uibutton">Increasing the tap area of a UIButton</a></li>
</ul>]]></content><author><name>Nikita Ermolenko</name></author><category term="UIResponder" /><category term="UIKit" /><summary type="html"><![CDATA[During several interview processes, I’ve been asked many times about UIResponder and what the beast it is. I guess everybody can somehow explain this concept in iOS, which I did, but lack of in-depth knowledge frustrated me. So I decided to spend a few evenings to clarify it for myself. In this article I won’t cover the everything about UIResponder at once. Instead we’ll figure out how our app handles touches and how responder chain helps us in that process. Later on we can look at the target-action logic as well, so stay tuned to my RSS feed.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://otbivnoe.ru/assets/img/articles/touch-responder/logo.png" /><media:content medium="image" url="https://otbivnoe.ru/assets/img/articles/touch-responder/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Enumerated ForEach in SwiftUI</title><link href="https://otbivnoe.ru/2022/07/13/Enumerated-ForEach-in-SwiftUI.html" rel="alternate" type="text/html" title="Enumerated ForEach in SwiftUI" /><published>2022-07-13T10:00:00+03:00</published><updated>2022-07-13T10:00:00+03:00</updated><id>https://otbivnoe.ru/2022/07/13/Enumerated-ForEach-in-SwiftUI</id><content type="html" xml:base="https://otbivnoe.ru/2022/07/13/Enumerated-ForEach-in-SwiftUI.html"><![CDATA[<p>Today I’m faced with a fairly common task - implementing an enumerated ForEach with indexes in SwiftUI. At first it seemed straightforward, and it was, but with some small pitfalls that I want to highlight in this post. So, without further ado, let’s dive into.</p>

<p>As an idol we’ll use the <a href="https://developer.apple.com/documentation/swiftui/foreach">ForEach</a> from the SwiftUI library with the same protocol conformance for our own version.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">struct</span> <span class="kt">ForEach</span><span class="o">&lt;</span><span class="kt">Data</span><span class="p">,</span> <span class="kt">ID</span><span class="p">,</span> <span class="kt">Content</span><span class="o">&gt;</span> <span class="k">where</span> <span class="kt">Data</span> <span class="p">:</span> <span class="kt">RandomAccessCollection</span><span class="p">,</span> <span class="kt">ID</span> <span class="p">:</span> <span class="kt">Hashable</span> <span class="p">{</span>

    <span class="c1">/// The collection of underlying identified data that SwiftUI uses to create</span>
    <span class="c1">/// views dynamically.</span>
    <span class="kd">public</span> <span class="k">var</span> <span class="nv">data</span><span class="p">:</span> <span class="kt">Data</span>

    <span class="c1">/// A function to create content on demand using the underlying data.</span>
    <span class="kd">public</span> <span class="k">var</span> <span class="nv">content</span><span class="p">:</span> <span class="p">(</span><span class="kt">Data</span><span class="o">.</span><span class="kt">Element</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Content</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The first implementation is the following:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">ForEachEnumerated</span><span class="o">&lt;</span><span class="kt">Data</span><span class="p">,</span> <span class="kt">Content</span><span class="o">&gt;</span><span class="p">:</span> <span class="kt">View</span> <span class="k">where</span> <span class="kt">Data</span><span class="p">:</span> <span class="kt">RandomAccessCollection</span><span class="p">,</span> <span class="kt">Data</span><span class="o">.</span><span class="kt">Element</span><span class="p">:</span> <span class="kt">Hashable</span><span class="p">,</span> <span class="kt">Content</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>

    <span class="k">var</span> <span class="nv">data</span><span class="p">:</span> <span class="kt">Data</span>
    <span class="k">var</span> <span class="nv">content</span><span class="p">:</span> <span class="p">(</span><span class="kt">Int</span><span class="p">,</span> <span class="kt">Data</span><span class="o">.</span><span class="kt">Element</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Content</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">ForEach</span><span class="p">(</span><span class="kt">Array</span><span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="nf">enumerated</span><span class="p">()),</span> <span class="nv">id</span><span class="p">:</span> <span class="p">\</span><span class="o">.</span><span class="n">element</span><span class="p">)</span> <span class="p">{</span> <span class="n">index</span><span class="p">,</span> <span class="n">element</span> <span class="k">in</span>
            <span class="nf">content</span><span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="n">element</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>It does the desired behavior, but there’s something we need to be aware of.</p>

<p><em>When you enumerate a collection, the integer part of each pair is a counter for the enumeration, but is not necessarily the index of the paired value. These counters can be used as indices only in instances of zero-based, integer-indexed collections, such as Array and ContiguousArray. For other collections the counters may be out of range or of the wrong type to use as an index. To iterate over the elements of a collection with its indices, use the zip(</em>:<em>:) function.</em> <a href="https://developer.apple.com/documentation/swift/array/enumerated()">(documentation)</a></p>

<ul>
  <li>This version is only suitable for integer-indexed collections</li>
  <li>Furthermore, it can lead us to an out of range crash. Consider this example, it leads to crashing an app, since the index is not zero-based:</li>
</ul>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">array</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span>
<span class="k">let</span> <span class="nv">newArray</span> <span class="o">=</span> <span class="n">array</span><span class="p">[</span><span class="mi">2</span><span class="o">...</span><span class="mi">3</span><span class="p">]</span>

<span class="c1">// newArray[0] // -&gt; CRASH</span>
<span class="c1">// newArray.indices // -&gt; [2..&lt;4]</span>
</code></pre></div></div>

<p>With the above in mind, we should modify our version. Now the index type is the same as Data’s one and indices are not zero-based anymore.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">ForEachEnumerated</span><span class="o">&lt;</span><span class="kt">Data</span><span class="p">,</span> <span class="kt">Content</span><span class="o">&gt;</span><span class="p">:</span> <span class="kt">View</span> <span class="k">where</span> <span class="kt">Data</span><span class="p">:</span> <span class="kt">RandomAccessCollection</span><span class="p">,</span> <span class="kt">Data</span><span class="o">.</span><span class="kt">Element</span><span class="p">:</span> <span class="kt">Hashable</span><span class="p">,</span> <span class="kt">Content</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>

    <span class="k">var</span> <span class="nv">data</span><span class="p">:</span> <span class="kt">Data</span>
    <span class="k">var</span> <span class="nv">content</span><span class="p">:</span> <span class="p">(</span><span class="kt">Data</span><span class="o">.</span><span class="kt">Index</span><span class="p">,</span> <span class="kt">Data</span><span class="o">.</span><span class="kt">Element</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Content</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">ForEach</span><span class="p">(</span><span class="kt">Array</span><span class="p">(</span><span class="nf">zip</span><span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">indices</span><span class="p">,</span> <span class="n">data</span><span class="p">)),</span> <span class="nv">id</span><span class="p">:</span> <span class="p">\</span><span class="o">.</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span> <span class="n">index</span><span class="p">,</span> <span class="n">element</span> <span class="k">in</span>
            <span class="nf">content</span><span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="n">element</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>It already looks as a production-ready, however there’s something that would be great to improve. What about this code <code class="language-plaintext highlighter-rouge">id: \.1</code>? This restricts <code class="language-plaintext highlighter-rouge">Data.Element</code> to be <code class="language-plaintext highlighter-rouge">Hashable</code> and what if I don’t want to use the entire object as a identifier?</p>

<p>To achieve this goal, we’ll add another property for storing a keypath and a small structure (<code class="language-plaintext highlighter-rouge">Info</code>) for enumerating using id.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">ForEachEnumerated</span><span class="o">&lt;</span><span class="kt">Data</span><span class="p">,</span> <span class="kt">ID</span><span class="p">,</span> <span class="kt">Content</span><span class="o">&gt;</span><span class="p">:</span> <span class="kt">View</span> <span class="k">where</span> <span class="kt">Data</span><span class="p">:</span> <span class="kt">RandomAccessCollection</span><span class="p">,</span> <span class="kt">ID</span><span class="p">:</span> <span class="kt">Hashable</span><span class="p">,</span> <span class="kt">Content</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>

    <span class="c1">// For the simplicity of type reading</span>
    <span class="kd">typealias</span> <span class="kt">Index</span> <span class="o">=</span> <span class="kt">Data</span><span class="o">.</span><span class="kt">Index</span>
    <span class="kd">typealias</span> <span class="kt">Element</span> <span class="o">=</span> <span class="kt">Data</span><span class="o">.</span><span class="kt">Element</span>

    <span class="kd">private</span> <span class="kd">struct</span> <span class="kt">Info</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">index</span><span class="p">:</span> <span class="kt">Index</span>
        <span class="k">let</span> <span class="nv">id</span><span class="p">:</span> <span class="kt">KeyPath</span><span class="o">&lt;</span><span class="kt">Element</span><span class="p">,</span> <span class="kt">ID</span><span class="o">&gt;</span>
        <span class="k">let</span> <span class="nv">element</span><span class="p">:</span> <span class="kt">Element</span>

        <span class="k">var</span> <span class="nv">elementID</span><span class="p">:</span> <span class="kt">ID</span> <span class="p">{</span>
            <span class="n">element</span><span class="p">[</span><span class="nv">keyPath</span><span class="p">:</span> <span class="n">id</span><span class="p">]</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">var</span> <span class="nv">data</span><span class="p">:</span> <span class="kt">Data</span>
    <span class="k">var</span> <span class="nv">content</span><span class="p">:</span> <span class="p">(</span><span class="kt">Index</span><span class="p">,</span> <span class="kt">Element</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Content</span>
    <span class="k">let</span> <span class="nv">id</span><span class="p">:</span> <span class="kt">KeyPath</span><span class="o">&lt;</span><span class="kt">Element</span><span class="p">,</span> <span class="kt">ID</span><span class="o">&gt;</span>

    <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">enumeratedData</span> <span class="o">=</span> <span class="nf">zip</span><span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">indices</span><span class="p">,</span> <span class="n">data</span><span class="p">)</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="n">index</span><span class="p">,</span> <span class="n">element</span> <span class="k">in</span>
            <span class="kt">Info</span><span class="p">(</span><span class="nv">index</span><span class="p">:</span> <span class="n">index</span><span class="p">,</span> <span class="nv">id</span><span class="p">:</span> <span class="n">id</span><span class="p">,</span> <span class="nv">element</span><span class="p">:</span> <span class="n">element</span><span class="p">)</span>
        <span class="p">}</span>

        <span class="kt">ForEach</span><span class="p">(</span><span class="n">enumeratedData</span><span class="p">,</span> <span class="nv">id</span><span class="p">:</span> <span class="p">\</span><span class="o">.</span><span class="n">elementID</span><span class="p">)</span> <span class="p">{</span> <span class="n">indexInfo</span> <span class="k">in</span>
            <span class="nf">content</span><span class="p">(</span><span class="n">indexInfo</span><span class="o">.</span><span class="n">index</span><span class="p">,</span> <span class="n">indexInfo</span><span class="o">.</span><span class="n">element</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Following the rules of our idol, let’s extend our type with a handy initializer that’s really useful for <code class="language-plaintext highlighter-rouge">Identifiable</code> data objects:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">ForEachEnumerated</span> <span class="k">where</span> <span class="kt">Element</span><span class="p">:</span> <span class="kt">Identifiable</span><span class="p">,</span> <span class="kt">ID</span> <span class="o">==</span> <span class="kt">Element</span><span class="o">.</span><span class="kt">ID</span> <span class="p">{</span>
    <span class="nf">init</span><span class="p">(</span><span class="n">_</span> <span class="nv">data</span><span class="p">:</span> <span class="kt">Data</span><span class="p">,</span> <span class="kd">@ViewBuilder</span> <span class="nv">content</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="p">(</span><span class="kt">Index</span><span class="p">,</span> <span class="kt">Element</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Content</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="nv">content</span><span class="p">:</span> <span class="n">content</span><span class="p">,</span> <span class="nv">id</span><span class="p">:</span> <span class="p">\</span><span class="o">.</span><span class="n">id</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>In conclusion, a small example that demonstrates how it looks:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">Article</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">title</span><span class="p">:</span> <span class="kt">String</span>
    <span class="k">let</span> <span class="nv">author</span><span class="p">:</span> <span class="kt">String</span>
<span class="p">}</span>

<span class="kd">struct</span> <span class="kt">ArticlesView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">articles</span><span class="p">:</span> <span class="p">[</span><span class="kt">Article</span><span class="p">]</span>

    <span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="kt">List</span> <span class="p">{</span>
            <span class="kt">ForEachEnumerated</span><span class="p">(</span><span class="n">articles</span><span class="p">,</span> <span class="nv">id</span><span class="p">:</span> <span class="p">\</span><span class="o">.</span><span class="n">title</span><span class="p">)</span> <span class="p">{</span> <span class="n">index</span><span class="p">,</span> <span class="n">article</span> <span class="k">in</span>
                <span class="kt">HStack</span> <span class="p">{</span>
                    <span class="kt">Text</span><span class="p">(</span><span class="s">"</span><span class="se">\(</span><span class="n">index</span><span class="se">)</span><span class="s">"</span> <span class="o">+</span> <span class="n">article</span><span class="o">.</span><span class="n">title</span><span class="p">)</span>
                    <span class="kt">Text</span><span class="p">(</span><span class="n">article</span><span class="o">.</span><span class="n">author</span><span class="p">)</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>]]></content><author><name>Nikita Ermolenko</name></author><category term="SwiftUI" /><summary type="html"><![CDATA[Today I’m faced with a fairly common task - implementing an enumerated ForEach with indexes in SwiftUI. At first it seemed straightforward, and it was, but with some small pitfalls that I want to highlight in this post. So, without further ado, let’s dive into.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://otbivnoe.ru/assets/img/articles/foreach-enumerated/logo.png" /><media:content medium="image" url="https://otbivnoe.ru/assets/img/articles/foreach-enumerated/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Multiline TextField in SwiftUI</title><link href="https://otbivnoe.ru/2022/07/10/Finally-Multiline-TextField-in-SwiftUI.html" rel="alternate" type="text/html" title="Multiline TextField in SwiftUI" /><published>2022-07-10T10:00:00+03:00</published><updated>2022-07-10T10:00:00+03:00</updated><id>https://otbivnoe.ru/2022/07/10/Finally-Multiline-TextField-in-SwiftUI</id><content type="html" xml:base="https://otbivnoe.ru/2022/07/10/Finally-Multiline-TextField-in-SwiftUI.html"><![CDATA[<p>Here it is! How many times did I try to find an <a href="https://stackoverflow.com/search?q=multiline+TextField+SwiftUI">answer</a> on Stackoverflow, uhm? No more custom multiline text fields using <code class="language-plaintext highlighter-rouge">TextEditor</code>. Starting with <strong>iOS 16</strong> we can achieve it using <a href="https://developer.apple.com/documentation/swiftui/textfield">TextField</a> just by adding a new parameter <code class="language-plaintext highlighter-rouge">axis</code>:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">TextField</span><span class="p">(</span><span class="s">"Multiline textfield"</span><span class="p">,</span> <span class="nv">text</span><span class="p">:</span> <span class="err">$</span><span class="n">text</span><span class="p">,</span> <span class="nv">axis</span><span class="p">:</span> <span class="o">.</span><span class="n">vertical</span><span class="p">)</span>
</code></pre></div></div>

<p>This argument controls the scroll direction when the text is large than the text field bounds. Using the <code class="language-plaintext highlighter-rouge">vertical</code> value we get a new-modern multiline text field, but by specifying the <code class="language-plaintext highlighter-rouge">horizontal</code> one we return to the behaviour we are all already get used to.</p>

<p><img class="centered post-img" srcset="/assets/img/articles/multiline-textfield/multiline-textfield-1.gif" alt="" /></p>

<p>See how smooth it works. This text field is not only multiline now, but it automatically expands and shrinks itself on content changes. If endlessly expanded content is not what you expect to see, old <code class="language-plaintext highlighter-rouge">lineLimit</code> with new handy friends are for rescue!</p>

<p>Using this modifier a text field grows until it exceeds the limited number of lines. There’re a few ways to specify how many <strong>visible</strong> text lines you need. Using the <a href="https://developer.apple.com/documentation/swiftui/defaultdateprogresslabel/linelimit(_:)-3c4c9">old one</a> you only configure the maximum number of lines that text can occupy in this view.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// iOS 13+</span>

<span class="kt">TextField</span><span class="p">(</span><span class="s">"Multiline textfield"</span><span class="p">,</span> <span class="nv">text</span><span class="p">:</span> <span class="err">$</span><span class="n">text</span><span class="p">,</span> <span class="nv">axis</span><span class="p">:</span> <span class="o">.</span><span class="n">vertical</span><span class="p">)</span>
    <span class="o">.</span><span class="nf">lineLimit</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
</code></pre></div></div>

<p>Starting with <strong>iOS 16</strong> there’re a few more with the configuration of a minimum and maximum number of lines as well.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// iOS 16+</span>

<span class="kt">TextField</span><span class="p">(</span><span class="s">"Multiline textfield"</span><span class="p">,</span> <span class="nv">text</span><span class="p">:</span> <span class="err">$</span><span class="n">text</span><span class="p">,</span> <span class="nv">axis</span><span class="p">:</span> <span class="o">.</span><span class="n">vertical</span><span class="p">)</span>
    <span class="o">.</span><span class="nf">lineLimit</span><span class="p">(</span><span class="o">...</span><span class="mi">2</span><span class="p">)</span> <span class="c1">// It's identical to usual `lineLimit(2)`</span>
    <span class="o">.</span><span class="nf">lineLimit</span><span class="p">(</span><span class="mi">2</span><span class="o">...</span><span class="p">)</span> <span class="c1">// min=2, max=Int.max</span>
 
<span class="kt">TextField</span><span class="p">(</span><span class="s">"Multiline textfield"</span><span class="p">,</span> <span class="nv">text</span><span class="p">:</span> <span class="err">$</span><span class="n">text</span><span class="p">,</span> <span class="nv">axis</span><span class="p">:</span> <span class="o">.</span><span class="n">vertical</span><span class="p">)</span>
    <span class="o">.</span><span class="nf">lineLimit</span><span class="p">(</span><span class="mi">2</span><span class="o">...</span><span class="mi">3</span><span class="p">)</span> <span class="c1">// min=2, max=3</span>
 
<span class="kt">TextField</span><span class="p">(</span><span class="s">"Multiline textfield"</span><span class="p">,</span> <span class="nv">text</span><span class="p">:</span> <span class="err">$</span><span class="n">text</span><span class="p">,</span> <span class="nv">axis</span><span class="p">:</span> <span class="o">.</span><span class="n">vertical</span><span class="p">)</span>
    <span class="o">.</span><span class="nf">lineLimit</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="nv">reservesSpace</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>  <span class="c1">// = `lineLimit(2...2)`</span>
    <span class="o">.</span><span class="nf">lineLimit</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="nv">reservesSpace</span><span class="p">:</span> <span class="kc">false</span><span class="p">)</span> <span class="c1">// = `lineLimit(2)`</span>
</code></pre></div></div>

<p>Although all these features sound cool, need to remember about iOS 16. So create yourself a reminder about this feature in a few years :)</p>]]></content><author><name>Nikita Ermolenko</name></author><category term="iOS16+," /><category term="SwiftUI" /><summary type="html"><![CDATA[Here it is! How many times did I try to find an answer on Stackoverflow, uhm? No more custom multiline text fields using TextEditor. Starting with iOS 16 we can achieve it using TextField just by adding a new parameter axis:]]></summary></entry><entry><title type="html">Type Eraser for Rx Subjects</title><link href="https://otbivnoe.ru/2021/09/04/Type-Eraser-for-RxSubjects.html" rel="alternate" type="text/html" title="Type Eraser for Rx Subjects" /><published>2021-09-04T20:00:00+03:00</published><updated>2021-09-04T20:00:00+03:00</updated><id>https://otbivnoe.ru/2021/09/04/Type-Eraser-for-RxSubjects</id><content type="html" xml:base="https://otbivnoe.ru/2021/09/04/Type-Eraser-for-RxSubjects.html"><![CDATA[<p>Let’s continue the series of quick articles!</p>

<p>Recently, I’ve had a wish to erase a type of my Subject outside of a class definition. To be clear what I mean I prepared a small class that observes the battery level of my bluetooth device and emits new values on time.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">BatteryObserver</span> <span class="p">{</span>

    <span class="k">let</span> <span class="nv">batterySubject</span> <span class="o">=</span> <span class="kt">ReplaySubject</span><span class="o">&lt;</span><span class="kt">Int</span><span class="o">&gt;.</span><span class="nf">create</span><span class="p">(</span><span class="nv">bufferSize</span><span class="p">:</span> <span class="mi">1</span><span class="p">)</span>

    <span class="kd">func</span> <span class="nf">loadBatteryLevelEveryNSeconds</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// battery level is 55 now - emit this value</span>
        <span class="n">batterySubject</span><span class="o">.</span><span class="nf">onNext</span><span class="p">(</span><span class="mi">55</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>I use <code class="language-plaintext highlighter-rouge">ReplaySubject</code> in my example, and it’d be great to erase this type (convert to <code class="language-plaintext highlighter-rouge">Observable</code>) for the outside definition. I don’t want to bother that someone else could send a battery level by mistake, who knows… Only this class is responsible for emitting new values.</p>

<p>Roger! One more property with the erased type for help:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">BatteryObserver</span> <span class="p">{</span>

    <span class="k">var</span> <span class="nv">batteryObservable</span><span class="p">:</span> <span class="kt">Observable</span><span class="o">&lt;</span><span class="kt">Int</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">batterySubject</span><span class="o">.</span><span class="nf">asObservable</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="kd">private</span> <span class="k">let</span> <span class="nv">batterySubject</span> <span class="o">=</span> <span class="kt">ReplaySubject</span><span class="o">&lt;</span><span class="kt">Int</span><span class="o">&gt;.</span><span class="nf">create</span><span class="p">(</span><span class="nv">bufferSize</span><span class="p">:</span> <span class="mi">1</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Looks solid, does it? I’ve always done this before, but this time I decided to over-engineering a bit - I really tired of this approach by creating two properties every time.</p>

<p>Taking advantage of the relatively new feature - property wrappers, I incapsulated the subject type only for in-class use.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@propertyWrapper</span>
<span class="kd">struct</span> <span class="kt">SubjectEraser</span><span class="o">&lt;</span><span class="kt">Subject</span><span class="o">&gt;</span><span class="p">:</span> <span class="kt">ObserverType</span> <span class="k">where</span> <span class="kt">Subject</span><span class="p">:</span> <span class="kt">SubjectType</span> <span class="p">{</span>

    <span class="kd">typealias</span> <span class="kt">Element</span> <span class="o">=</span> <span class="kt">Subject</span><span class="o">.</span><span class="kt">Observer</span><span class="o">.</span><span class="kt">Element</span>

    <span class="k">var</span> <span class="nv">wrappedValue</span><span class="p">:</span> <span class="kt">Observable</span><span class="o">&lt;</span><span class="kt">Subject</span><span class="o">.</span><span class="kt">Element</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">subject</span><span class="o">.</span><span class="nf">asObservable</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="kd">private</span> <span class="k">let</span> <span class="nv">subject</span><span class="p">:</span> <span class="kt">Subject</span>

    <span class="nf">init</span><span class="p">(</span><span class="nv">subject</span><span class="p">:</span> <span class="kt">Subject</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="n">subject</span> <span class="o">=</span> <span class="n">subject</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">on</span><span class="p">(</span><span class="n">_</span> <span class="nv">event</span><span class="p">:</span> <span class="kt">Event</span><span class="o">&lt;</span><span class="kt">Element</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">subject</span><span class="o">.</span><span class="nf">asObserver</span><span class="p">()</span><span class="o">.</span><span class="nf">on</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="kt">BatteryObserver</span> <span class="p">{</span>

    <span class="kd">@SubjectEraser</span><span class="p">(</span><span class="nv">subject</span><span class="p">:</span> <span class="kt">ReplaySubject</span><span class="o">&lt;</span><span class="kt">Int</span><span class="o">&gt;.</span><span class="nf">create</span><span class="p">(</span><span class="nv">bufferSize</span><span class="p">:</span> <span class="mi">1</span><span class="p">))</span>
    <span class="k">var</span> <span class="nv">batteryObservable</span>

    <span class="kd">func</span> <span class="nf">loadBatteryLevelEveryNSeconds</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// ...</span>
        <span class="c1">// battery level is 55 now - emit this value</span>
        <span class="n">_batteryObservable</span><span class="o">.</span><span class="nf">onNext</span><span class="p">(</span><span class="mi">55</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>With this solution we have a <code class="language-plaintext highlighter-rouge">wrappedValue</code> for outside use - the type is erased and using underscore for getting a property wrapper itself we send values for handling further.</p>]]></content><author><name>Nikita Ermolenko</name></author><summary type="html"><![CDATA[Let’s continue the series of quick articles!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://otbivnoe.ru/assets/img/articles/eraser-subject/logo.png" /><media:content medium="image" url="https://otbivnoe.ru/assets/img/articles/eraser-subject/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">How to clear Xcode console a bit another way</title><link href="https://otbivnoe.ru/2021/03/28/How-to-Clear-Xcode-Console-a-Bit-Another-Way-copy.html" rel="alternate" type="text/html" title="How to clear Xcode console a bit another way" /><published>2021-03-28T19:00:00+03:00</published><updated>2021-03-28T19:00:00+03:00</updated><id>https://otbivnoe.ru/2021/03/28/How-to-Clear-Xcode-Console-a-Bit-Another-Way%20copy</id><content type="html" xml:base="https://otbivnoe.ru/2021/03/28/How-to-Clear-Xcode-Console-a-Bit-Another-Way-copy.html"><![CDATA[<p>I think everyone has had a whole debugging day trying to figure out how this bug was caught by a QA team. Embellishes the fact you spend a lot of time repeating these steps to reproduce the bug even once but the QA team does it regularly. Familiar feeling, isn’t it? We have such days from time to time and I had a similar one recently. I turned out as a detective and started to investigate the problem.</p>

<p>The first step of my investigation was turning on verbose logging. Thereby Xcode’s console started to spam plenty of messages. Yep, it became a mess by far. And since my goal was finding a “key” inside this galaxy of logs, I became clicking the tiny icon “trash” on every lap of my investigation steps to start looking again. On the 25th lap, I opened the StackOverflow trying to find a programmable way to clear the console - without success. On that note, I took off the detective hat to find a way to simplifying the debug workflow a bit.</p>

<hr />

<p>The first thought that visited me - my old friend Apple Script. So I wrote a script in minutes:</p>

<div class="language-applescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">activate</span><span class="w"> </span><span class="nb">application</span><span class="w"> </span><span class="s2">"Xcode"</span><span class="w">

</span><span class="k">tell</span><span class="w"> </span><span class="nb">application</span><span class="w"> </span><span class="s2">"System Events"</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="k">tell</span><span class="w"> </span><span class="nv">process</span><span class="w"> </span><span class="s2">"Xcode"</span><span class="w">
	</span><span class="nv">click</span><span class="w"> </span><span class="na">menu</span><span class="w"> </span><span class="nb">item</span><span class="w"> </span><span class="s2">"Clear Console"</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="na">menu</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="na">menu</span><span class="w"> </span><span class="nb">item</span><span class="w"> </span><span class="s2">"Debug Workflow"</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="na">menu</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="na">menu</span><span class="w"> </span><span class="nv">bar</span><span class="w"> </span><span class="nb">item</span><span class="w"> </span><span class="s2">"Debug"</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="na">menu</span><span class="w"> </span><span class="nv">bar</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span><span class="k">end</span><span class="w"> </span><span class="k">tell</span><span class="w">
</span></code></pre></div></div>

<p>The longest line of this code just invokes the <em>“Clear Console”</em> command from the Xcode’s menu: <em>Debug &gt; Debug Workflow &gt; Clear Console.</em></p>

<p>Of course, it does look nicer using a shortcut invocation from the script, but I think it’s less safe.</p>

<div class="language-applescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">keystroke</span><span class="w"> </span><span class="s2">"K"</span><span class="w"> </span><span class="nv">using</span><span class="w"> </span><span class="p">{</span><span class="nv">command</span><span class="w"> </span><span class="nv">down</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Now that we have the worked script, need to find a way to invoke it right from Xcode. The handy thing you may not have heard, it’s the ability to invoke an apple script right from a breakpoint: <em>(Right-click on any breakpoint) &gt; Edit breakpoint &gt; Add Action &gt; Chouse ‘Apple Script’ from dropdown &gt; (paste our script):</em></p>

<p><img class="centered post-img" srcset="/assets/img/articles/xcode-clear/breakpoint-aplescript.png" alt="" /></p>

<p>At this step, I could get back to the initial problem, but the current solution doesn’t look elegant as could be. What if I decide to repeat this code for another breakpoint? You know for sure that developers are lazy people, so we don’t like to repeat code and maintain its consistency across many places in our codebase. Thus I went further!</p>

<p>Ideally is to have a simple <a href="https://lldb.llvm.org/use/python-reference.html#create-a-new-lldb-command-using-a-python-function">LLDB</a> command - <code class="language-plaintext highlighter-rouge">clear</code> for instance. From my experience, a developer is able to write a LLDB command on his own. So if you haven’t heard about this feature I recommend to read <a href="https://www.ryanipete.com/blog/lldb/python/how_to_create_a_custom_lldb_command_pt_1/">this simple article</a> that covers the basics.</p>

<h4 id="steps">Steps:</h4>

<ul>
  <li>At first check the existence of the <code class="language-plaintext highlighter-rouge">~/.lldbinit</code> file (create if doesn’t exist). It’s the file that LLDB looks for and loads each time a new session is started.</li>
  <li>Then we need to import our further Python script that clears the Xcode’s console. Paste this code inside the <code class="language-plaintext highlighter-rouge">.lldbinit</code> file:</li>
</ul>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">command</span> <span class="n">script</span> <span class="k">import</span> <span class="o">~/</span><span class="p">.</span><span class="n">lldb</span><span class="o">/</span><span class="n">otbivnoe</span><span class="p">.</span><span class="n">py</span> <span class="c1"># you can choose any directory for your script
</span></code></pre></div></div>

<ul>
  <li>Final part is finally to write the magic script:</li>
</ul>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env python
</span>
<span class="kn">from</span> <span class="nn">subprocess</span> <span class="kn">import</span> <span class="n">Popen</span><span class="p">,</span> <span class="n">PIPE</span>

<span class="k">def</span> <span class="nf">clearXcodeConsole</span><span class="p">(</span><span class="n">debugger</span><span class="p">,</span> <span class="n">command</span><span class="p">,</span> <span class="n">result</span><span class="p">,</span> <span class="n">internal_dict</span><span class="p">):</span>
  <span class="n">applescript</span><span class="o">=</span><span class="s">'''
    activate application "Xcode"
    tell application "System Events" to tell process "Xcode"
      click menu item "Clear Console" of menu 1 of menu item "Debug Workflow" of menu 1 of menu bar item "Debug" of menu bar 1
    end tell
  '''</span>

  <span class="n">p</span> <span class="o">=</span> <span class="n">Popen</span><span class="p">([</span><span class="s">'osascript'</span><span class="p">,</span> <span class="s">'-'</span><span class="p">],</span> <span class="n">stdin</span><span class="o">=</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">universal_newlines</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
  <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span> <span class="o">=</span> <span class="n">p</span><span class="p">.</span><span class="n">communicate</span><span class="p">(</span><span class="n">applescript</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">__lldb_init_module</span><span class="p">(</span><span class="n">debugger</span><span class="p">,</span> <span class="n">internal_dict</span><span class="p">):</span>	
  <span class="n">debugger</span><span class="p">.</span><span class="n">HandleCommand</span><span class="p">(</span><span class="s">'command script add -f otbivnoe.clearXcodeConsole clear'</span><span class="p">)</span>
</code></pre></div></div>

<p>Be aware I have the following version of python. Maybe you need to tune this script a bit by yourself.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>3.8.2 <span class="o">(</span>default, Dec 21 2020, 15:06:04<span class="o">)</span>
<span class="o">[</span>Clang 12.0.0 <span class="o">(</span>clang-1200.0.32.29<span class="o">)]</span>
</code></pre></div></div>

<ul>
  <li>Restart your LLDB debugging session or just reload the current one:</li>
</ul>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">command</span> <span class="n">source</span> <span class="o">~/</span><span class="p">.</span><span class="n">lldbinit</span>
</code></pre></div></div>

<p>That’s It! Now you’re able to write a simple command <code class="language-plaintext highlighter-rouge">clear</code> to flush the console. And I go back to detective duties :)</p>

<hr />

<h3 id="additional-recourses">Additional Recourses:</h3>

<ul>
  <li><a href="https://github.com/facebook/chisel">Chisel</a> - a ton of useful LLDB commands by Facebook</li>
  <li><a href="https://github.com/bangerang/lowmad">Lowmad</a> - a command line tool for managing scripts and configurations in LLDB.</li>
</ul>

<hr />

<p>I would happy to hear a better way or even a native way of clearing a console if it exists. So <a href="https://x.com/otbivnoe">let me know</a> if you know anything about it.</p>]]></content><author><name>Nikita Ermolenko</name></author><summary type="html"><![CDATA[I think everyone has had a whole debugging day trying to figure out how this bug was caught by a QA team. Embellishes the fact you spend a lot of time repeating these steps to reproduce the bug even once but the QA team does it regularly. Familiar feeling, isn’t it? We have such days from time to time and I had a similar one recently. I turned out as a detective and started to investigate the problem.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://otbivnoe.ru/assets/img/articles/xcode-clear/logo.png" /><media:content medium="image" url="https://otbivnoe.ru/assets/img/articles/xcode-clear/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Dependency Injection for Property Wrappers</title><link href="https://otbivnoe.ru/2021/03/13/Dependency-Injection-for-Property-Wrappers.html" rel="alternate" type="text/html" title="Dependency Injection for Property Wrappers" /><published>2021-03-13T22:50:00+03:00</published><updated>2021-03-13T22:50:00+03:00</updated><id>https://otbivnoe.ru/2021/03/13/Dependency-Injection-for-Property-Wrappers</id><content type="html" xml:base="https://otbivnoe.ru/2021/03/13/Dependency-Injection-for-Property-Wrappers.html"><![CDATA[<p>My past sprint was nothing special. I had to implement an onboarding flow with saving a completion flag so that a user can see this for the first time only. Sounds like a typical work for iOS developers for saving something in UserDefaults, doesn’t it? So, I prepared a special class with the most popular (IMHO) property wrapper inside:</p>

<p><em>If you haven’t heard about Property Wrappers so far, I recommend reading <a href="https://nshipster.com/propertywrapper/">this article</a> first.</em></p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@propertyWrapper</span>
<span class="kd">struct</span> <span class="kt">UserDefault</span><span class="o">&lt;</span><span class="kt">Value</span><span class="o">&gt;</span> <span class="p">{</span>

    <span class="kd">private</span> <span class="k">let</span> <span class="nv">key</span><span class="p">:</span> <span class="kt">String</span>
    <span class="kd">private</span> <span class="k">let</span> <span class="nv">defaultValue</span><span class="p">:</span> <span class="kt">Value</span>
    <span class="kd">private</span> <span class="k">let</span> <span class="nv">storage</span><span class="p">:</span> <span class="kt">UserDefaults</span>

    <span class="k">var</span> <span class="nv">wrappedValue</span><span class="p">:</span> <span class="kt">Value</span> <span class="p">{</span>
        <span class="k">get</span> <span class="p">{</span>
            <span class="k">return</span> <span class="n">storage</span><span class="o">.</span><span class="nf">object</span><span class="p">(</span><span class="nv">forKey</span><span class="p">:</span> <span class="n">key</span><span class="p">)</span> <span class="k">as?</span> <span class="kt">Value</span> <span class="p">??</span> <span class="n">defaultValue</span>
        <span class="p">}</span>
        <span class="k">set</span> <span class="p">{</span>
            <span class="n">storage</span><span class="o">.</span><span class="nf">set</span><span class="p">(</span><span class="n">newValue</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="n">key</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="nf">init</span><span class="p">(</span><span class="n">defaultValue</span><span class="p">:</span> <span class="kt">Value</span><span class="p">,</span> <span class="nv">key</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="nv">storage</span><span class="p">:</span> <span class="kt">UserDefaults</span> <span class="o">=</span> <span class="o">.</span><span class="n">standard</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="n">defaultValue</span> <span class="o">=</span> <span class="n">defaultValue</span>
        <span class="k">self</span><span class="o">.</span><span class="n">key</span> <span class="o">=</span> <span class="n">key</span>
        <span class="k">self</span><span class="o">.</span><span class="n">storage</span> <span class="o">=</span> <span class="n">storage</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="kt">Onboarding</span> <span class="p">{</span>

    <span class="kd">@UserDefault</span><span class="p">(</span><span class="n">defaultValue</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nv">key</span><span class="p">:</span> <span class="s">"isOnboardingCompleted"</span><span class="p">)</span>
    <span class="k">var</span> <span class="nv">isOnboardingCompleted</span><span class="p">:</span> <span class="kt">Bool</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Done, looks as simple as possible. The second step is to cover this code by tests. In order to run tests safely - to have stable tests, I decided to pass different domains of User Defaults. So the first code I wrote was the following one:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">Onboarding</span> <span class="p">{</span>

    <span class="kd">@UserDefault</span><span class="p">(</span><span class="n">defaultValue</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nv">key</span><span class="p">:</span> <span class="s">"isOnboardingCompleted"</span><span class="p">,</span> <span class="nv">storage</span><span class="p">:</span> <span class="n">storage</span><span class="p">)</span>
    <span class="k">var</span> <span class="nv">isOnboardingCompleted</span><span class="p">:</span> <span class="kt">Bool</span>

    <span class="kd">private</span> <span class="k">let</span> <span class="nv">storage</span><span class="p">:</span> <span class="kt">UserDefaults</span>

    <span class="nf">init</span><span class="p">(</span><span class="nv">storage</span><span class="p">:</span> <span class="kt">UserDefaults</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="n">storage</span> <span class="o">=</span> <span class="n">storage</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Obviously it didn’t work. The red error was flashing to my eyes:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">Cannot</span> <span class="n">use</span> <span class="n">instance</span> <span class="n">member</span> <span class="err">'</span><span class="n">storage</span><span class="err">'</span> <span class="n">within</span> <span class="n">property</span> <span class="n">initializer</span><span class="p">;</span> <span class="n">property</span> <span class="n">initializers</span> <span class="n">run</span> <span class="n">before</span> <span class="err">'</span><span class="k">self</span><span class="err">'</span> <span class="k">is</span> <span class="n">available</span>
</code></pre></div></div>

<p>After that I used to try a few approaches to solve this issue without luck until I remembered one important thing: using an underscore within a class allowing to access a property wrapper class itself rather than a general property.</p>

<p>The final solution:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">Onboarding</span> <span class="p">{</span>

    <span class="kd">@UserDefault</span>
    <span class="k">var</span> <span class="nv">isOnboardingCompleted</span><span class="p">:</span> <span class="kt">Bool</span>

    <span class="nf">init</span><span class="p">(</span><span class="nv">storage</span><span class="p">:</span> <span class="kt">UserDefaults</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">_isOnboardingCompleted</span> <span class="o">=</span> <span class="kt">UserDefault</span><span class="p">(</span><span class="n">defaultValue</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nv">key</span><span class="p">:</span> <span class="s">"isOnboardingCompleted"</span><span class="p">,</span> <span class="nv">storage</span><span class="p">:</span> <span class="n">storage</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<p>This article doesn’t look like a long step-by-step tutorial, it’s just a quick tip for a problem I faced recently. So I decided to share a non-obvious solution for me, maybe it will save some time for someone. <a href="https://x.com/otbivnoe">Let me know</a> if the article was useful to you :)</p>]]></content><author><name>Nikita Ermolenko</name></author><summary type="html"><![CDATA[My past sprint was nothing special. I had to implement an onboarding flow with saving a completion flag so that a user can see this for the first time only. Sounds like a typical work for iOS developers for saving something in UserDefaults, doesn’t it? So, I prepared a special class with the most popular (IMHO) property wrapper inside:]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://otbivnoe.ru/assets/img/articles/property-wrapper-di/logo.png" /><media:content medium="image" url="https://otbivnoe.ru/assets/img/articles/property-wrapper-di/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Make friends UITableView with layout margins</title><link href="https://otbivnoe.ru/2021/02/18/Make-Friends-UITableView-with-Layout-Margins.html" rel="alternate" type="text/html" title="Make friends UITableView with layout margins" /><published>2021-02-18T19:00:00+03:00</published><updated>2021-02-18T19:00:00+03:00</updated><id>https://otbivnoe.ru/2021/02/18/Make-Friends-UITableView-with-Layout-Margins</id><content type="html" xml:base="https://otbivnoe.ru/2021/02/18/Make-Friends-UITableView-with-Layout-Margins.html"><![CDATA[<p>It will not surpise you that most iOS applications tend to have scrolling-style screens. Have a look at some of the screenshots I’ve prepared below - all of which contain scrollable content. One more similarity is layout margins, notice that all content is aligned to the left with the same inset - 16pt. It’s important to keep this value consistent across the whole screen. I’ve tried and heard about many different approaches. Someone can use a base cell class with a container adjusted to a needed value. Other developers don’t bother with this method and use a global constant for horizontal pinning instead. I’ve even seen an adjusted tableview’s origin.x value, but a scroll area suffers this way…</p>

<p>Today I’m going to talk about another approach - using layout margins! We will configure a particular inset value for a root view only and then pin all subviews to this guide.</p>

<p><strong>If you haven’t heard about layout margins in-depth before, I highly recommend <a href="https://blog.smartnsoft.com/layout-guide-margins-insets-and-safe-area-demystified-on-ios-10-11-d6e7246d7cb8">this detailed article</a> before reading further.</strong></p>

<p><img class="centered post-img" srcset="/assets/img/articles/layout-margins/apps.png" alt="" /></p>

<hr />

<p>If we can agree, that 16pt is a lot for our app as a horizontal margin, then perpahs 10pt is a great value that we’re going to work with.</p>

<p>Our first step is disabling <code class="language-plaintext highlighter-rouge">viewRespectsSystemMinimumLayoutMargins</code> since 10pt is less than 16pt (the default system margin).</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">viewRespectsSystemMinimumLayoutMargins</span> <span class="o">=</span> <span class="kc">false</span>
</code></pre></div></div>

<p><em>When the value of this property is true (default), the root view’s layout margins are <strong>guaranteed to be no smaller than the values in the systemMinimumLayoutMargins property</strong>. Changing this property to false causes the view to obtain its margins solely from its directionalLayoutMargins property. Setting the margins in that property to 0 allows you to eliminate the view’s margins altogether.</em></p>

<p>Then we configure <code class="language-plaintext highlighter-rouge">directionalLayoutMargins</code> to correct and obtain our margin values:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">view</span><span class="o">.</span><span class="n">directionalLayoutMargins</span> <span class="o">=</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">top</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="nv">leading</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span> <span class="nv">bottom</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="nv">trailing</span><span class="p">:</span> <span class="mi">10</span><span class="p">)</span>
</code></pre></div></div>

<p>I’ve prepared two screenshots to better understand the differences between these options:</p>

<p><img class="centered post-img" srcset="/assets/img/articles/layout-margins/viewRespects.png" alt="" /></p>

<p>Go over to the second step - configuring a table view. Since the table view has its own margins, it is necessary to preserve the root view’s margins. We need to set these properties:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tableView</span><span class="o">.</span><span class="n">preservesSuperviewLayoutMargins</span> <span class="o">=</span> <span class="kc">true</span>
<span class="n">tableView</span><span class="o">.</span><span class="n">directionalLayoutMargins</span> <span class="o">=</span> <span class="o">.</span><span class="n">zero</span>
</code></pre></div></div>

<p><em>When the value of this property is true, <strong>the superview’s margins are also considered when laying out content. This margin affects layouts where the distance between the edge of a view and its superview is smaller than the corresponding margin.</strong> For example, you might have a content view whose frame precisely matches the bounds of its superview. When any of the superview’s margins is inside the area represented by the content view and its own margins, UIKit adjusts the content view’s layout to respect the superview’s margins. The amount of the adjustment is the smallest amount needed to ensure that content is also inside the superview’s margins.</em></p>

<p>The last step you need is to configure layout in conformity with cell’s margins:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">label</span><span class="o">.</span><span class="n">leadingAnchor</span><span class="o">.</span><span class="nf">constraint</span><span class="p">(</span><span class="nv">equalTo</span><span class="p">:</span> <span class="n">contentView</span><span class="o">.</span><span class="n">layoutMarginsGuide</span><span class="o">.</span><span class="n">leadingAnchor</span><span class="p">)</span><span class="o">.</span><span class="n">isActive</span> <span class="o">=</span> <span class="kc">true</span>
</code></pre></div></div>

<p>That’s it! Now you only need to change the layout margins of the root’s view and all subviews will be in sync automatically in the case of correct configuration of course! I also prepared a <a href="https://github.com/Otbivnoe/LayoutMargins">demo project</a> for fast cloning and playing at your side. Your welcome!</p>

<p>It is important to mention the layout margins approach is applicable for <strong>UIStackView</strong> and <strong>UICollectionView</strong> as well! For stack view, there’s a <code class="language-plaintext highlighter-rouge">isLayoutMarginsRelativeArrangement</code> for help with <a href="https://useyourloaf.com/blog/adding-padding-to-a-stack-view/">useful article</a> and in a collection view, this can be achieved by adjusting section insets or using the similar way as described in the article.</p>

<hr />

<p>I hope it was worth reading for you and you will consider this approach when you start coding a new screen :)</p>]]></content><author><name>Nikita Ermolenko</name></author><summary type="html"><![CDATA[It will not surpise you that most iOS applications tend to have scrolling-style screens. Have a look at some of the screenshots I’ve prepared below - all of which contain scrollable content. One more similarity is layout margins, notice that all content is aligned to the left with the same inset - 16pt. It’s important to keep this value consistent across the whole screen. I’ve tried and heard about many different approaches. Someone can use a base cell class with a container adjusted to a needed value. Other developers don’t bother with this method and use a global constant for horizontal pinning instead. I’ve even seen an adjusted tableview’s origin.x value, but a scroll area suffers this way…]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://otbivnoe.ru/assets/img/articles/layout-margins/logo.png" /><media:content medium="image" url="https://otbivnoe.ru/assets/img/articles/layout-margins/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>