<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>SwiftUI on Collin Daugherty</title>
    <link>https://collindaugherty.com/tags/swiftui/</link>
    <description>Recent content in SwiftUI on Collin Daugherty</description>
    <image>
      <title>Collin Daugherty</title>
      <url>https://collindaugherty.com/papermod-cover.png</url>
      <link>https://collindaugherty.com/papermod-cover.png</link>
    </image>
    <generator>Hugo -- gohugo.io</generator>
    <lastBuildDate>Wed, 22 Jun 2022 00:00:00 +0000</lastBuildDate><atom:link href="https://collindaugherty.com/tags/swiftui/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>How to override system appearance in SwiftUI</title>
      <link>https://collindaugherty.com/articles/override-system-appearance-swiftui/</link>
      <pubDate>Wed, 22 Jun 2022 00:00:00 +0000</pubDate>
      
      <guid>https://collindaugherty.com/articles/override-system-appearance-swiftui/</guid>
      <description>An under-utilized feature I always appreciate when I see it is the ability to select an appearance for an app independent of the system appearance.
If you search how to set the color scheme of a view in SwiftUI, you&amp;rsquo;ll probably come across preferredColorScheme which looks great but doesn&amp;rsquo;t work how we&amp;rsquo;d like for this feature. Instead, we are going to make use of overrideUserInterfaceStyle
We&amp;rsquo;ll start off by creating our AppearanceOptions enum and make it conform to String and CaseIterable for use in our form later on.</description>
      <content:encoded><![CDATA[<p>An under-utilized feature I always appreciate when I see it is the ability to select an appearance for an app independent of the system appearance.</p>
<p>If you search how to set the color scheme of a view in SwiftUI, you&rsquo;ll probably come across <a href="https://developer.apple.com/documentation/swiftui/view/preferredcolorscheme(_:)"><code>preferredColorScheme</code></a> which looks great but doesn&rsquo;t work how we&rsquo;d like for this feature. Instead, we are going to make use of <a href="https://developer.apple.com/documentation/uikit/uiview/3238086-overrideuserinterfacestyle"><code>overrideUserInterfaceStyle</code></a></p>
<p>We&rsquo;ll start off by creating our <code>AppearanceOptions</code> enum and make it conform to <code>String</code> and <code>CaseIterable</code> for use in our form later on.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Swift" data-lang="Swift"><span class="line"><span class="cl"><span class="kd">enum</span> <span class="nc">AppearanceOptions</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span> <span class="n">CaseIterable</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="n">system</span><span class="p">,</span> <span class="n">light</span><span class="p">,</span> <span class="n">dark</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>And then we&rsquo;ll create the most important part, our <code>AppearanceController</code> and set the default appearance to <code>.System</code> in <code>UserDefaults</code> using the <code>@AppStorage</code> property wrapper</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Swift" data-lang="Swift"><span class="line"><span class="cl"><span class="kd">class</span> <span class="nc">AppearanceController</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">static</span> <span class="kd">let</span> <span class="nv">shared</span> <span class="p">=</span> <span class="n">AppearanceController</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">@</span><span class="n">AppStorage</span><span class="p">(</span><span class="s">&#34;appAppearance&#34;</span><span class="p">)</span> <span class="kd">var</span> <span class="nv">appAppearance</span><span class="p">:</span> <span class="n">AppearanceOptions</span> <span class="p">=</span> <span class="p">.</span><span class="n">system</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Next we&rsquo;ll need to get the correct <code>UIUserInterfaceStyle</code> to use with <code>overrideUserInterfaceStyle</code> so we&rsquo;ll create a computed property in our <code>AppearanceController</code> that checks our appearance saved in <code>UserDefaults</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Swift" data-lang="Swift"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nv">appearance</span><span class="p">:</span> <span class="n">UIUserInterfaceStyle</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">switch</span> <span class="n">appAppearance</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="p">.</span><span class="n">system</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">.</span><span class="n">unspecified</span> <span class="c1">// Uses appearance set by user in Settings</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="p">.</span><span class="n">light</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">.</span><span class="n">light</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="p">.</span><span class="n">dark</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">.</span><span class="n">dark</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>And finally we&rsquo;ll create our <code>setAppearance</code> function.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Swift" data-lang="Swift"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">setAppearance</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nv">windowScene</span> <span class="p">=</span> <span class="n">UIApplication</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">connectedScenes</span><span class="p">.</span><span class="bp">first</span> <span class="k">as</span><span class="p">?</span> <span class="n">UIWindowScene</span>
</span></span><span class="line"><span class="cl">    <span class="k">guard</span> <span class="kd">let</span> <span class="nv">window</span> <span class="p">=</span> <span class="n">windowScene</span><span class="p">?.</span><span class="n">windows</span><span class="p">.</span><span class="bp">first</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">window</span><span class="p">.</span><span class="n">overrideUserInterfaceStyle</span> <span class="p">=</span> <span class="n">appearance</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Now in <code>ContentView</code> we&rsquo;ll create a Picker for our appearance options.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Swift" data-lang="Swift"><span class="line"><span class="cl"><span class="kd">struct</span> <span class="nc">ContentView</span><span class="p">:</span> <span class="n">View</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">@</span><span class="n">AppStorage</span><span class="p">(</span><span class="s">&#34;appAppearance&#34;</span><span class="p">)</span> <span class="kd">var</span> <span class="nv">appAppearance</span><span class="p">:</span> <span class="n">AppearanceOptions</span> <span class="p">=</span> <span class="p">.</span><span class="n">system</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nv">body</span><span class="p">:</span> <span class="n">some</span> <span class="n">View</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Form</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">Picker</span><span class="p">(</span><span class="s">&#34;Appearance&#34;</span><span class="p">,</span> <span class="n">selection</span><span class="p">:</span> <span class="err">$</span><span class="n">appAppearance</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// This is where CaseIterable comes into play, allowing us to loop over our AppearanceOptions enum </span>
</span></span><span class="line"><span class="cl">                <span class="n">ForEach</span><span class="p">(</span><span class="n">AppearanceOptions</span><span class="p">.</span><span class="n">allCases</span><span class="p">,</span> <span class="n">id</span><span class="p">:</span> <span class="err">\</span><span class="p">.</span><span class="kc">self</span><span class="p">)</span> <span class="p">{</span> <span class="n">option</span> <span class="k">in</span>
</span></span><span class="line"><span class="cl">                    <span class="n">Text</span><span class="p">(</span><span class="n">option</span><span class="p">.</span><span class="n">rawValue</span><span class="p">.</span><span class="n">capitalized</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">.</span><span class="n">pickerStyle</span><span class="p">(.</span><span class="n">inline</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>On that Picker we&rsquo;ll add an <code>onChange</code> modifier to call <code>setAppearance</code> when the user changes the selected appearance.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Swift" data-lang="Swift"><span class="line"><span class="cl"><span class="p">.</span><span class="n">onChange</span><span class="p">(</span><span class="n">of</span><span class="p">:</span> <span class="n">appAppearance</span><span class="p">)</span> <span class="p">{</span> <span class="kc">_</span> <span class="k">in</span>
</span></span><span class="line"><span class="cl">    <span class="n">AppearanceController</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">setAppearance</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Now if you test your app, you should be able to set any color scheme, regardless of your system settings. But you might notice a problem if you close the app and reopen it.</p>
<p>If you selected <code>Dark</code> and your system appearance is set to <code>Light</code> (or vice-versa) you&rsquo;ll see that the Picker reflects your selection but the appearance hasn&rsquo;t changed. That&rsquo;s because we forgot an important step: you need to call <code>setAppearance</code> when the app loads.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Swift" data-lang="Swift"><span class="line"><span class="cl"><span class="p">@</span><span class="n">main</span>
</span></span><span class="line"><span class="cl"><span class="kd">struct</span> <span class="nc">AppearanceDemoApp</span><span class="p">:</span> <span class="n">App</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nv">body</span><span class="p">:</span> <span class="n">some</span> <span class="n">Scene</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">WindowGroup</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">ContentView</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">                <span class="p">.</span><span class="n">task</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">AppearanceController</span><span class="p">.</span><span class="n">shared</span><span class="p">.</span><span class="n">setAppearance</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div>]]></content:encoded>
    </item>
    
  </channel>
</rss>
