<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.3">Jekyll</generator><link href="http://localhost:4000/feed.xml" rel="self" type="application/atom+xml" /><link href="http://localhost:4000/" rel="alternate" type="text/html" /><updated>2025-01-11T19:56:24+01:00</updated><id>http://localhost:4000/feed.xml</id><title type="html">Ardent Swift</title><subtitle>Blog mainly about Swift, iOS, macOS, watchOS and visionOS programming!</subtitle><author><name>Daniel Arden</name><email>me@danielarden.com</email></author><entry><title type="html">Stellar SwiftUI Performance Part 1 - View Struct is your friend</title><link href="http://localhost:4000/posts/spt-view-struct-is-your-friend/" rel="alternate" type="text/html" title="Stellar SwiftUI Performance Part 1 - View Struct is your friend" /><published>2025-01-11T00:00:00+01:00</published><updated>2025-01-11T00:00:00+01:00</updated><id>http://localhost:4000/posts/spt-view-struct-is-your-friend</id><content type="html" xml:base="http://localhost:4000/posts/spt-view-struct-is-your-friend/"><![CDATA[<blockquote>
  <p>I intentionally omit the part where I make excuses for defaulting on my ambition to post articles every 2 weeks, since I didn’t post anything for 4 months.</p>

  <p>Oh wait…</p>
</blockquote>

<h3 id="whats-the-point-of-this">What’s the point of this?</h3>
<p>In a more complex SwiftUI Apps, as the Views get more heavy, the SwiftUI performance can quickly get out of hand. While the latest Apple devices are blazingly fast, we should still strive to not waste computation and power resources for unnecessary operations. And above all, I suppose pretty much everyone wants their App to run smoothly so that it feels just right. Let’s jump right into the next performance tip.</p>

<h3 id="view-struct-is-your-friend">View Struct is your friend</h3>
<h4 id="the-problem">The problem</h4>
<p>After creating small View components I would write a bigger SwiftUI View that kinda is a <code class="language-plaintext highlighter-rouge">UIViewController</code> or <code class="language-plaintext highlighter-rouge">NSViewController</code>, and add name suffix - Screen. This Screen (heavily simplified) would usually look something like this:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@MainActor</span>
<span class="kd">final</span> <span class="kd">class</span> <span class="kt">LoginScreenModel</span><span class="p">:</span> <span class="kt">ObservableObject</span> <span class="p">{</span>

    <span class="kd">@Published</span>
    <span class="k">var</span> <span class="nv">username</span><span class="p">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">""</span>

    <span class="kd">@Published</span>
    <span class="k">var</span> <span class="nv">password</span><span class="p">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">""</span>
<span class="p">}</span>

<span class="kd">struct</span> <span class="kt">LoginScreen</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>

    <span class="kd">@StateObject</span>
    <span class="kd">private</span> <span class="k">var</span> <span class="nv">model</span><span class="p">:</span> <span class="kt">LoginScreenModel</span> <span class="o">=</span> <span class="o">.</span><span class="nf">init</span><span class="p">()</span>

    <span class="kd">@State</span>
    <span class="kd">private</span> <span class="k">var</span> <span class="nv">isAlertPresented</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">false</span> <span class="c1">// unused, just an example</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">VStack</span> <span class="p">{</span>
            <span class="n">usernameField</span>
            <span class="n">passwordField</span>

            <span class="kt">Spacer</span><span class="p">()</span>

            <span class="n">loginButton</span>
        <span class="p">}</span>
        <span class="o">.</span><span class="nf">padding</span><span class="p">()</span>
        <span class="o">.</span><span class="n">toolbar</span> <span class="p">{</span> <span class="n">toolbarView</span> <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">private</span> <span class="kd">extension</span> <span class="kt">LoginScreen</span> <span class="p">{</span>

    <span class="k">var</span> <span class="nv">toolbarView</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="kt">Button</span> <span class="p">{</span>

        <span class="p">}</span> <span class="nv">label</span><span class="p">:</span> <span class="p">{</span>
            <span class="kt">Image</span><span class="p">(</span><span class="nv">systemName</span><span class="p">:</span> <span class="s">"xmark"</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">var</span> <span class="nv">usernameField</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="kt">TextField</span><span class="p">(</span><span class="s">"User name"</span><span class="p">,</span> <span class="nv">text</span><span class="p">:</span> <span class="n">$model</span><span class="o">.</span><span class="n">username</span><span class="p">)</span>
            <span class="o">.</span><span class="nf">padding</span><span class="p">()</span>
            <span class="o">.</span><span class="nf">background</span><span class="p">(</span><span class="o">.</span><span class="n">thinMaterial</span><span class="p">)</span>
            <span class="o">.</span><span class="nf">clipShape</span><span class="p">(</span><span class="kt">RoundedRectangle</span><span class="p">(</span><span class="nv">cornerRadius</span><span class="p">:</span> <span class="mi">12</span><span class="p">))</span>
    <span class="p">}</span>

    <span class="k">var</span> <span class="nv">passwordField</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="kt">SecureField</span><span class="p">(</span><span class="s">"Password"</span><span class="p">,</span> <span class="nv">text</span><span class="p">:</span> <span class="n">$model</span><span class="o">.</span><span class="n">password</span><span class="p">)</span>
            <span class="o">.</span><span class="nf">padding</span><span class="p">()</span>
            <span class="o">.</span><span class="nf">background</span><span class="p">(</span><span class="o">.</span><span class="n">thinMaterial</span><span class="p">)</span>
            <span class="o">.</span><span class="nf">clipShape</span><span class="p">(</span><span class="kt">RoundedRectangle</span><span class="p">(</span><span class="nv">cornerRadius</span><span class="p">:</span> <span class="mi">12</span><span class="p">))</span>
    <span class="p">}</span>

    <span class="k">var</span> <span class="nv">loginButton</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
        <span class="kt">Button</span><span class="p">(</span><span class="s">"Login"</span><span class="p">)</span> <span class="p">{</span>
            <span class="c1">// Login logic</span>
        <span class="p">}</span>
        <span class="o">.</span><span class="nf">buttonStyle</span><span class="p">(</span><span class="o">.</span><span class="n">borderedProminent</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>I always thought that once <code class="language-plaintext highlighter-rouge">LoginScreenModel</code>’s username property changes, it would trigger SwiftUI View diffing but only for the <code class="language-plaintext highlighter-rouge">usernameField</code> computed property, since it is the only place where <code class="language-plaintext highlighter-rouge">model.username</code> is used. But <strong>that’s not true</strong>, as I learned thanks to Instruments.</p>

<p>Whenever any <code class="language-plaintext highlighter-rouge">StateObject</code>’s or <code class="language-plaintext highlighter-rouge">State</code> property value changes, the whole <code class="language-plaintext highlighter-rouge">var body: some View</code> has to perform diffing and check for changes, but so do all computed properties and the computed properties’ subviews until it reaches another View struct that doesn’t depend on our LoginScreen’s State properties.</p>

<p><em>Keep in mind: Even though properties unrelated to the subview in question don’t trigger the actual redraw (on a CoreAnimation/CoreGraphics level), the SwiftUI diffing itself is pretty expensive, especially since it always runs on the MainActor.</em></p>

<p>We can verify this by adding this handy <code class="language-plaintext highlighter-rouge">Self._printChanges()</code> magic spice like so:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">var</span> <span class="nv">usernameField</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">_</span> <span class="o">=</span> <span class="k">Self</span><span class="o">.</span><span class="nf">_printChanges</span><span class="p">()</span>
    
    <span class="o">...</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now if you start typing into the password’s SecureField, thus changing the <code class="language-plaintext highlighter-rouge">model.password</code>, property on the <code class="language-plaintext highlighter-rouge">LoginScreenModel</code>, the console would output something like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>LoginScreen: _model changed.
</code></pre></div></div>
<p>But of course in the case of an unrelated property, we don’t really need SwiftUI to even recompute the property to perform the diffing.</p>

<h4 id="the-solution">The Solution</h4>
<p>What even Apple recommends is to break the Views not into computed properties, but separate structs. If we do decide to keep our <code class="language-plaintext highlighter-rouge">usernameField</code> computed property for <code class="language-plaintext highlighter-rouge">var body: some View</code>’s readability’s sake, we should create a separate struct like so:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fileprivate</span> <span class="kd">struct</span> <span class="kt">UsernameFieldView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>

    <span class="kd">@Binding</span>
    <span class="k">var</span> <span class="nv">text</span><span class="p">:</span> <span class="kt">String</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">_</span> <span class="o">=</span> <span class="k">Self</span><span class="o">.</span><span class="nf">_printChanges</span><span class="p">()</span>

        <span class="kt">TextField</span><span class="p">(</span><span class="s">"User name"</span><span class="p">,</span> <span class="nv">text</span><span class="p">:</span> <span class="n">$text</span><span class="p">)</span>
            <span class="o">.</span><span class="nf">padding</span><span class="p">()</span>
            <span class="o">.</span><span class="nf">background</span><span class="p">(</span><span class="o">.</span><span class="n">thinMaterial</span><span class="p">)</span>
            <span class="o">.</span><span class="nf">clipShape</span><span class="p">(</span><span class="kt">RoundedRectangle</span><span class="p">(</span><span class="nv">cornerRadius</span><span class="p">:</span> <span class="mi">12</span><span class="p">))</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>and then refactor our computed property on the <code class="language-plaintext highlighter-rouge">LoginScreen</code>:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">var</span> <span class="nv">usernameField</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
    <span class="kt">UsernameFieldView</span><span class="p">(</span><span class="nv">text</span><span class="p">:</span> <span class="n">$model</span><span class="o">.</span><span class="n">username</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now when you type into the password’s SecureField, the <code class="language-plaintext highlighter-rouge">let _ = Self._printChanges()</code> inside of the <code class="language-plaintext highlighter-rouge">UsernameFieldView</code> will not print anything. In our case this means that SwiftUI is not even recomputing the whole contents of <code class="language-plaintext highlighter-rouge">UsernameFieldView</code> since it knows that it only depends on the <code class="language-plaintext highlighter-rouge">model.username</code> and that hasn’t changed.</p>

<h3 id="conclusion">Conclusion</h3>
<p>It’s embarassing, but after 3 years of working with SwiftUI this is something I learned just recently. As a good practice it’s probably to write it in a way that doesn’t do the unnecessary diffing straight away, but if your App’s performance is good enough and the Views are rather not complex, then there is no need to fall into the premature optimization trap 🪤. It’s all about striking the right balance.</p>

<p>Happy coding and stay tuned for more SwiftUI tips, (hopefully) coming up next week! 💻</p>]]></content><author><name>Daniel Arden</name><email>me@danielarden.com</email></author><category term="SwiftUI" /><category term="SwiftUI Performance" /><summary type="html"><![CDATA[First post in the SwiftUI Performance series helping you make your Apps blazing fast - and efficient!]]></summary></entry><entry><title type="html">How to programatically resolve dynamic Dark and Light Colors</title><link href="http://localhost:4000/posts/dynamic-color/" rel="alternate" type="text/html" title="How to programatically resolve dynamic Dark and Light Colors" /><published>2024-09-16T00:00:00+02:00</published><updated>2024-09-16T00:00:00+02:00</updated><id>http://localhost:4000/posts/dynamic-color</id><content type="html" xml:base="http://localhost:4000/posts/dynamic-color/"><![CDATA[<p>In this article we will take a look at how to programatically resolve dynamic colors from Hex values.</p>

<h3 id="what-we-want-to-achieve">What we want to achieve</h3>
<p><img src="../../assets/images/dynamic-color-showcase.png" alt="Dynamic Color Showcase" /></p>

<p>If for some reason such as building a Color-configurable Theme Library or UI Library we can’t use the Xcode Asset Catalog, we will either need to create <code class="language-plaintext highlighter-rouge">UIColor</code>, <code class="language-plaintext highlighter-rouge">NSColor</code> and <code class="language-plaintext highlighter-rouge">SwiftUI.Color</code> with RGBA components, or, as often practiced, write an extension on <code class="language-plaintext highlighter-rouge">UIColor</code>, <code class="language-plaintext highlighter-rouge">NSColor</code> and <code class="language-plaintext highlighter-rouge">SwiftUI.Color</code> that allows us to initialize it from a Hex value.</p>

<p>However such a color doesn’t respect Light or Dark Mode and it can become a bit messy to always resolve two color versions in your View and read the <code class="language-plaintext highlighter-rouge">UITraitCollection</code> or <code class="language-plaintext highlighter-rouge">ColorScheme</code> environment.</p>

<p>But don’t worry, there is a simple solution to that, if we are willing to have some boilerplate!</p>

<h3 id="the-approach">The approach</h3>
<p>In case you need to support both macOS and iOS, let’s start by defining Platform-agnostic typealiases for the <code class="language-plaintext highlighter-rouge">UIColor</code> and <code class="language-plaintext highlighter-rouge">NSColor</code>. While we can initialize a <code class="language-plaintext highlighter-rouge">SwiftUI.Color</code> directly and we don’t have to deal with <code class="language-plaintext highlighter-rouge">UIColor</code> and <code class="language-plaintext highlighter-rouge">NSColor</code>, we will need this to be able to dynamically resolve Dark and Light mode version of our colors as if they came from the Asset Catalog.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">import</span> <span class="kt">SwiftUI</span>

<span class="cp">#if canImport(AppKit)</span>
    <span class="kd">import</span> <span class="kt">AppKit</span>

    <span class="kd">typealias</span> <span class="kt">PlatformColor</span> <span class="o">=</span> <span class="kt">NSColor</span>
<span class="cp">#elseif canImport(UIKit)</span>
    <span class="kd">import</span> <span class="kt">UIKit</span>

    <span class="kd">typealias</span> <span class="kt">PlatformColor</span> <span class="o">=</span> <span class="kt">UIColor</span>
<span class="cp">#endif</span>
</code></pre></div></div>
<p>This will make sure that our dynamic color resolution works with both AppKit and UIKit.</p>

<p>Next, we need to add an extension on our new <code class="language-plaintext highlighter-rouge">PlatformColor</code>, so that we can initialize it from a Hex value</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">PlatformColor</span> <span class="p">{</span>

    <span class="c1">/// Initializes the PlatformColor with Hex String</span>
    <span class="c1">/// - RGB (12-bit), ex. "FFF" for pure white</span>
    <span class="c1">/// - RGB (24-bit), ex. "FFFFFF" for pure white</span>
    <span class="c1">///</span>
    <span class="c1">/// - Parameters:</span>
    <span class="c1">///   - hex: Hex description of the color without the alpha channel</span>
    <span class="c1">///   - alpha: Alpha/opacity to apply to the color, `by default 1.0`. The values have to be between 0.0 and 1.0</span>
    <span class="kd">convenience</span> <span class="nf">init</span><span class="p">(</span><span class="nv">hex</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="nv">alpha</span><span class="p">:</span> <span class="kt">Double</span> <span class="o">=</span> <span class="mf">1.0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">hex</span> <span class="o">=</span> <span class="n">hex</span><span class="o">.</span><span class="nf">trimmingCharacters</span><span class="p">(</span><span class="nv">in</span><span class="p">:</span> <span class="kt">CharacterSet</span><span class="o">.</span><span class="n">alphanumerics</span><span class="o">.</span><span class="n">inverted</span><span class="p">)</span>
        <span class="k">var</span> <span class="nv">int</span><span class="p">:</span> <span class="kt">UInt64</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="kt">Scanner</span><span class="p">(</span><span class="nv">string</span><span class="p">:</span> <span class="n">hex</span><span class="p">)</span><span class="o">.</span><span class="nf">scanHexInt64</span><span class="p">(</span><span class="o">&amp;</span><span class="n">int</span><span class="p">)</span>

        <span class="k">let</span> <span class="nv">r</span><span class="p">,</span> <span class="n">g</span><span class="p">,</span> <span class="nv">b</span><span class="p">:</span> <span class="kt">UInt64</span>
        <span class="k">switch</span> <span class="n">hex</span><span class="o">.</span><span class="n">count</span> <span class="p">{</span>
        <span class="k">case</span> <span class="mi">3</span><span class="p">:</span> <span class="c1">// RGB (12-bit)</span>
            <span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">g</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span> <span class="o">=</span> <span class="p">((</span><span class="n">int</span> <span class="o">&gt;&gt;</span> <span class="mi">8</span><span class="p">)</span> <span class="o">*</span> <span class="mi">17</span><span class="p">,</span> <span class="p">(</span><span class="n">int</span> <span class="o">&gt;&gt;</span> <span class="mi">4</span> <span class="o">&amp;</span> <span class="mh">0xF</span><span class="p">)</span> <span class="o">*</span> <span class="mi">17</span><span class="p">,</span> <span class="p">(</span><span class="n">int</span> <span class="o">&amp;</span> <span class="mh">0xF</span><span class="p">)</span> <span class="o">*</span> <span class="mi">17</span><span class="p">)</span>
        <span class="k">case</span> <span class="mi">6</span><span class="p">:</span> <span class="c1">// RGB (24-bit)</span>
            <span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">g</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="n">int</span> <span class="o">&gt;&gt;</span> <span class="mi">16</span><span class="p">,</span> <span class="n">int</span> <span class="o">&gt;&gt;</span> <span class="mi">8</span> <span class="o">&amp;</span> <span class="mh">0xFF</span><span class="p">,</span> <span class="n">int</span> <span class="o">&amp;</span> <span class="mh">0xFF</span><span class="p">)</span>
        <span class="k">default</span><span class="p">:</span>
            <span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">g</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span> <span class="o">=</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</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="nv">displayP3Red</span><span class="p">:</span> <span class="kt">Double</span><span class="p">(</span><span class="n">r</span><span class="p">)</span> <span class="o">/</span> <span class="mi">255</span><span class="p">,</span>
            <span class="nv">green</span><span class="p">:</span> <span class="kt">Double</span><span class="p">(</span><span class="n">g</span><span class="p">)</span> <span class="o">/</span> <span class="mi">255</span><span class="p">,</span>
            <span class="nv">blue</span><span class="p">:</span>  <span class="kt">Double</span><span class="p">(</span><span class="n">b</span><span class="p">)</span> <span class="o">/</span> <span class="mi">255</span><span class="p">,</span>
            <span class="nv">alpha</span><span class="p">:</span> <span class="n">alpha</span>
        <span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Note that the Hex color has to be a <code class="language-plaintext highlighter-rouge">String</code> in either RGB or RRGGBB format.</p>

<p>As of now, we can initialize <code class="language-plaintext highlighter-rouge">UIColor</code> and <code class="language-plaintext highlighter-rouge">NSColor</code> with a <code class="language-plaintext highlighter-rouge">String</code> Hex color value like so:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">myColor</span> <span class="o">=</span> <span class="kt">PlatformColor</span><span class="p">(</span><span class="nv">hex</span><span class="p">:</span> <span class="s">"#FFFFF"</span><span class="p">)</span>
</code></pre></div></div>
<p>And to be able to create a <code class="language-plaintext highlighter-rouge">SwiftUI.Color</code> with just one initializer and not two, since the one taking <code class="language-plaintext highlighter-rouge">NSColor</code> and the one taking <code class="language-plaintext highlighter-rouge">UIColor</code> are different, a new initializer will do the trick:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">Color</span> <span class="p">{</span>

    <span class="nf">init</span><span class="p">(</span><span class="nv">platformColor</span><span class="p">:</span> <span class="kt">PlatformColor</span><span class="p">)</span> <span class="p">{</span>
        <span class="cp">#if canImport(AppKit)</span>
        <span class="k">self</span> <span class="o">=</span> <span class="kt">Color</span><span class="p">(</span><span class="n">platformColor</span><span class="p">)</span>
        <span class="cp">#else</span>
        <span class="k">self</span> <span class="o">=</span> <span class="kt">Color</span><span class="p">(</span><span class="nv">uiColor</span><span class="p">:</span> <span class="n">platformColor</span><span class="p">)</span>
        <span class="cp">#endif</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now finally to the fun part!</p>

<h4 id="dynamically-resolve-color-based-on-dark-or-light-mode">Dynamically resolve Color based on Dark or Light mode</h4>
<p>As mentioned above, to save us some headache we will bridge through the <code class="language-plaintext highlighter-rouge">PlatformColor</code> by extending it with a static method:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">PlatformColor</span> <span class="p">{</span>

    <span class="kd">static</span> <span class="kd">func</span> <span class="nf">dynamicColor</span><span class="p">(</span><span class="nv">light</span><span class="p">:</span> <span class="kt">PlatformColor</span><span class="p">,</span> <span class="nv">dark</span><span class="p">:</span> <span class="kt">PlatformColor</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">PlatformColor</span> <span class="p">{</span>
        <span class="cp">#if canImport(AppKit)</span>
        <span class="k">return</span> <span class="kt">PlatformColor</span><span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span> <span class="p">{</span> <span class="nv">$0</span><span class="o">.</span><span class="n">isDarkMode</span> <span class="p">?</span> <span class="nv">dark</span> <span class="p">:</span> <span class="n">light</span> <span class="p">}</span>
        <span class="cp">#else</span>
        <span class="k">return</span> <span class="kt">PlatformColor</span> <span class="p">{</span> <span class="nv">$0</span><span class="o">.</span><span class="n">userInterfaceStyle</span> <span class="o">==</span> <span class="o">.</span><span class="n">dark</span> <span class="p">?</span> <span class="nv">dark</span> <span class="p">:</span> <span class="n">light</span> <span class="p">}</span>
        <span class="cp">#endif</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>For the <code class="language-plaintext highlighter-rouge">UIKit</code> version of <code class="language-plaintext highlighter-rouge">PlatformColor</code> we are initializing it with the <a href="https://developer.apple.com/documentation/uikit/uicolor/3238041-init" target="_blank">init(dynamicProvider:)</a> and for the <code class="language-plaintext highlighter-rouge">AppKit</code> version of <code class="language-plaintext highlighter-rouge">PlatformColor</code> we are using the <a href="https://developer.apple.com/documentation/appkit/nscolor/3294481-init" target="_blank">init(name:dynamicProvider:)</a> initializer. Since the <code class="language-plaintext highlighter-rouge">AppKit</code> version contains <code class="language-plaintext highlighter-rouge">NSAppearance</code> that <a href="https://developer.apple.com/documentation/appkit/nsappearance/name" target="_blank">has many different appearances</a>, I have also created a convenience computed property that returns <code class="language-plaintext highlighter-rouge">true</code> if we have Dark Mode enabled on macOS:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#if canImport(AppKit)</span>
<span class="kd">extension</span> <span class="kt">NSAppearance</span> <span class="p">{</span>

    <span class="k">var</span> <span class="nv">isDarkMode</span><span class="p">:</span> <span class="kt">Bool</span> <span class="p">{</span>
        <span class="k">switch</span> <span class="n">name</span> <span class="p">{</span>
        <span class="k">case</span> <span class="o">.</span><span class="n">aqua</span><span class="p">,</span> <span class="o">.</span><span class="n">vibrantLight</span><span class="p">,</span> <span class="o">.</span><span class="n">accessibilityHighContrastVibrantLight</span><span class="p">,</span> <span class="o">.</span><span class="nv">accessibilityHighContrastAqua</span><span class="p">:</span>
            <span class="k">return</span> <span class="kc">false</span>
        <span class="k">case</span> <span class="o">.</span><span class="n">darkAqua</span><span class="p">,</span> <span class="o">.</span><span class="n">vibrantDark</span><span class="p">,</span> <span class="o">.</span><span class="n">accessibilityHighContrastVibrantDark</span><span class="p">,</span> <span class="o">.</span><span class="nv">accessibilityHighContrastDarkAqua</span><span class="p">:</span>
            <span class="k">return</span> <span class="kc">true</span>
        <span class="k">default</span><span class="p">:</span>
            <span class="k">return</span> <span class="kc">false</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
<span class="cp">#endif</span>
</code></pre></div></div>

<p>Now when we create a <code class="language-plaintext highlighter-rouge">PlatformColor</code> with our <code class="language-plaintext highlighter-rouge">dynamicColor</code> method, it will become one instance of <code class="language-plaintext highlighter-rouge">PlatformColor</code> that will apply the Light or Dark mode version based on the system traits, so we should end up with something like this:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">myPlatformColor</span> <span class="o">=</span> <span class="kt">PlatformColor</span><span class="o">.</span><span class="nf">dynamicColor</span><span class="p">(</span>
    <span class="nv">light</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">hex</span><span class="p">:</span> <span class="s">"#FFF"</span><span class="p">),</span>
    <span class="nv">dark</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">hex</span><span class="p">:</span> <span class="s">"#000"</span><span class="p">)</span>
<span class="p">)</span>

<span class="k">let</span> <span class="nv">myColor</span> <span class="o">=</span> <span class="kt">Color</span><span class="p">(</span><span class="n">myPlatformColor</span><span class="p">)</span>
</code></pre></div></div>
<h4 id="final-steps">Final steps</h4>
<p>And since us programmers are lazy (or at least I am 😳), I want to have an even more convenient way of creating the color with SwiftUI on all platforms. To do so, I added one more initializer (as if it’s not enough already) that will make it more convenient than ever before!</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">Color</span> <span class="p">{</span>

    <span class="c1">/// Initializes the Color with Light and Dark Hex String</span>
    <span class="c1">/// - RGB (12-bit), ex. "FFF" for pure white</span>
    <span class="c1">/// - RGB (24-bit), ex. "FFFFFF" for pure white</span>
    <span class="c1">///</span>
    <span class="c1">/// - Parameters:</span>
    <span class="c1">///   - lightHex: Light Mode Hex description of the color</span>
    <span class="c1">///   - darkHex: Dark Mode Hex description of the color, if nil, Light Mode Hex is used</span>
    <span class="c1">///   - opacity: Alpha to apply to the color, `by default 1.0`. The values have to be between 0.0 and 1.0</span>
    <span class="nf">init</span><span class="p">(</span><span class="nv">lightHex</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="nv">darkHex</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="nv">alpha</span><span class="p">:</span> <span class="kt">Double</span> <span class="o">=</span> <span class="mf">1.0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span> <span class="o">=</span> <span class="kt">Color</span><span class="p">(</span>
            <span class="kt">PlatformColor</span><span class="o">.</span><span class="nf">dynamicColor</span><span class="p">(</span>
                <span class="nv">light</span><span class="p">:</span> <span class="kt">PlatformColor</span><span class="p">(</span><span class="nv">hex</span><span class="p">:</span> <span class="n">lightHex</span><span class="p">,</span> <span class="nv">alpha</span><span class="p">:</span> <span class="n">alpha</span><span class="p">),</span>
                <span class="nv">dark</span><span class="p">:</span> <span class="kt">PlatformColor</span><span class="p">(</span><span class="nv">hex</span><span class="p">:</span> <span class="n">darkHex</span><span class="p">,</span> <span class="nv">alpha</span><span class="p">:</span> <span class="n">alpha</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>And now we can store our Colors as easily as this:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">enum</span> <span class="kt">MyAppColors</span> <span class="p">{</span>

    <span class="kd">static</span> <span class="k">let</span> <span class="nv">contentPrimary</span><span class="p">:</span> <span class="kt">Color</span> <span class="o">=</span> <span class="kt">Color</span><span class="p">(</span><span class="nv">lightHex</span><span class="p">:</span> <span class="s">"#000000"</span><span class="p">,</span> <span class="nv">darkHex</span><span class="p">:</span> <span class="s">"#FFFFFF"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>or in <code class="language-plaintext highlighter-rouge">UIKit</code>/<code class="language-plaintext highlighter-rouge">AppKit</code> case:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">enum</span> <span class="kt">MyAppPlatformColors</span> <span class="p">{</span>

    <span class="kd">static</span> <span class="k">let</span> <span class="nv">contentPrimary</span><span class="p">:</span> <span class="kt">PlatformColor</span> <span class="o">=</span> <span class="kt">PlatformColor</span><span class="o">.</span><span class="nf">dynamicColor</span><span class="p">(</span>
        <span class="nv">light</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">hex</span><span class="p">:</span> <span class="s">"#FFF"</span><span class="p">),</span>
        <span class="nv">dark</span><span class="p">:</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">hex</span><span class="p">:</span> <span class="s">"#000"</span><span class="p">)</span>
    <span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="conclusion">Conclusion</h3>
<p>With this approach we can replace the Asset Catalog’s colors with programatic Hex values! This enables to have better control over our Colors and better searchability. Also this can come in handy in case you are building some UI SDK that needs to have configurable Theme of colors that support both light and dark mode, which can get a bit tricky with relying only on Asset Catalog.</p>

<p>I wish Xcode also could preview the color as a rectangle next to the Hex value (please,  Apple 🙏), but I guess for now we have to use VSCode 🤡.</p>

<p>As always, I will be happy for any feedback!</p>

<p>Happy coding! 💻</p>

<h3 id="coming-next-">Coming Next 🔜</h3>
<p>As of writing this article (Sep 15) I honestly don’t know yet 😅 but don’t worry, I will figure something out 🤞!</p>]]></content><author><name>Daniel Arden</name><email>me@danielarden.com</email></author><category term="SwiftUI" /><category term="macOS" /><category term="iOS" /><category term="UIKit" /><category term="AppKit" /><summary type="html"><![CDATA[In this article we will take a look at how to programatically resolve dynamic colors from Hex values.]]></summary></entry><entry><title type="html">Spotlight-like hotkey window</title><link href="http://localhost:4000/posts/hotkey-window/" rel="alternate" type="text/html" title="Spotlight-like hotkey window" /><published>2024-09-02T00:00:00+02:00</published><updated>2024-09-02T00:00:00+02:00</updated><id>http://localhost:4000/posts/hotkey-window</id><content type="html" xml:base="http://localhost:4000/posts/hotkey-window/"><![CDATA[<p>In this week’s article we will take a look at how to create a system-wide Spotlight-like hotkey window for our SwiftUI app.</p>

<h3 id="what-we-want-to-achieve">What we want to achieve</h3>
<p>A window that would float on top of all other windows and can be triggered on any macOS space by pressing a user-customizable hotkey window, just like Spotlight.
<img src="../../assets/images/spotlight-like-hotkey-window.png" alt="Spotlight-like hotkey window" /></p>
<h3 id="my-approach">My approach</h3>
<p>Let’s start by defining some simple View such as this HotkeyView that mimics the Spotlight search bar, but of course that can be any View of your liking.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">HotkeyView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>

    <span class="kd">@State</span>
    <span class="kd">private</span> <span class="k">var</span> <span class="nv">text</span><span class="p">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">""</span>

    <span class="c1">// Since we have a TextField here, we want to make it focused on appear</span>
    <span class="kd">@FocusState</span>
    <span class="kd">private</span> <span class="k">var</span> <span class="nv">focused</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">16</span><span class="p">)</span> <span class="p">{</span>
            <span class="kt">Image</span><span class="p">(</span><span class="nv">systemName</span><span class="p">:</span> <span class="s">"magnifyingglass"</span><span class="p">)</span>
                <span class="o">.</span><span class="nf">resizable</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">24</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">24</span><span class="p">)</span>

            <span class="kt">TextField</span><span class="p">(</span><span class="s">"My hotkey window"</span><span class="p">,</span> <span class="nv">text</span><span class="p">:</span> <span class="n">$text</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">title</span><span class="p">)</span>
                <span class="o">.</span><span class="nf">textFieldStyle</span><span class="p">(</span><span class="o">.</span><span class="n">plain</span><span class="p">)</span>
                <span class="o">.</span><span class="nf">focused</span><span class="p">(</span><span class="n">$focused</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="o">.</span><span class="nf">padding</span><span class="p">()</span>
        <span class="o">.</span><span class="nf">foregroundStyle</span><span class="p">(</span><span class="o">.</span><span class="n">primary</span><span class="p">)</span>
        <span class="c1">// Setting a static width will save us some headache when centering the NSPanel</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">750</span><span class="p">)</span>
        <span class="o">.</span><span class="n">onAppear</span> <span class="p">{</span>
            <span class="n">focused</span> <span class="o">=</span> <span class="kc">true</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="hotkey-recording">Hotkey Recording</h4>
<p>While we can create our own implementation, I really recommend using a library called <a href="https://github.com/sindresorhus/KeyboardShortcuts" target="_blank">KeyboardShortcuts</a>. It’s reliable, very easy to use, and can get you started in minutes.</p>

<p>Let’s add it to our project by going to Xcode &gt; File &gt; Add Package Dependencies… and pasting the <a href="https://github.com/sindresorhus/KeyboardShortcuts" target="_blank">KeyboardShortcuts GitHub URL</a> in the search bar.</p>

<p>First, for convenience’s sake, we define an extension on the library’s <code class="language-plaintext highlighter-rouge">KeyboardShortcut.Name</code> like so 👇 . It is not only a good practice, but also it will prevent us from misspelling the name later on or in case it is renamed in the future.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">KeyboardShortcuts</span><span class="o">.</span><span class="kt">Name</span> <span class="p">{</span>
    <span class="kd">static</span> <span class="k">let</span> <span class="nv">openHotkeyWindow</span> <span class="o">=</span> <span class="kt">Self</span><span class="p">(</span><span class="s">"openHotkeyWindow"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now we need to create some Settings Scene where we would let the user customize the hotkey. Of course, the setting doesn’t need to be inside native <a href="https://developer.apple.com/documentation/swiftui/settings" target="_blank"><code class="language-plaintext highlighter-rouge">Settings</code></a> Scene, however, that’s usually where the user would logically look for hotkey settings. The use of the native <a href="https://developer.apple.com/documentation/swiftui/settings" target="_blank"><code class="language-plaintext highlighter-rouge">Settings</code></a> allows the App Settings to be opened by pressing <code class="language-plaintext highlighter-rouge">⌘+</code>. What’s most important is the use of <code class="language-plaintext highlighter-rouge">KeyboardShortcuts.Recorder(for: .openHotkeyWindow)</code> to have a SwiftUI View that allows us to record the hotkey with our custom name.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">SettingsScreen</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="kt">Text</span><span class="p">(</span><span class="s">"Configure Hotkey"</span><span class="p">)</span>

            <span class="kt">KeyboardShortcuts</span><span class="o">.</span><span class="kt">Recorder</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="o">.</span><span class="n">openHotkeyWindow</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="o">.</span><span class="nf">padding</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>And then inside the <code class="language-plaintext highlighter-rouge">var body: some Scene</code> in our <code class="language-plaintext highlighter-rouge">@main</code> App struct we need to add this:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">Settings</span> <span class="p">{</span>
    <span class="kt">SettingsScreen</span><span class="p">()</span>
<span class="p">}</span>

</code></pre></div></div>
<h4 id="hotkey-handling">Hotkey Handling</h4>
<p>Since we have our convenient <a href="https://github.com/sindresorhus/KeyboardShortcuts" target="_blank">KeyboardShortcuts</a> library, listening to our hotkey being pressed is as simple as this:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">KeyboardShortcuts</span><span class="o">.</span><span class="nf">onKeyUp</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="o">.</span><span class="n">openHotkeyWindow</span><span class="p">)</span> <span class="p">{}</span>
</code></pre></div></div>
<p>If you run your app and put some <code class="language-plaintext highlighter-rouge">debugPrint</code> or a breakpoint inside this closure, you should see it being called.</p>

<p>Now that we are listening to our hotkey presses and we have our HotkeyView in place, we can move on to making it appear when the hotkey is pressed.</p>

<h4 id="issues-along-the-way-">Issues along the way 😢</h4>

<p>I have attempted to use a <code class="language-plaintext highlighter-rouge">WindowGroup(id:)</code> and <code class="language-plaintext highlighter-rouge">WindowGroup(value:)</code>, and then opening such window with <code class="language-plaintext highlighter-rouge">@Environment(\.openWindow)</code> in the hotkey handling closure, but unfortunately as of macOS Sequoia it still doesn’t have the desired effect, and <code class="language-plaintext highlighter-rouge">UtilityWindow</code> did not help either. For me the window wasn’t opening on the current space I was at with Mission Control, sometimes the window didn’t float on top of everything else and I had issues with having it appear in fullscreen mode.</p>

<h4 id="resorting-to-appkit">Resorting to AppKit</h4>
<p>Initially, I tried experimenting with <code class="language-plaintext highlighter-rouge">NSWindow</code> that would take in a SwiftUI View in the parameter, but then I have stumbled upon this article <a href="https://cindori.com/developer/floating-panel" target="_blank">Make a floating panel in SwiftUI for macOS</a> that suggested using <code class="language-plaintext highlighter-rouge">NSPanel</code>. When comparing the behavior with Spotlight, it seems like <code class="language-plaintext highlighter-rouge">NSPanel</code> is the way to go. It works well with multiple spaces and screens and it by default is dismissable by pressing the <code class="language-plaintext highlighter-rouge">esc</code> key. Taking from the article, I have slightly modified the proposed <code class="language-plaintext highlighter-rouge">NSPanel</code> subclass like so:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="kd">class</span> <span class="kt">FloatingPanel</span><span class="o">&lt;</span><span class="kt">Content</span><span class="p">:</span> <span class="kt">View</span><span class="o">&gt;</span><span class="p">:</span> <span class="kt">NSPanel</span> <span class="p">{</span>

    <span class="nf">init</span><span class="p">(</span>
        <span class="nv">view</span><span class="p">:</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">Content</span><span class="p">,</span>
        <span class="c1">// We need to provide NSRect since the NSWindow doesn't inherit the size from the content</span>
        <span class="c1">// by default. Not setting the contentRect would result in incorrect positioning</span>
        <span class="c1">// when centering the window</span>
        <span class="nv">contentRect</span><span class="p">:</span> <span class="kt">NSRect</span><span class="p">,</span>
        <span class="nv">didClose</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">Void</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="n">didClose</span> <span class="o">=</span> <span class="n">didClose</span>

        <span class="k">super</span><span class="o">.</span><span class="nf">init</span><span class="p">(</span>
            <span class="nv">contentRect</span><span class="p">:</span> <span class="o">.</span><span class="n">zero</span><span class="p">,</span>
            <span class="nv">styleMask</span><span class="p">:</span> <span class="p">[</span>
                <span class="o">.</span><span class="n">borderless</span><span class="p">,</span>
                <span class="o">.</span><span class="n">nonactivatingPanel</span><span class="p">,</span>
                <span class="o">.</span><span class="n">titled</span><span class="p">,</span>
                <span class="o">.</span><span class="n">fullSizeContentView</span>
            <span class="p">],</span>
            <span class="nv">backing</span><span class="p">:</span> <span class="o">.</span><span class="n">buffered</span><span class="p">,</span>
            <span class="nv">defer</span><span class="p">:</span> <span class="kc">false</span>
        <span class="p">)</span>

        <span class="c1">/// Allow the panel to be on top of other windows</span>
        <span class="n">isFloatingPanel</span> <span class="o">=</span> <span class="kc">true</span>
        <span class="n">level</span> <span class="o">=</span> <span class="o">.</span><span class="n">statusBar</span>

        <span class="c1">/// Allow the pannel to be overlaid in a fullscreen space</span>
        <span class="n">collectionBehavior</span> <span class="o">=</span> <span class="p">[</span><span class="o">.</span><span class="n">canJoinAllSpaces</span><span class="p">,</span> <span class="o">.</span><span class="n">fullScreenAuxiliary</span><span class="p">,</span> <span class="o">.</span><span class="n">transient</span><span class="p">,</span> <span class="o">.</span><span class="n">ignoresCycle</span><span class="p">]</span>

        <span class="c1">/// Don't show a window title, even if it's set</span>
        <span class="n">titleVisibility</span> <span class="o">=</span> <span class="o">.</span><span class="n">hidden</span>
        <span class="n">titlebarAppearsTransparent</span> <span class="o">=</span> <span class="kc">true</span>

        <span class="c1">/// Since there is no title bar make the window moveable by dragging on the background</span>
        <span class="n">isMovableByWindowBackground</span> <span class="o">=</span> <span class="kc">true</span>

        <span class="c1">/// Hide when unfocused</span>
        <span class="n">hidesOnDeactivate</span> <span class="o">=</span> <span class="kc">true</span>

        <span class="c1">/// Hide all traffic light buttons</span>
        <span class="nf">standardWindowButton</span><span class="p">(</span><span class="o">.</span><span class="n">closeButton</span><span class="p">)?</span><span class="o">.</span><span class="n">isHidden</span> <span class="o">=</span> <span class="kc">true</span>
        <span class="nf">standardWindowButton</span><span class="p">(</span><span class="o">.</span><span class="n">miniaturizeButton</span><span class="p">)?</span><span class="o">.</span><span class="n">isHidden</span> <span class="o">=</span> <span class="kc">true</span>
        <span class="nf">standardWindowButton</span><span class="p">(</span><span class="o">.</span><span class="n">zoomButton</span><span class="p">)?</span><span class="o">.</span><span class="n">isHidden</span> <span class="o">=</span> <span class="kc">true</span>

        <span class="c1">/// Sets animations accordingly</span>
        <span class="n">animationBehavior</span> <span class="o">=</span> <span class="o">.</span><span class="n">utilityWindow</span>

        <span class="c1">/// Set the content view.</span>
        <span class="c1">/// The safe area is ignored because the title bar still interferes with the geometry</span>
        <span class="n">contentView</span> <span class="o">=</span> <span class="kt">NSHostingView</span><span class="p">(</span>
            <span class="nv">rootView</span><span class="p">:</span> <span class="nf">view</span><span class="p">()</span>
        <span class="p">)</span>
    <span class="p">}</span>

    <span class="kd">private</span> <span class="k">let</span> <span class="nv">didClose</span><span class="p">:</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">Void</span>

    <span class="c1">/// Close automatically when out of focus, e.g. outside click</span>
    <span class="k">override</span> <span class="kd">func</span> <span class="nf">resignKey</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">super</span><span class="o">.</span><span class="nf">resignKey</span><span class="p">()</span>
        <span class="nf">close</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="c1">/// Close and toggle presentation, so that it matches the current state of the panel</span>
    <span class="k">override</span> <span class="kd">func</span> <span class="nf">close</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">super</span><span class="o">.</span><span class="nf">close</span><span class="p">()</span>
        <span class="nf">didClose</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="c1">/// `canBecomeKey` is required so that text inputs inside the panel can receive focus</span>
    <span class="k">override</span> <span class="k">var</span> <span class="nv">canBecomeKey</span><span class="p">:</span> <span class="kt">Bool</span> <span class="p">{</span>
        <span class="k">return</span> <span class="kc">true</span>
    <span class="p">}</span>

    <span class="c1">// For our use case, we don't want the window to become main and thus steal the focus from</span>
    <span class="c1">// the previously opened app completely</span>
    <span class="k">override</span> <span class="k">var</span> <span class="nv">canBecomeMain</span><span class="p">:</span> <span class="kt">Bool</span> <span class="p">{</span>
        <span class="k">return</span> <span class="kc">false</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>In the original implementation there was an issue that the instance was never deallocated after closing the <code class="language-plaintext highlighter-rouge">NSPanel</code>, because there wasn’t any callback that would signal that the window was closed. For that I added <code class="language-plaintext highlighter-rouge">didClose</code> closure.</p>

<p>I have also modified it so that it doesn’t have to be tied to an existing View with a <code class="language-plaintext highlighter-rouge">ViewModifier</code> by a Binding.</p>
<h4 id="last-steps">Last steps</h4>
<p>To encapsulate logic in one place, I have created a class <code class="language-plaintext highlighter-rouge">FloatingPanelHandler</code> that holds a reference to an instance of our <code class="language-plaintext highlighter-rouge">NSPanel</code> and in case our hotkey is pressed again, it would dismiss the <code class="language-plaintext highlighter-rouge">NSPanel</code> if already open, just like Spotlight.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="kd">class</span> <span class="kt">FloatingPanelHandler</span> <span class="p">{</span>

    <span class="kd">private</span> <span class="k">var</span> <span class="nv">panel</span><span class="p">:</span> <span class="kt">NSPanel</span><span class="p">?</span>

    <span class="nf">init</span><span class="p">()</span> <span class="p">{</span>
        <span class="kt">KeyboardShortcuts</span><span class="o">.</span><span class="nf">onKeyUp</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="o">.</span><span class="n">openHotkeyWindow</span><span class="p">)</span> <span class="p">{</span>
            <span class="c1">// We don't need [weak self] here since this class will be alive during the whole</span>
            <span class="c1">// lifecycle of the App.</span>
            <span class="k">if</span> <span class="k">self</span><span class="o">.</span><span class="n">panel</span> <span class="o">==</span> <span class="kc">nil</span> <span class="p">{</span>
                <span class="k">let</span> <span class="nv">panel</span> <span class="o">=</span> <span class="kt">FloatingPanel</span><span class="p">(</span>
                    <span class="nv">view</span><span class="p">:</span> <span class="p">{</span>
                        <span class="c1">// Create the SwiftUI View that you want to be shown</span>
                        <span class="c1">// in the floating window</span>
                        <span class="kt">HotkeyView</span><span class="p">()</span>
                    <span class="p">},</span>
                    <span class="c1">// If you want your window to be perfectly centered, we need to provide</span>
                    <span class="c1">// proper width and height. For our case, since the height is pretty small,</span>
                    <span class="c1">// it's ok-ish to just pass 0 as the height (Designers, please don't kill me)</span>
                    <span class="nv">contentRect</span><span class="p">:</span> <span class="kt">NSRect</span><span class="p">(</span><span class="nv">x</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="nv">width</span><span class="p">:</span> <span class="mi">750</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">0</span><span class="p">),</span>
                    <span class="nv">didClose</span><span class="p">:</span> <span class="p">{</span>
                        <span class="c1">// When `didClose` gets called, make sure to remove the reference</span>
                        <span class="c1">// to allow it to deallocate</span>
                        <span class="k">self</span><span class="o">.</span><span class="n">panel</span> <span class="o">=</span> <span class="kc">nil</span>
                    <span class="p">}</span>
                <span class="p">)</span>

                <span class="c1">// It's important to activate the NSApplication so that our window</span>
                <span class="c1">// shows on top and takes the focus.</span>
                <span class="kt">NSApplication</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="nf">activate</span><span class="p">()</span>
                <span class="n">panel</span><span class="o">.</span><span class="nf">makeKeyAndOrderFront</span><span class="p">(</span><span class="kc">nil</span><span class="p">)</span>
                <span class="n">panel</span><span class="o">.</span><span class="nf">orderFrontRegardless</span><span class="p">()</span>
                <span class="n">panel</span><span class="o">.</span><span class="nf">center</span><span class="p">()</span>

                <span class="k">self</span><span class="o">.</span><span class="n">panel</span> <span class="o">=</span> <span class="n">panel</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="c1">// If the panel is already shown and the hotkey is pressed again,</span>
                <span class="c1">// we close it and nullify the reference</span>
                <span class="k">self</span><span class="o">.</span><span class="n">panel</span><span class="p">?</span><span class="o">.</span><span class="nf">close</span><span class="p">()</span>
                <span class="k">self</span><span class="o">.</span><span class="n">panel</span> <span class="o">=</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>As a last step we need to save an instance of the <code class="language-plaintext highlighter-rouge">FloatingPanelHandler</code> in our <code class="language-plaintext highlighter-rouge">@main</code> App struct, simply by adding:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="k">let</span> <span class="nv">floatingPanelHandler</span> <span class="o">=</span> <span class="kt">FloatingPanelHandler</span><span class="p">()</span>
</code></pre></div></div>
<p>Since reading the Hotkey is closure based, this is sufficient to make your App always listen to the keyboard shortcut the user has defined and open the hotkey window.</p>
<h3 id="the-result-">The result 🎉</h3>
<p>Voila! Now we should be able to open the SwiftUI View on any space, regardless of full-screen mode, it should automatically take focus, and close when pressing <code class="language-plaintext highlighter-rouge">esc</code> or when clicking outside of the window.
<img src="../../assets/images/spotlight-like-hotkey-window-padding.png" alt="Spotlight-like hotkey window with padding" />
As of macOS Sequoia there still seems to be an issue with SafeArea insets when a SwiftUI view is wrapped inside a <code class="language-plaintext highlighter-rouge">NSHostingView</code> 👀
To make sure that the SafeArea gets ignored properly for our SwiftUI View, check out my previous article <a href="https://ardentswift.com/posts/macos-hide-toolbar/" target="_blank">How to hide toolbar in macOS App’s window in SwiftUI</a>!</p>

<p>We can then change our <code class="language-plaintext highlighter-rouge">FloatingPanel</code> subclass’s init at the bottom as follows:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">contentView</span> <span class="o">=</span> <span class="kt">NSHostingViewSuppressingSafeArea</span><span class="p">(</span>
    <span class="nv">rootView</span><span class="p">:</span> <span class="nf">view</span><span class="p">()</span>
<span class="p">)</span>
</code></pre></div></div>
<p>This should give us the desired result 🚀
<img src="../../assets/images/spotlight-like-hotkey-window.png" alt="Spotlight-like hotkey window" /></p>

<h3 id="conclusion">Conclusion</h3>
<p>While this approach has a lot of room for improvement, it’s the only thing that I got working with multiple spaces and monitors support after spending many hours on it (<strong><em>cries in building SwiftUI apps on macOS</em></strong> 🥲)</p>

<p>Happy coding! 💻</p>

<h3 id="coming-next-">Coming Next 🔜</h3>
<p>In the next article we will take a look at how to ~create a ScrollView with custom Pull actions~ programatically resolve dynamic UIColor/NSColor from Hex values in Light and Dark Mode!</p>

<h4 id="edit">Edit</h4>
<p>When working on my app today (20th Sep 24) I found out that the code has been creating a strong reference to the SwiftUI View. I have modified the code so that strong reference cycle no longer happens 🙏</p>]]></content><author><name>Daniel Arden</name><email>me@danielarden.com</email></author><category term="SwiftUI" /><category term="macOS" /><summary type="html"><![CDATA[In this week's article we will take a look at how to create a system-wide Spotlight-like hotkey window for our SwiftUI app.]]></summary></entry><entry><title type="html">How to hide toolbar in macOS App’s window in SwiftUI</title><link href="http://localhost:4000/posts/macos-hide-toolbar/" rel="alternate" type="text/html" title="How to hide toolbar in macOS App’s window in SwiftUI" /><published>2024-08-18T00:00:00+02:00</published><updated>2024-08-18T00:00:00+02:00</updated><id>http://localhost:4000/posts/macos-hide-toolbar</id><content type="html" xml:base="http://localhost:4000/posts/macos-hide-toolbar/"><![CDATA[<p>Let’s take a look at what are our options to remove the toolbar in macOS. Despite many new APIs introduced in WWDC 2024, it turned out to be rather tricky.</p>

<h3 id="the-problem-">The problem 🤔</h3>
<p>For my Todo list app “Simply Things” that I am currently building I wanted to have a Spotlight-like hotkey window that I could trigger with a keyboard shortcut anywhere to add a new Thing to do that looks like this:
<img src="../../assets/images/st-quick-add.png" alt="Simply Things Quick Add Transparent window" />
Starting with macOS 15.0 (<a href="https://www.youtube.com/watch?v=zosCe4q1xPI">WWDC24: Tailor macOS windows with SwiftUI</a>) we can easily hide the toolbar for macOS windows:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="nf">toolbarVisibility</span><span class="p">(</span><span class="o">.</span><span class="n">hidden</span><span class="p">,</span> <span class="nv">for</span><span class="p">:</span> <span class="o">.</span><span class="n">windowToolbar</span><span class="p">)</span>
</code></pre></div></div>
<p>But what I got as a result was this:
<img src="../../assets/images/st-quick-add-broken.png" alt="Simply Things Quick Add Window with Safe Area" />
So I thought I would just need to apply <code class="language-plaintext highlighter-rouge">.ignoresSafeArea()</code> and my problem would be solved, but that only pushed the content behind the now invisible toolbar background, leaving a padding on the bottom that I couldn’t get rid of:
<img src="../../assets/images/st-quick-add-ignore-safe-area.png" alt="Simply Things Quick Add Window ignoring Safe Area - still broken" /></p>
<h3 id="the-solution-">The solution 💡</h3>
<p>It’s not a perfect one, but we need to go back to AppKit. No matter what I have tried in SwiftUI, it doesn’t seem like there is a sensible way to ignore the window toolbar’s safe area completely. In order to work around this, first I have created a subclass of <code class="language-plaintext highlighter-rouge">NSHostingView</code> that ignores the Safe Area. Unfortunately without doing this adding even a negative padding to the view would not successfuly make the content go underneath the title bar without creating a space at the bottom.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="kd">class</span> <span class="kt">NSHostingViewIgnoringSafeArea</span><span class="o">&lt;</span><span class="kt">T</span><span class="p">:</span> <span class="kt">View</span><span class="o">&gt;</span><span class="p">:</span> <span class="kt">NSHostingView</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="kd">required</span> <span class="nf">init</span><span class="p">(</span><span class="nv">rootView</span><span class="p">:</span> <span class="kt">T</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">super</span><span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">rootView</span><span class="p">:</span> <span class="n">rootView</span><span class="p">)</span>

        <span class="nf">addLayoutGuide</span><span class="p">(</span><span class="n">layoutGuide</span><span class="p">)</span>

        <span class="c1">// Pinning the view to the edges</span>
        <span class="kt">NSLayoutConstraint</span><span class="o">.</span><span class="nf">activate</span><span class="p">([</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">layoutGuide</span><span class="o">.</span><span class="n">leadingAnchor</span><span class="p">),</span>
            <span class="n">topAnchor</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">layoutGuide</span><span class="o">.</span><span class="n">topAnchor</span><span class="p">),</span>
            <span class="n">trailingAnchor</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">layoutGuide</span><span class="o">.</span><span class="n">trailingAnchor</span><span class="p">),</span>
            <span class="n">bottomAnchor</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">layoutGuide</span><span class="o">.</span><span class="n">bottomAnchor</span><span class="p">)</span>
        <span class="p">])</span>
    <span class="p">}</span>

    <span class="k">override</span> <span class="kd">func</span> <span class="nf">viewDidMoveToWindow</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// We need to set the alpha value to 0 here, since</span>
        <span class="c1">// before the title bar computation is done, the view might jump.</span>
        <span class="c1">// We will be setting the alpha back to 1 after computation is finished.</span>
        <span class="n">window</span><span class="p">?</span><span class="o">.</span><span class="n">alphaValue</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">super</span><span class="o">.</span><span class="nf">viewDidMoveToWindow</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="kd">private</span> <span class="kd">lazy</span> <span class="k">var</span> <span class="nv">layoutGuide</span><span class="p">:</span> <span class="kt">NSLayoutGuide</span> <span class="o">=</span> <span class="kt">NSLayoutGuide</span><span class="p">()</span>

    <span class="kd">required</span> <span class="nf">init</span><span class="p">?(</span><span class="nv">coder</span><span class="p">:</span> <span class="kt">NSCoder</span><span class="p">)</span> <span class="p">{</span>
        <span class="nf">fatalError</span><span class="p">(</span><span class="s">"init(coder:) has not been implemented"</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">override</span> <span class="k">var</span> <span class="nv">safeAreaRect</span><span class="p">:</span> <span class="kt">NSRect</span> <span class="p">{</span> <span class="n">frame</span> <span class="p">}</span>

    <span class="k">override</span> <span class="k">var</span> <span class="nv">safeAreaInsets</span><span class="p">:</span> <span class="kt">NSEdgeInsets</span> <span class="p">{</span>
        <span class="kt">NSEdgeInsets</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">left</span><span class="p">:</span> <span class="mi">0</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">right</span><span class="p">:</span> <span class="mi">0</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">override</span> <span class="k">var</span> <span class="nv">safeAreaLayoutGuide</span><span class="p">:</span> <span class="kt">NSLayoutGuide</span> <span class="p">{</span> <span class="n">layoutGuide</span> <span class="p">}</span>

    <span class="k">override</span> <span class="k">var</span> <span class="nv">additionalSafeAreaInsets</span><span class="p">:</span> <span class="kt">NSEdgeInsets</span> <span class="p">{</span>
        <span class="k">get</span> <span class="p">{</span> <span class="kt">NSEdgeInsets</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">left</span><span class="p">:</span> <span class="mi">0</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">right</span><span class="p">:</span> <span class="mi">0</span><span class="p">)</span> <span class="p">}</span>

        <span class="k">set</span> <span class="p">{}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Once we have this in place, we need to be able to calculate the title bar’s height. Coming from iOS we might want to just get the Key Window of the <code class="language-plaintext highlighter-rouge">NSApplication</code>, compute the title bar height and apply negative padding, but with macOS we might not have a <code class="language-plaintext highlighter-rouge">NSWindow</code> at all for our app while trying to compute the height, so it’s not very reliable.
For reliability we should create a <code class="language-plaintext highlighter-rouge">NSViewRepresentable</code> that will grab its <code class="language-plaintext highlighter-rouge">NSWindow</code> and calculate the title bar height. Let’s also not forget to set the alpha back to 1 for the window.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">WindowHiddenToolbarView</span><span class="o">&lt;</span><span class="kt">Content</span><span class="p">:</span> <span class="kt">View</span><span class="o">&gt;</span><span class="p">:</span> <span class="kt">NSViewRepresentable</span> <span class="p">{</span>

    <span class="nf">init</span><span class="p">(</span>
        <span class="nv">contentTopPadding</span><span class="p">:</span> <span class="kt">Binding</span><span class="o">&lt;</span><span class="kt">CGFloat</span><span class="o">&gt;</span><span class="p">,</span>
        <span class="nv">content</span><span class="p">:</span> <span class="kd">@escaping</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="n">_contentTopPadding</span> <span class="o">=</span> <span class="n">contentTopPadding</span>
        <span class="k">self</span><span class="o">.</span><span class="n">content</span> <span class="o">=</span> <span class="n">content</span>
    <span class="p">}</span>

    <span class="kd">@Binding</span> <span class="kd">private</span> <span class="k">var</span> <span class="nv">contentTopPadding</span><span class="p">:</span> <span class="kt">CGFloat</span>
    <span class="kd">private</span> <span class="k">let</span> <span class="nv">content</span><span class="p">:</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">Content</span>

    <span class="kd">func</span> <span class="nf">makeNSView</span><span class="p">(</span><span class="nv">context</span><span class="p">:</span> <span class="kt">Context</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">NSView</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">view</span> <span class="o">=</span> <span class="kt">NSHostingViewIgnoringSafeArea</span><span class="p">(</span>
            <span class="nv">rootView</span><span class="p">:</span> <span class="nf">content</span><span class="p">()</span>
        <span class="p">)</span>

        <span class="kt">DispatchQueue</span><span class="o">.</span><span class="n">main</span><span class="o">.</span><span class="k">async</span> <span class="p">{</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nv">window</span> <span class="o">=</span> <span class="n">view</span><span class="o">.</span><span class="n">window</span> <span class="p">{</span>
                <span class="k">let</span> <span class="nv">windowFrameHeight</span> <span class="o">=</span> <span class="n">window</span><span class="o">.</span><span class="n">frame</span><span class="o">.</span><span class="n">height</span>
                <span class="k">let</span> <span class="nv">contentLayoutFrameHeight</span> <span class="o">=</span> <span class="n">window</span><span class="o">.</span><span class="n">contentLayoutRect</span><span class="o">.</span><span class="n">height</span>
                <span class="k">let</span> <span class="nv">titlebarHeight</span> <span class="o">=</span> <span class="n">windowFrameHeight</span> <span class="o">-</span> <span class="n">contentLayoutFrameHeight</span>
                <span class="n">contentTopPadding</span> <span class="o">=</span> <span class="o">-</span><span class="n">titlebarHeight</span>

                <span class="c1">// We need to make sure to set alpha back to 1, since we are setting</span>
                <span class="c1">// it to 0 in NSHostingViewSuppressingSafeArea</span>
                <span class="n">window</span><span class="o">.</span><span class="n">alphaValue</span> <span class="o">=</span> <span class="mi">1</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="kd">func</span> <span class="nf">updateNSView</span><span class="p">(</span><span class="n">_</span> <span class="nv">nsView</span><span class="p">:</span> <span class="kt">NSViewType</span><span class="p">,</span> <span class="nv">context</span><span class="p">:</span> <span class="kt">Context</span><span class="p">)</span> <span class="p">{}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Using our newly created <code class="language-plaintext highlighter-rouge">WindowHiddenToolbarView</code> will do the job already, but we would need to keep a <code class="language-plaintext highlighter-rouge">@State</code> in our view and apply the negative padding, but here comes a <code class="language-plaintext highlighter-rouge">ViewModifier</code> to our rescue 🎉!</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">WindowHiddenToolbarModifier</span><span class="p">:</span> <span class="kt">ViewModifier</span> <span class="p">{</span>

    <span class="kd">@State</span>
    <span class="kd">private</span> <span class="k">var</span> <span class="nv">contentTopPadding</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="mi">0</span>

    <span class="kd">func</span> <span class="nf">body</span><span class="p">(</span><span class="nv">content</span><span class="p">:</span> <span class="kt">Content</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="kt">WindowHiddenToolbarView</span><span class="p">(</span>
            <span class="nv">contentTopPadding</span><span class="p">:</span> <span class="n">$contentTopPadding</span><span class="p">,</span>
            <span class="nv">content</span><span class="p">:</span> <span class="p">{</span> <span class="n">content</span> <span class="p">}</span>
        <span class="p">)</span>
        <span class="o">.</span><span class="nf">toolbarVisibility</span><span class="p">(</span><span class="o">.</span><span class="n">hidden</span><span class="p">,</span> <span class="nv">for</span><span class="p">:</span> <span class="o">.</span><span class="n">windowToolbar</span><span class="p">)</span>
        <span class="o">.</span><span class="nf">padding</span><span class="p">(</span><span class="o">.</span><span class="n">top</span><span class="p">,</span> <span class="n">contentTopPadding</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">windowToolbarHidden</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">modifier</span><span class="p">(</span><span class="kt">WindowHiddenToolbarModifier</span><span class="p">())</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This neat modifier allows us to be able to hide the toolbar just like that</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">TheView</span><span class="p">()</span>
    <span class="o">.</span><span class="nf">windowToolbarHidden</span><span class="p">()</span>
</code></pre></div></div>
<p>And voila! We have a SwiftUI view that hides the toolbar and completely ignores the Safe Area!
<img src="../../assets/images/st-quick-add.png" alt="Simply Things Quick Add Transparent window" /></p>

<h3 id="targeting-older-macos-versions">Targeting older macOS Versions</h3>
<p>If you need to target older macOS versions and cannot use the <code class="language-plaintext highlighter-rouge">.toolbarVisibility(.hidden, for: .windowToolbar)</code> modifier, inside of <code class="language-plaintext highlighter-rouge">WindowHiddenToolbarView</code> you can change the <code class="language-plaintext highlighter-rouge">makeNSView</code> method like so:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="kd">func</span> <span class="nf">makeNSView</span><span class="p">(</span><span class="nv">context</span><span class="p">:</span> <span class="kt">Context</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">NSView</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">view</span> <span class="o">=</span> <span class="kt">NSHostingViewIgnoringSafeArea</span><span class="p">(</span>
            <span class="nv">rootView</span><span class="p">:</span> <span class="nf">content</span><span class="p">()</span>
        <span class="p">)</span>

        <span class="kt">DispatchQueue</span><span class="o">.</span><span class="n">main</span><span class="o">.</span><span class="k">async</span> <span class="p">{</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nv">window</span> <span class="o">=</span> <span class="n">view</span><span class="o">.</span><span class="n">window</span> <span class="p">{</span>
                <span class="k">let</span> <span class="nv">windowFrameHeight</span> <span class="o">=</span> <span class="n">window</span><span class="o">.</span><span class="n">frame</span><span class="o">.</span><span class="n">height</span>
                <span class="k">let</span> <span class="nv">contentLayoutFrameHeight</span> <span class="o">=</span> <span class="n">window</span><span class="o">.</span><span class="n">contentLayoutRect</span><span class="o">.</span><span class="n">height</span>
                <span class="k">let</span> <span class="nv">titlebarHeight</span> <span class="o">=</span> <span class="n">windowFrameHeight</span> <span class="o">-</span> <span class="n">contentLayoutFrameHeight</span>
                <span class="n">contentTopPadding</span> <span class="o">=</span> <span class="o">-</span><span class="n">titlebarHeight</span>

                <span class="c1">// This makes sure we hide the title bar as well as the buttons</span>
                <span class="n">window</span><span class="o">.</span><span class="n">titlebarAppearsTransparent</span> <span class="o">=</span> <span class="kc">true</span>
                <span class="n">window</span><span class="o">.</span><span class="n">titleVisibility</span> <span class="o">=</span> <span class="o">.</span><span class="n">hidden</span>
                <span class="n">window</span><span class="o">.</span><span class="n">styleMask</span><span class="o">.</span><span class="nf">insert</span><span class="p">(</span><span class="o">.</span><span class="n">borderless</span><span class="p">)</span>
                <span class="n">window</span><span class="o">.</span><span class="n">styleMask</span><span class="o">.</span><span class="nf">remove</span><span class="p">(</span><span class="o">.</span><span class="n">closable</span><span class="p">)</span>
                <span class="n">window</span><span class="o">.</span><span class="n">styleMask</span><span class="o">.</span><span class="nf">remove</span><span class="p">(</span><span class="o">.</span><span class="n">fullScreen</span><span class="p">)</span>
                <span class="n">window</span><span class="o">.</span><span class="n">styleMask</span><span class="o">.</span><span class="nf">remove</span><span class="p">(</span><span class="o">.</span><span class="n">miniaturizable</span><span class="p">)</span>

                <span class="c1">// We need to make sure to set alpha back to 1, since we are setting</span>
                <span class="c1">// it to 0 in NSHostingViewSuppressingSafeArea</span>
                <span class="n">window</span><span class="o">.</span><span class="n">alphaValue</span> <span class="o">=</span> <span class="mi">1</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="n">view</span>
    <span class="p">}</span>
</code></pre></div></div>

<h3 id="final-thoughts-">Final Thoughts 🔚</h3>
<p>While this solution is far from perfect, this seems like the only sensible way to achieve this but perhaps I am missing something 🧐.
I will be very glad for any suggestions on how to achieve the same result, since I would be happy to remove this boilerplate in my own project myself 😅!</p>

<h3 id="coming-next-">Coming Next 🔜</h3>
<p>In next article we will take a look at how to create a Spotlight-like hotkey window, stay tuned!</p>]]></content><author><name>Daniel Arden</name><email>me@danielarden.com</email></author><category term="SwiftUI" /><category term="macOS" /><summary type="html"><![CDATA[Let's take a look at what are our options to remove the toolbar in macOS. Despite many new APIs introduced in WWDC 2024, it turned out to be rather tricky.]]></summary></entry><entry><title type="html">👋 Hello Swift!</title><link href="http://localhost:4000/posts/hello-swift/" rel="alternate" type="text/html" title="👋 Hello Swift!" /><published>2024-08-04T00:00:00+02:00</published><updated>2024-08-04T00:00:00+02:00</updated><id>http://localhost:4000/posts/hello-swift</id><content type="html" xml:base="http://localhost:4000/posts/hello-swift/"><![CDATA[<h2 id="hey-and-welcome">Hey and welcome!</h2>

<p>For quite some time now I have been thinking about starting my own blog about Swift development but I have never felt confident enough. However, over the past 5 years of developing in Swift, even though I admittedly sometimes do smart things like dividng by zero and expecting my app to work 🤡, I have realized that I do have valuable things to share, and that’s exactly why I am starting this blog!</p>

<p>My intention is to write articles somewhat periodically, ideally at least twice a month 🤞.</p>

<p>If you have any suggestions/comments regarding:</p>
<ul>
  <li>🌱 how to make the blog better to provide better learning value</li>
  <li>🐛 a bug in code</li>
  <li>⚠️ mistakes in text</li>
</ul>

<p>or just want to chat about 👨‍💻 coding, ✈️ travelling, 🎻 music or anything else really, feel free to hit me up! (Links are down below in the footer)</p>

<p>In a perfect world scenario this blog would serve as a handy learning source for beginners and advanced  iOS, macOS, watchOS and visionOS developers!</p>

<p>Big thanks to my friend and a 🌟 stellar Swift developer <a href="https://danielsaidi.com/">Daniel Saidi</a> for planting a seed in my head that I could start my own blog, and to my friends for supporting me that I actually should do it.</p>

<p>Let’s learn together 🚀 !</p>

<p><sub>The blog name feels cringy, but definitely better than something like <em>Daniel’s Swift Oddysey</em> 😄<sub></sub></sub></p>]]></content><author><name>Daniel Arden</name><email>me@danielarden.com</email></author><summary type="html"><![CDATA[Welcome to my new blog where I write about Swift, iOS, macOS, watchOS and visionOS programming.]]></summary></entry></feed>