Jekyll2019-03-22T20:55:17+00:00https://cyrilcathala.github.io/feed.xmlCyril CathalaCyril Cathala's blog to talk about mobile, web, cloud & geek stuff, what else?
Cyril Cathalacyril@cathala.orgBest way to use SafeAreaInsets2019-02-11T00:00:00+00:002019-02-11T00:00:00+00:00https://cyrilcathala.github.io/2019/02/11/safeareatinsets<p>Using a <a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/platform/ios/page-safe-area-layout">safe area</a> is a great way to support the iPhone X* and safely avoid overlaps with the virtual home button or the notch.</p>
<p>In Xamarin.Forms, the easy way is to set a safe area using the platform specific API of a <code class="highlighter-rouge">Page</code>:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">Xamarin.Forms.PlatformConfiguration</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Xamarin.Forms.PlatformConfiguration.iOSSpecific</span><span class="p">;</span>
<span class="k">public</span> <span class="nf">MyPage</span><span class="p">()</span>
<span class="p">{</span>
<span class="nf">InitializeComponent</span><span class="p">();</span>
<span class="n">On</span><span class="p"><</span><span class="n">iOS</span><span class="p">>().</span><span class="nf">SetUseSafeArea</span><span class="p">(</span><span class="k">true</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Ok, pretty straightforward. But suppose you want to apply a gradient background underneath the status bar and the virtual home button:</p>
<div style="display:flex; justify-content:space-evenly;">
<figure>
<img src="/assets/img/2019-02-11-safeareainsets/screen.png" height="400" />
<figcaption>What we want.</figcaption>
</figure>
<figure>
<img src="/assets/img/2019-02-11-safeareainsets/screen-safearea.png" height="400" />
<figcaption>What we get with SetUseSafeArea(true).</figcaption>
</figure>
</div>
<p>To achieve that, we’d rather play with the <code class="highlighter-rouge">On<iOS>().SafeAreaInsets()</code> API. Following the documentation, we should use the <code class="highlighter-rouge">Page.OnAppearing</code> method:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><ContentPage></span>
<span class="nt"><controls:GradientView</span> <span class="na">StartColor=</span><span class="s">"#2196F3"</span>
<span class="na">EndColor=</span><span class="s">"#E10AEB"</span> <span class="nt">/></span>
<span class="nt"><Grid</span> <span class="na">x:Name=</span><span class="s">"MainContainer"</span><span class="nt">></span>
<span class="c"><!-- Content --></span>
<span class="nt"></Grid></span>
<span class="nt"></ContentPage></span>
</code></pre></div></div>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnAppearing</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">base</span><span class="p">.</span><span class="nf">OnAppearing</span><span class="p">();</span>
<span class="n">MainContainer</span><span class="p">.</span><span class="n">Margin</span> <span class="p">=</span> <span class="n">On</span><span class="p"><</span><span class="n">Xamarin</span><span class="p">.</span><span class="n">Forms</span><span class="p">.</span><span class="n">PlatformConfiguration</span><span class="p">.</span><span class="n">iOS</span><span class="p">>().</span><span class="nf">SafeAreaInsets</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Simple, right? Let’s see it in action:</p>
<p><img src="/assets/img/2019-02-11-safeareainsets/01.gif" height="400" /></p>
<p>It does work but it is not perfect because the margin gets applied <strong>after</strong> the navigation animation. Let’s review a few solutions!</p>
<h2 id="option-1-layoutchanged">Option #1: LayoutChanged</h2>
<p><code class="highlighter-rouge">LayoutChanged</code> is an event raised when bounds have changed. It is usely first raised when the view is mounted, way before the <code class="highlighter-rouge">OnAppearing</code>.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="nf">MyPage</span><span class="p">()</span>
<span class="p">{</span>
<span class="nf">InitializeComponent</span><span class="p">();</span>
<span class="n">LayoutChanged</span> <span class="p">+=</span> <span class="n">MyPage_LayoutChanged</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">MyPage_LayoutChanged</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">EventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// We only need the first call, so we unsubscribe immediately</span>
<span class="n">LayoutChanged</span> <span class="p">-=</span> <span class="n">MyPage_LayoutChanged</span><span class="p">;</span>
<span class="n">MainContainer</span><span class="p">.</span><span class="n">Margin</span> <span class="p">=</span> <span class="n">On</span><span class="p"><</span><span class="n">Xamarin</span><span class="p">.</span><span class="n">Forms</span><span class="p">.</span><span class="n">PlatformConfiguration</span><span class="p">.</span><span class="n">iOS</span><span class="p">>().</span><span class="nf">SafeAreaInsets</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Nice try, but not good enough:</p>
<ul>
<li>The inset is correctly set for the first Page appearing, but for some reason, the following pages get an empty inset.</li>
<li>You don’t get notified when the value changes (e.g. orientation).</li>
</ul>
<h2 id="option-2-property-changed">Option #2: Property changed</h2>
<p>We have a way to be notified when a value changes: <code class="highlighter-rouge">PropertyChanged</code>. Let’s use it for the <code class="highlighter-rouge">Page</code>!</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="nf">MyPage</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">PropertyChanged</span> <span class="p">+=</span> <span class="n">ContentPageBase_PropertyChanged</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">ContentPageBase_PropertyChanged</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">PropertyChangedEventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">PropertyName</span> <span class="p">==</span> <span class="s">"SafeAreaInsets"</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">MainContainer</span><span class="p">.</span><span class="n">Margin</span> <span class="p">=</span> <span class="n">On</span><span class="p"><</span><span class="n">Xamarin</span><span class="p">.</span><span class="n">Forms</span><span class="p">.</span><span class="n">PlatformConfiguration</span><span class="p">.</span><span class="n">iOS</span><span class="p">>().</span><span class="nf">SafeAreaInsets</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>If the phone is rotated or a split window is created, the page would be notified. We can then create a <code class="highlighter-rouge">ContentPageBase</code> and use this technique from every page.</p>
<p>But what if we need this value from a <code class="highlighter-rouge">ContentView</code> somewhere else in the app?</p>
<h2 id="option-3-property-changed--dynamic-resource">Option #3: Property changed + Dynamic resource</h2>
<p>There is an easy way to propagate a value in the app: the <a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/styles/xaml/dynamic"><code class="highlighter-rouge">DynamicResource</code></a> markup. It is just like an usual <code class="highlighter-rouge">StaticResource</code>, except we can change the value and any XAML bound to its value is changed.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="nf">MyPage</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">PropertyChanged</span> <span class="p">+=</span> <span class="n">ContentPageBase_PropertyChanged</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">ContentPageBase_PropertyChanged</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">PropertyChangedEventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">PropertyName</span> <span class="p">==</span> <span class="s">"SafeAreaInsets"</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">safeAreaInsets</span> <span class="p">=</span> <span class="n">On</span><span class="p"><</span><span class="n">Xamarin</span><span class="p">.</span><span class="n">Forms</span><span class="p">.</span><span class="n">PlatformConfiguration</span><span class="p">.</span><span class="n">iOS</span><span class="p">>().</span><span class="nf">SafeAreaInsets</span><span class="p">();</span>
<span class="n">Application</span><span class="p">.</span><span class="n">Current</span><span class="p">.</span><span class="n">Resources</span><span class="p">[</span><span class="s">"SafeAreaInsets"</span><span class="p">]</span> <span class="p">=</span> <span class="n">safeAreaInsets</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><Application></span>
<span class="nt"><Application.Resources></span>
<span class="nt"><Thickness</span> <span class="na">x:Key=</span><span class="s">"SafeAreaInsets"</span><span class="nt">/></span>
<span class="nt"></Application.Resources></span>
<span class="nt"></Application></span>
</code></pre></div></div>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><Grid></span>
<span class="nt"><controls:GradientView</span> <span class="na">StartColor=</span><span class="s">"{StaticResource PrimaryColor}"</span>
<span class="na">EndColor=</span><span class="s">"{StaticResource SecondaryColor}"</span> <span class="nt">/></span>
<span class="nt"><Grid</span> <span class="na">Margin=</span><span class="s">"{DynamicResource SafeAreaInsets}"</span><span class="nt">></span>
<span class="c"><!-- ... --></span>
<span class="nt"></Grid></span>
<span class="nt"></Grid></span>
</code></pre></div></div>
<p>Nice & clean! Let’s review the result:</p>
<p><img src="/assets/img/2019-02-11-safeareainsets/02.gif" height="400" /></p>
<h2 id="conclusion">Conclusion</h2>
<p>We saw a few tricks along the way. Some can feel quite “hacky”, but the right solution seems to be a combination of <code class="highlighter-rouge">PropertyChanged</code> and <code class="highlighter-rouge">DynamicResource</code>.</p>Cyril Cathalacyril@cathala.orgUsing a safe area is a great way to support the iPhone X* and safely avoid overlaps with the virtual home button or the notch.(Re)Launching my blog2019-01-26T00:00:00+00:002019-01-26T00:00:00+00:00https://cyrilcathala.github.io/2019/01/26/launch-site<p>I finally got around to putting this website together.</p>
<p>This is powered by Jekyll and I can use Markdown to author my posts, really awesome and easy to post, I hope it’ll help me post more :)</p>Cyril Cathalacyril@cathala.orgI finally got around to putting this website together.