Jekyll2021-01-03T05:55:56+00:00https://thomas-sivilay.github.io//morningswiftui.github.io/feed.xmlMorning SwiftUIArticles and blog posts about SwiftUI or anything related to iOS and Apple ecosystem.Conditionally use VStack in iOS 13 or LazyVStack in iOS 142020-07-01T07:38:05+00:002020-07-01T07:38:05+00:00https://thomas-sivilay.github.io//morningswiftui.github.io/swiftui/2020/07/01/conditionally-use-vstack-in-ios-13-or-lazyvstack-in-ios-14<p><strong>WWDC2020</strong> offered a new suite of Views, one interesting one is <code class="language-plaintext highlighter-rouge">LazyVStack</code> which allow us to delay the initialization of some content only when needed. Unfortunately it’s only available in iOS 14. <strong>What if you still want to offer an app that support iOS 13?</strong></p>
<p>You can easily achieve this by creating your own wrapper. Here’s how we can offer either a <code class="language-plaintext highlighter-rouge">VStack</code> in iOS 13 and the new shiny <code class="language-plaintext highlighter-rouge">LazyVStack</code> in iOS 14:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">struct</span> <span class="kt">MyVStack</span><span class="o"><</span><span class="kt">Content</span><span class="o">></span><span class="p">:</span> <span class="kt">View</span> <span class="k">where</span> <span class="kt">Content</span> <span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">content</span><span class="p">:</span> <span class="p">()</span> <span class="o">-></span> <span class="kt">Content</span>
<span class="k">let</span> <span class="nv">alignment</span><span class="p">:</span> <span class="kt">HorizontalAlignment</span>
<span class="k">let</span> <span class="nv">spacing</span><span class="p">:</span> <span class="kt">CGFloat</span><span class="p">?</span>
<span class="kd">public</span> <span class="nf">init</span><span class="p">(</span><span class="nv">alignment</span><span class="p">:</span> <span class="kt">HorizontalAlignment</span> <span class="o">=</span> <span class="o">.</span><span class="n">center</span><span class="p">,</span> <span class="nv">spacing</span><span class="p">:</span> <span class="kt">CGFloat</span><span class="p">?</span> <span class="o">=</span> <span class="kc">nil</span><span class="p">,</span> <span class="kd">@ViewBuilder</span> <span class="nv">content</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="p">()</span> <span class="o">-></span> <span class="kt">Content</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">content</span> <span class="o">=</span> <span class="n">content</span>
<span class="k">self</span><span class="o">.</span><span class="n">alignment</span> <span class="o">=</span> <span class="n">alignment</span>
<span class="k">self</span><span class="o">.</span><span class="n">spacing</span> <span class="o">=</span> <span class="n">spacing</span>
<span class="p">}</span>
<span class="kd">@ViewBuilder</span> <span class="kd">public</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">if</span> <span class="kd">#available(iOS 14.0, *)</span> <span class="p">{</span>
<span class="kt">LazyVStack</span><span class="p">(</span><span class="nv">alignment</span><span class="p">:</span> <span class="n">alignment</span><span class="p">,</span> <span class="nv">spacing</span><span class="p">:</span> <span class="n">spacing</span><span class="p">,</span> <span class="nv">content</span><span class="p">:</span> <span class="k">self</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="kt">VStack</span><span class="p">(</span><span class="nv">alignment</span><span class="p">:</span> <span class="n">alignment</span><span class="p">,</span> <span class="nv">spacing</span><span class="p">:</span> <span class="n">spacing</span><span class="p">,</span> <span class="nv">content</span><span class="p">:</span> <span class="k">self</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>With this quick trick, we now can use <code class="language-plaintext highlighter-rouge">MyVStack</code> following the same API as Apple provides us. We still have the ability to adjust the horizontal adjustment, the spacing and to pass the content in a similar shape.</p>
<p>Of course, this isn’t limited to <code class="language-plaintext highlighter-rouge">VStack</code> and <code class="language-plaintext highlighter-rouge">LazyVStack</code>. We could extend this to <code class="language-plaintext highlighter-rouge">HStack</code> and his <code class="language-plaintext highlighter-rouge">LazyHStack</code> counter part.</p>WWDC2020 offered a new suite of Views, one interesting one is LazyVStack which allow us to delay the initialization of some content only when needed. Unfortunately it’s only available in iOS 14. What if you still want to offer an app that support iOS 13?Display multiple Sheet in a SwiftUI View2020-05-13T07:38:05+00:002020-05-13T07:38:05+00:00https://thomas-sivilay.github.io//morningswiftui.github.io/swiftui/2020/05/13/display-multiple-sheet-in-a-swiftui-view<p>SwiftUI views has a <strong>ViewModifier</strong> <code class="language-plaintext highlighter-rouge">.sheet</code> that allows us to present a modal view based on a state .isPresented which is a Bool. <strong>What happened when we need to present more than one sheet?</strong></p>
<h2 id="you-cant-use-multiple-sheet-view-modifiers">You can’t use multiple <code class="language-plaintext highlighter-rouge">.sheet</code> view modifiers</h2>
<p>One naive approach could be to structure our view with multiple view modifiers, have multiple <code class="language-plaintext highlighter-rouge">.sheet</code> with multiple Boolean to drive when and what needs to be displayed.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">MyView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="kd">@State</span> <span class="k">var</span> <span class="nv">showSheetA</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">false</span>
<span class="kd">@State</span> <span class="k">var</span> <span class="nv">showSheetB</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">false</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="c1">// This doesn’t work</span>
<span class="kt">Text</span><span class="p">(</span><span class="s">"My view"</span><span class="p">)</span>
<span class="o">.</span><span class="nf">sheet</span><span class="p">(</span><span class="nv">isPresented</span><span class="p">:</span> <span class="err">$</span><span class="n">showSheetA</span><span class="p">,</span> <span class="nv">content</span><span class="p">:</span> <span class="p">{</span> <span class="kt">Text</span><span class="p">(</span><span class="s">"Sheet A"</span><span class="p">)</span> <span class="p">})</span>
<span class="o">.</span><span class="nf">sheet</span><span class="p">(</span><span class="nv">isPresented</span><span class="p">:</span> <span class="err">$</span><span class="n">showSheetB</span><span class="p">,</span> <span class="nv">content</span><span class="p">:</span> <span class="p">{</span> <span class="kt">Text</span><span class="p">(</span><span class="s">"Sheet B"</span><span class="p">)</span> <span class="p">})</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>With this approach when <code class="language-plaintext highlighter-rouge">showSheetB</code> is true, we see a modal that display the content <code class="language-plaintext highlighter-rouge">Text("Sheet B")</code> but when <code class="language-plaintext highlighter-rouge">showSheetA</code> is true nothing happens. <strong>Only the last <code class="language-plaintext highlighter-rouge">.sheet</code> view modifier will be used.</strong></p>
<p>In the case where <code class="language-plaintext highlighter-rouge">showSheetA</code> and <code class="language-plaintext highlighter-rouge">showSheetB</code> are both true, we might apply the first modifier but then the second similar view modifier will then replace what has been done previously.</p>
<h2 id="one-sheet-multiple-content">One sheet, multiple content</h2>
<p>One solution is to have only one view modifier but display different content based on our needs.</p>
<p>Again, the simplest approach could be to introduce another state and use more booleans.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">MyView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="kd">@State</span> <span class="k">var</span> <span class="nv">showSheet</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">false</span>
<span class="kd">@State</span> <span class="k">var</span> <span class="nv">showA</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">false</span>
<span class="kd">@State</span> <span class="k">var</span> <span class="nv">showB</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">false</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="c1">// This works but is it elegant?</span>
<span class="kt">Text</span><span class="p">(</span><span class="s">"My view"</span><span class="p">)</span>
<span class="o">.</span><span class="nf">sheet</span><span class="p">(</span><span class="nv">isPresented</span><span class="p">:</span> <span class="err">$</span><span class="n">showSheet</span><span class="p">,</span> <span class="nv">content</span><span class="p">:</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">sheet</span> <span class="p">})</span>
<span class="p">}</span>
<span class="kd">private</span> <span class="k">var</span> <span class="nv">sheet</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">showA</span> <span class="p">{</span>
<span class="kt">Text</span><span class="p">(</span><span class="s">"Sheet A"</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">showB</span> <span class="p">{</span>
<span class="kt">Text</span><span class="p">(</span><span class="s">"Sheet B"</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="c1">// Of course that will never happen...</span>
<span class="kt">EmptyView</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Since this works, we can try to improve it. <strong>There must be a more elegant and safer approach</strong> that will represent better the state of the view. We probably should avoid all those booleans and find a data structure that represent well our problem.</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">ActiveSheet</span><span class="p">:</span> <span class="kt">ObservableObject</span> <span class="p">{</span>
<span class="kd">enum</span> <span class="kt">Kind</span> <span class="p">{</span>
<span class="k">case</span> <span class="n">a</span>
<span class="k">case</span> <span class="n">b</span>
<span class="k">case</span> <span class="k">none</span>
<span class="p">}</span>
<span class="kd">@Published</span> <span class="k">var</span> <span class="nv">kind</span><span class="p">:</span> <span class="kt">Kind</span> <span class="o">=</span> <span class="o">.</span><span class="k">none</span> <span class="p">{</span>
<span class="k">didSet</span> <span class="p">{</span> <span class="n">showSheet</span> <span class="o">=</span> <span class="n">kind</span> <span class="o">!=</span> <span class="o">.</span><span class="k">none</span> <span class="p">}</span>
<span class="p">}</span>
<span class="kd">@Published</span> <span class="k">var</span> <span class="nv">showSheet</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">false</span>
<span class="p">}</span>
<span class="kd">struct</span> <span class="kt">MyView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="kd">@ObservedObject</span> <span class="k">var</span> <span class="nv">activeSheet</span><span class="p">:</span> <span class="kt">ActiveSheet</span> <span class="o">=</span> <span class="kt">ActiveSheet</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">Text</span><span class="p">(</span><span class="s">"My view"</span><span class="p">)</span>
<span class="o">.</span><span class="nf">sheet</span><span class="p">(</span><span class="nv">isPresented</span><span class="p">:</span> <span class="k">self</span><span class="o">.</span><span class="err">$</span><span class="n">activeSheet</span><span class="o">.</span><span class="n">showSheet</span><span class="p">,</span> <span class="nv">content</span><span class="p">:</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">sheet</span> <span class="p">})</span>
<span class="p">}</span>
<span class="kd">private</span> <span class="k">var</span> <span class="nv">sheet</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
<span class="k">switch</span> <span class="n">activeSheet</span><span class="o">.</span><span class="n">kind</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">none</span><span class="p">:</span> <span class="k">return</span> <span class="kt">AnyView</span><span class="p">(</span><span class="kt">EmptyView</span><span class="p">())</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">a</span><span class="p">:</span> <span class="k">return</span> <span class="kt">AnyView</span><span class="p">(</span><span class="kt">Text</span><span class="p">(</span><span class="s">"Sheet a"</span><span class="p">))</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">b</span><span class="p">:</span> <span class="k">return</span> <span class="kt">AnyView</span><span class="p">(</span><span class="kt">Text</span><span class="p">(</span><span class="s">"Sheet b"</span><span class="p">))</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This way, we hold all different kind of sheets in our SwiftUI View. It gives <strong>more clarity and scale better</strong> when we will need to add more kind of sheets.</p>
<p>For example, I had to implement a sheet that will display itself given a URL of a file. All I had to do is whenever the file was ready: <code class="language-plaintext highlighter-rouge">activeSheet.kind = .export(url: url)</code> and the view modifier that we set up declaratively will now present a new content.</p>
<p>We could probably still improve this, somehow removing the <code class="language-plaintext highlighter-rouge">@Published var showSheet: Bool = false</code> but that’s it for today.</p>SwiftUI views has a ViewModifier .sheet that allows us to present a modal view based on a state .isPresented which is a Bool. What happened when we need to present more than one sheet?Build layout programmatically or visually with SwiftUI2020-04-12T07:38:05+00:002020-04-12T07:38:05+00:00https://thomas-sivilay.github.io//morningswiftui.github.io/swiftui/2020/04/12/build-layout-programmatically-or-visually-with-swiftui<p><strong>How do you build your layout?</strong> Are you using auto layout? Frame calculations? Interface builder? I think we’ve all try different approaches and they all come with tradeoffs. It’s even harder when working within a group of people because sometimes the choice has been made out of common beliefs that became a convention. I believe it’s difficult to satisfy everyone because it’s just a matter of taste and preference.. until SwiftUI?</p>
<p><img src="/morningswiftui.github.io//assets/images/20200412-poll.png" alt="Poll" /></p>
<p>Out of the numerous ways of building a layout I listed, I think we could group them into two different categories. Either programmatically or visual? Which might be the same as, does your brain prefer to read/write or to picture something?</p>
<h2 id="programmatically">Programmatically</h2>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">HStack</span><span class="p">(</span><span class="nv">spacing</span><span class="p">:</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">ZStack</span> <span class="p">{</span>
<span class="kt">Circle</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">32</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">32</span><span class="p">)</span>
<span class="o">.</span><span class="nf">foregroundColor</span><span class="p">(</span><span class="n">type</span><span class="o">.</span><span class="n">backgroundColor</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="n">type</span><span class="o">.</span><span class="n">iconName</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">20</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">20</span><span class="p">)</span>
<span class="o">.</span><span class="nf">foregroundColor</span><span class="p">(</span><span class="n">type</span><span class="o">.</span><span class="n">iconColor</span><span class="p">)</span>
<span class="p">}</span>
<span class="kt">Text</span><span class="p">(</span><span class="n">type</span><span class="o">.</span><span class="n">title</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">headline</span><span class="p">)</span>
<span class="o">.</span><span class="nf">foregroundColor</span><span class="p">(</span><span class="o">.</span><span class="n">secondary</span><span class="p">)</span>
<span class="o">.</span><span class="nf">multilineTextAlignment</span><span class="p">(</span><span class="o">.</span><span class="n">center</span><span class="p">)</span>
<span class="o">.</span><span class="nf">lineLimit</span><span class="p">(</span><span class="mi">1</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">horizontal</span><span class="p">,</span> <span class="mi">12</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>There is less efforts required into translating the SwiftUI syntax into a visual representation of it. It is easier to grasp the intention of a block of code. So it’s definitely programmatically! <strong>We write and read from there.</strong> After all, that’s how Apple described SwiftUI.</p>
<h2 id="visually">Visually</h2>
<p>Even with an improved syntax, <em>Apple</em> didn’t stop there and instead, decided to give us <code class="language-plaintext highlighter-rouge">PreviewProvider</code>. With few more lines of codes, that also allow us to inject different variables we can have a live visual representation of what we can read and write.</p>
<p>But that’s not it… I almost forgot about it because of habits of doing layout programmatically:</p>
<ul>
<li>in the <strong>Canvas</strong> (<em>the part of Xcode where we have a live representation</em>) you can select existing Views and with the inspector, like on interface builder, you can adjust values and add more modifiers to them</li>
<li>additionally, if you want to add new element to your View, you can drag and drop elements from the <strong>Library</strong> button (+) in the toolbar.</li>
</ul>
<p><img src="/morningswiftui.github.io//assets/images/20200412-visual.png" alt="Visual" /></p>
<h2 id="the-best-of-both-worlds">The best of both worlds</h2>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">How do you build user interface with SwiftUI? <a href="https://twitter.com/hashtag/SwiftUI?src=hash&ref_src=twsrc%5Etfw">#SwiftUI</a><a href="https://twitter.com/hashtag/swift?src=hash&ref_src=twsrc%5Etfw">#swift</a></p>— Thomas Sivilay (@thomassivilay) <a href="https://twitter.com/thomassivilay/status/1249123120683929602?ref_src=twsrc%5Etfw">April 11, 2020</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p><strong>Providing both options gives flexibility and reduce trade-offs</strong>, it allow us to choose the best tool for based on our skills, preferences and a situation. If you prefer the visual way, it doesn’t force the others that prefers the programmatically way to read something else than code (like a XIB). The source of truth is still code because while updating visually, it generates the required code.</p>
<p>Even more than the best of both worlds, I believe with SwiftUI’s principles, the productivity is increased with the ability to quickly jump to a specific view, quickly iterate on it, see live update with injected data/state/environment. The feedback loop is shorter, we don’t need to navigate to a particular view to verify that our tiny change is accurate, we have some kind of hot reload.</p>How do you build your layout? Are you using auto layout? Frame calculations? Interface builder? I think we’ve all try different approaches and they all come with tradeoffs. It’s even harder when working within a group of people because sometimes the choice has been made out of common beliefs that became a convention. I believe it’s difficult to satisfy everyone because it’s just a matter of taste and preference.. until SwiftUI?Swift Package Manager workflow compared to Cocoapods2020-04-10T07:38:05+00:002020-04-10T07:38:05+00:00https://thomas-sivilay.github.io//morningswiftui.github.io/swift/2020/04/10/swift-package-manager-workflow-compared-to-cocoapods<p>For many years I’ve been relying on Cocoapods as a dependency manager, which works fine and it’s also widely adopted. But with Xcode 11, SPM now supports iOS targets so I decided to give it a try.</p>
<p>There’s already many article about how to use SPM, so we will not focus on this, but I wanted to share how the workflow/user experience differentiate between Cocoapods and SPM. To illustrate this, we will take the use case of creating a framework that we maintain and we use it from a main iOS application.</p>
<h2 id="setting-up-and-creating-a-framework">Setting up and creating a framework</h2>
<h3 id="hosting">Hosting</h3>
<p>For both cases, we will first need to host the pod or the package to a repository (e.g: https://github.com/new). Once it’s ready, we can clone the repo from Xcode or your CLI.</p>
<p><img src="/morningswiftui.github.io//assets/images/20200410-1.png" alt="1" /></p>
<p>When using SPM you can actually create the framework as a project first and directly from Xcode create the repository. You will have to enable Source Control from Xcode as a prerequisite. I usually just go with Github, it’s simple enough and you might have some templates ready to go.</p>
<h3 id="cocoapods">Cocoapods</h3>
<p>Before being able to use Cocoapods, you have to install it first and maintain an up to date version. After that, you can create a pod. <a href="https://guides.cocoapods.org/making/using-pod-lib-create">CocoaPods Guides - Using Pod Lib Create</a></p>
<h3 id="spm">SPM</h3>
<p>Starting from Xcode 11, to create your framework, it’s as simple as ⌃⇧⌘N or go to File > New > Swift Package.</p>
<p><img src="/morningswiftui.github.io//assets/images/20200410-2.png" alt="2" /></p>
<h3 id="who-wins">Who wins?</h3>
<p>On one side, you’ve SPM which has a better integration with Xcode but it’s /too?/ simple. On the other side, with Cocoapods, you’ve creation templates and additional set up features. It requires to install an extra tool, it feels a bit more heavy and it requires you to checkout the public master repo which takes more space in your disk.</p>
<h2 id="integrating-the-framework-to-your-main-app">Integrating the framework to your main app</h2>
<p>Fast forwarding a lot, assuming that we already have the pod or the package available (we will cover releasing new changes later), how do we get to use this framework within our main iOS app?</p>
<h3 id="cocoapods-1">Cocoapods</h3>
<p>You’ll need to have a Podfile that will list all the different library you want your project to use, so just run pod init and Cocoapods should generate this file for you.</p>
<p>Within the Podfile, you can specify your framework.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">target</span> <span class="err">‘</span><span class="kt">MainApp</span><span class="err">’</span> <span class="k">do</span>
<span class="n">pod</span> <span class="err">‘</span><span class="kt">MyFramework</span><span class="err">’</span>
<span class="n">end</span>
</code></pre></div></div>
<p>Once specified, you need to run <code class="language-plaintext highlighter-rouge">pod install</code> to set up your project. You are also now constrained to use a Xcode workspace.</p>
<h3 id="spm-1">SPM</h3>
<p>From your Main App project, in Xcode you can go to File > Swift Packages > Add Package Depedency… and give the link to your repo.</p>
<p><img src="/morningswiftui.github.io//assets/images/20200410-3.png" alt="3" /></p>
<p>This will add a new section /Swift Package Dependencies/ in the Project navigator and a new tab in your project settings.</p>
<p><img src="/morningswiftui.github.io//assets/images/20200410-4.png" alt="4" /></p>
<h3 id="who-wins-1">Who wins?</h3>
<p>SPM leverage a full integration with Xcode. We still have a Package.swift file that we can manipulate or use as a git diff output for for code review. Cocoapods has a lot to offer with Podfile syntaxes, you can be quite creative and use abstract target, shared pods, inherit from different scheme… (CocoaPods Guides - Podfile Syntax Reference). So again, with more features brings more complexity.</p>
<h2 id="adding-changes-to-the-framework">Adding changes to the framework</h2>
<p>What do we need to do when adding or updating code in our framework?</p>
<h3 id="cocoapods-2">Cocoapods</h3>
<p>In the Podfile, we can specify to use a specific path for a pod. After that, you can apply changes from the framework project itself or from your Main App workspace.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">target</span> <span class="err">‘</span><span class="kt">MainApp</span><span class="err">’</span> <span class="k">do</span>
<span class="n">pod</span> <span class="err">‘</span><span class="kt">MyFramework</span><span class="err">’</span><span class="p">,</span> <span class="p">:</span><span class="n">path</span> <span class="o">=></span> <span class="err">‘</span><span class="n">path_to_your_local_version</span><span class="err">’</span>
<span class="n">end</span>
</code></pre></div></div>
<p>When changes are done, we can commit them. You probably need to update the Podfile to not use a local version but a specific commit until you release a new update of the framework.</p>
<ul>
<li>For local changes, use :path</li>
<li>For code-review/while the pod isn’t released yet, use :branch or :commit</li>
<li>After a release, use the version of the release ’1.1.0</li>
</ul>
<h3 id="spm-2">SPM</h3>
<p>Drag and drop your local version of the Library into the .xcodeproj of your main app. It will recognise it, and you can now work directly with it.</p>
<p><img src="/morningswiftui.github.io//assets/images/20200410-5.png" alt="5" /></p>
<p>Files inside /Sources are now accessible and can be updated. When you’re good with your changes, commit and push them to your framework repo.</p>
<p>Before releasing your framework, you might need from the Main App project to specify the commit sha1 or the branch name from your framework repo. You can have access to all Swift Packages from your project setting, double-clicking to your framework should open a dialog that gives you the options to choose a rule.</p>
<p><img src="/morningswiftui.github.io//assets/images/20200410-6.png" alt="6" /></p>
<p><img src="/morningswiftui.github.io//assets/images/20200410-7.png" alt="7" /></p>
<h3 id="who-wins-2">Who wins?</h3>
<p>Not many differences in number of steps, again an advantage is that SPM is integrated within Xcode and provide a UI instead of a raw file. We can argue that using the UI is slower (there’s a way to do it via .xcworkspacedata). Overall, I had some issues with Cocoapods changes that doesn’t applies which I never faced with SPM.</p>
<h2 id="releasing-changes-of-the-framework">Releasing changes of the framework</h2>
<p>Once you’ve all your changes, potentially went through some code review and has been approved all we need is to release and make it available with a new tag and version of the library.</p>
<h3 id="cocoapods-3">Cocoapods</h3>
<p>You’ll need to update the <code class="language-plaintext highlighter-rouge">.podspec</code> to upgrade the version number, verify that it’s valid (pod spec lint Library.podspec) and then get ready to deploy publicly or maybe your private specs repo.</p>
<p>Once it’s deployed, you can tag the new commit that increased the .podspec and push it to your library’s repo.</p>
<p>On your app level you now need to use the latest release. So, that requires to update the Podfile again to point to this new version. Run some pod update Library and you’re finally done.</p>
<h3 id="spm-3">SPM</h3>
<p>By nature, we don’t need to push it to a centralised repo. If you have a tag that reflect the new version number it’s all we need to make it available and discoverable for anyone who has access to your library’s repo.</p>
<p>On your app level, you can either remove the drag and dropped version of your Library (make sure to only remove reference) and it will fallback to find the Swift Package (you might need to update the version).</p>
<p><img src="/morningswiftui.github.io//assets/images/20200410-8.png" alt="8" /></p>
<p>Or, if you already update the Package to point to a specific branch or commit you need to change that back to Version. This can be done from going to your project setting (SPM tab).</p>
<p><img src="/morningswiftui.github.io//assets/images/20200410-9.png" alt="9" /></p>
<h3 id="who-wins-3">Who wins?</h3>
<p>Less steps involved with SPM, just tag the commit you need to make available and it’s technically enough. That’s less steps than from Cocoapods that requires to update the podspec and publish it.</p>
<p>Also pointing to the new release from your App is easier with SPM, it will require less commands and should be faster to be ready. You will not need to fetch and update your Spec repo before getting your Library last version.</p>
<h2 id="conclusion-spm-or-cocoapods">Conclusion: SPM or Cocoapods?</h2>
<p>SPM stands for Swift Package Manager but it could be Simple Package Manager. It’s really convenient that we don’t need an extra tool, that it’s well integrated with Xcode. But that is also one of the downside of SPM, we might not see as many features and evolutions as Cocoapods which provides a good amount of extra features you might want.</p>
<p>At the end, I believe that SPM is a good replacement if your project don’t need special features from Cocoapods. Especially if you work in a smaller team, if your project structure are relatively simple, if you work with frameworks that don’t require frequent updates.</p>
<p>As far as I know, there is one limitation that can be a deal breaker. It seems that it’s going to be lifted soon but we can’t include resources to be shared between your framework and apps.</p>For many years I’ve been relying on Cocoapods as a dependency manager, which works fine and it’s also widely adopted. But with Xcode 11, SPM now supports iOS targets so I decided to give it a try.ViewState in SwiftUI using ViewBuilder2020-01-12T07:38:05+00:002020-01-12T07:38:05+00:00https://thomas-sivilay.github.io//morningswiftui.github.io/swiftui/2020/01/12/viewstate-in-swiftui-using-viewbuilder<p>Recently I wanted to <strong>drive a SwiftUI view content based on a <code class="language-plaintext highlighter-rouge">ViewState</code></strong>, it became pretty common to use an <code class="language-plaintext highlighter-rouge">Enum</code> to represent the different state of a View.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">enum</span> <span class="kt">ViewState</span><span class="o"><</span><span class="kt">T</span><span class="o">></span> <span class="p">{</span>
<span class="k">case</span> <span class="n">loading</span>
<span class="k">case</span> <span class="nf">loaded</span><span class="p">(</span><span class="nv">result</span><span class="p">:</span> <span class="kt">T</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>To keep it simple, we only have two states, either <code class="language-plaintext highlighter-rouge">.loaded</code> with a result using a generic type or <code class="language-plaintext highlighter-rouge">.loading</code>. Right now, it’s a bit over-engineered and we could get away with just a boolean. But with this approach I found it useful to be able to later add more states like an error state or an empty state.</p>
<h2 id="how-to-use-the-viewstate">How to use the ViewState?</h2>
<p>I already spoiled the final solution, but let’s see different way of doing it.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// It works but not elegant</span>
<span class="kd">struct</span> <span class="kt">RestaurantListView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="kd">@ObservedObject</span> <span class="k">var</span> <span class="nv">viewModel</span><span class="p">:</span> <span class="kt">RestaurantListViewModel</span> <span class="o">=</span> <span class="kt">RestaurantListViewModel</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="k">switch</span> <span class="n">viewModel</span><span class="o">.</span><span class="n">viewState</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">loading</span><span class="p">:</span>
<span class="c1">// show a loading indicator, it could be using UIKit.ActivityIndicatorView (check UIViewRepresentable).</span>
<span class="k">return</span> <span class="kt">AnyView</span><span class="p">(</span><span class="kt">ActivityIndicator</span><span class="p">())</span>
<span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">loaded</span><span class="p">(</span><span class="nv">result</span><span class="p">:</span> <span class="n">restaurants</span><span class="p">):</span>
<span class="k">return</span> <span class="kt">AnyView</span><span class="p">(</span>
<span class="kt">List</span> <span class="p">{</span>
<span class="kt">ForEach</span><span class="p">(</span><span class="n">result</span><span class="p">)</span> <span class="p">{</span> <span class="n">restaurant</span> <span class="k">in</span>
<span class="kt">RestaurantListRow</span><span class="p">(</span><span class="nv">restaurant</span><span class="p">:</span> <span class="n">restaurant</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>From a simple SwiftUI View, we could use directly the <code class="language-plaintext highlighter-rouge">ViewState</code> and with some control statements achieve our goal. We switch between the different ViewState (here it’s a property <code class="language-plaintext highlighter-rouge">@Published var viewState</code> in our <code class="language-plaintext highlighter-rouge">ViewModel</code> but you could also store it as a property @State of the RestaurantListView) and return a different view for each case.</p>
<p><strong>So what’s wrong with this?</strong> First, since we are using this switch statement, the compiler is going to be annoying and can’t infer the return type so we have to wrap the returned View using AnyView. Second, we only have one example here but if you start to use this ViewState in multiple places of your codebase, chances are you will copy this pattern of the View in all the places your use a ViewState.</p>
<h2 id="introducing-viewbuilder">Introducing <code class="language-plaintext highlighter-rouge">ViewBuilder</code></h2>
<p>With ViewBuilder we can refactor our RestaurantListView to this:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">RestaurantListView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="kd">@ObservedObject</span> <span class="k">var</span> <span class="nv">viewModel</span><span class="p">:</span> <span class="kt">RestaurantListViewModel</span> <span class="o">=</span> <span class="kt">RestaurantListViewModel</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">ViewStateView</span><span class="p">(</span>
<span class="nv">viewState</span><span class="p">:</span> <span class="n">viewModel</span><span class="o">.</span><span class="n">viewState</span><span class="p">,</span>
<span class="nv">content</span><span class="p">:</span> <span class="p">{</span> <span class="n">result</span> <span class="k">in</span>
<span class="kt">List</span> <span class="p">{</span>
<span class="kt">ForEach</span><span class="p">(</span><span class="n">result</span><span class="p">)</span> <span class="p">{</span> <span class="n">restaurant</span> <span class="k">in</span>
<span class="kt">RestaurantListRow</span><span class="p">(</span><span class="nv">restaurant</span><span class="p">:</span> <span class="n">restaurant</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now we only care about how to represent the content when it’s loaded, which also give us the associated value from <code class="language-plaintext highlighter-rouge">ViewState.loaded(result: T)</code>. Yes we could go further and move the List and ForEach logic in the ViewBuilder by checking if the associated value is an array, but I will leave it up to you since not all array might be represented the same way.</p>
<p>For the other case, when it’s <code class="language-plaintext highlighter-rouge">.loading</code> since we don’t do anything special here, it’s already treated by the <code class="language-plaintext highlighter-rouge">ViewBuilder</code> which is named ViewStateView (<em>I wasn’t inspired</em>).</p>
<p>This isn’t the first <code class="language-plaintext highlighter-rouge">ViewBuilder</code> you see, if you already play a bit with SwiftUI, I bet you already used it before. <code class="language-plaintext highlighter-rouge">VStack</code> is one of the many example. Try to jump to the definition of <code class="language-plaintext highlighter-rouge">List</code>, <code class="language-plaintext highlighter-rouge">NavigationLink</code>.. <strong>all those <code class="language-plaintext highlighter-rouge">View</code> we get out of the boxes is using <code class="language-plaintext highlighter-rouge">ViewBuilder</code> and we are simply creating our own.</strong></p>
<h2 id="creating-a-viewbuilder">Creating a <code class="language-plaintext highlighter-rouge">ViewBuilder</code></h2>
<p>How to build this <code class="language-plaintext highlighter-rouge">ViewStateView</code> is straightforward. It is almost like building a View because <code class="language-plaintext highlighter-rouge">ViewBuilder</code> are Views, the additions is that it allows the caller to inject in a closure form some content that will be used.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">struct</span> <span class="kt">ViewStateView</span><span class="o"><</span><span class="kt">Content</span><span class="p">:</span> <span class="kt">View</span><span class="p">,</span> <span class="kt">T</span><span class="o">></span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">content</span><span class="p">:</span> <span class="p">(</span><span class="kt">T</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Content</span>
<span class="k">let</span> <span class="nv">viewState</span><span class="p">:</span> <span class="kt">ViewState</span><span class="o"><</span><span class="kt">T</span><span class="o">></span>
<span class="kd">public</span> <span class="nf">init</span><span class="p">(</span><span class="nv">viewState</span><span class="p">:</span> <span class="kt">ViewState</span><span class="o"><</span><span class="kt">T</span><span class="o">></span><span class="p">,</span> <span class="kd">@ViewBuilder</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="kt">T</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Content</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">content</span> <span class="o">=</span> <span class="nf">content</span><span class="p">()</span>
<span class="k">self</span><span class="o">.</span><span class="n">viewState</span> <span class="o">=</span> <span class="n">viewState</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="k">switch</span> <span class="n">viewState</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="nf">loaded</span><span class="p">(</span><span class="k">let</span> <span class="nv">result</span><span class="p">):</span>
<span class="k">return</span> <span class="kt">AnyView</span><span class="p">(</span><span class="nf">content</span><span class="p">(</span><span class="n">result</span><span class="p">))</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">loading</span><span class="p">:</span>
<span class="k">return</span> <span class="kt">AnyView</span><span class="p">(</span><span class="kt">LoadingView</span><span class="p">(</span><span class="nv">style</span><span class="p">:</span> <span class="o">.</span><span class="n">large</span><span class="p">))</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">struct</span> <span class="kt">LoadingView</span><span class="p">:</span> <span class="kt">UIViewRepresentable</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">style</span><span class="p">:</span> <span class="kt">UIActivityIndicatorView</span><span class="o">.</span><span class="kt">Style</span>
<span class="kd">func</span> <span class="nf">makeUIView</span><span class="p">(</span><span class="nv">context</span><span class="p">:</span> <span class="kt">UIViewRepresentableContext</span><span class="o"><</span><span class="kt">ActivityIndicator</span><span class="o">></span><span class="p">)</span> <span class="o">-></span> <span class="kt">UIActivityIndicatorView</span> <span class="p">{</span>
<span class="k">return</span> <span class="kt">UIActivityIndicatorView</span><span class="p">(</span><span class="nv">style</span><span class="p">:</span> <span class="n">style</span><span class="p">)</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">updateUIView</span><span class="p">(</span><span class="n">_</span> <span class="nv">uiView</span><span class="p">:</span> <span class="kt">UIActivityIndicatorView</span><span class="p">,</span> <span class="nv">context</span><span class="p">:</span> <span class="kt">UIViewRepresentableContext</span><span class="o"><</span><span class="kt">ActivityIndicator</span><span class="o">></span><span class="p">)</span> <span class="p">{</span>
<span class="n">uiView</span><span class="o">.</span><span class="nf">startAnimating</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="conclusion">Conclusion</h2>
<p>This was a really simple example of using <code class="language-plaintext highlighter-rouge">@ViewBuilder</code> and solving our initial goal to represent <code class="language-plaintext highlighter-rouge">ViewState<T></code> in SwiftUI but there’s many other example where you could apply this again.</p>
<h3 id="bonus-show-a-vstack-on-iphone-but-switch-to-hstack-on-ipad">Bonus: Show a VStack on iPhone but switch to HStack on iPad</h3>
<p>One places where I actually used it before was to handle switching between a Vertical and Horizontal stack. The rules is arbitrary, but I ended up re-using this alternative Stack in multiple places in another project:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">Stack</span><span class="o"><</span><span class="kt">Content</span><span class="p">:</span> <span class="kt">View</span><span class="o">></span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">content</span><span class="p">:</span> <span class="kt">Content</span>
<span class="nf">init</span><span class="p">(</span><span class="kd">@ViewBuilder</span> <span class="nv">content</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="p">()</span> <span class="o">-></span> <span class="kt">Content</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">content</span> <span class="o">=</span> <span class="nf">content</span><span class="p">()</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="k">if</span> <span class="kt">AppEnvironment</span><span class="o">.</span><span class="n">current</span><span class="o">.</span><span class="n">isIphone</span> <span class="p">{</span>
<span class="k">return</span> <span class="kt">AnyView</span><span class="p">(</span><span class="kt">VStack</span><span class="p">(</span><span class="n">content</span><span class="p">))</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="kt">AnyView</span><span class="p">(</span><span class="kt">HStack</span><span class="p">(</span><span class="n">content</span><span class="p">))</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>Recently I wanted to drive a SwiftUI view content based on a ViewState, it became pretty common to use an Enum to represent the different state of a View.Show a banner from anywhere in SwiftUI using ViewModifier2019-11-05T07:38:05+00:002019-11-05T07:38:05+00:00https://thomas-sivilay.github.io//morningswiftui.github.io/swiftui/2019/11/05/show-a-banner-from-anywhere-in-swiftui-using-viewmodifier<p>Creating view isn’t hard with SwiftUI, we can quickly iterate to build our final <code class="language-plaintext highlighter-rouge">struct BannerView: View</code>. But what if we would like to display it on top of our content? <strong>What if we have multiple <code class="language-plaintext highlighter-rouge">View</code> that needs to show a banner?</strong> Aren’t we going to duplicate code in lot of places with also defining how we want to animate in/out the transition?</p>
<h2 id="the-solution">The solution</h2>
<h3 id="create-a-bannerview">Create a <code class="language-plaintext highlighter-rouge">BannerView</code></h3>
<p>It could be anything here, up to you to decide how it looks like, what do we need to show in a banner. Does it needs a Title? A subtitle? A button? For our example we will just need a data structure <code class="language-plaintext highlighter-rouge">BannerData</code> to represent what the banner needs to display.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">BannerData</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">title</span><span class="p">:</span> <span class="kt">String</span>
<span class="k">var</span> <span class="nv">actionTitle</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span> <span class="o">=</span> <span class="kc">nil</span>
<span class="c1">// Level to drive tint colors and importance of the banner.</span>
<span class="k">var</span> <span class="nv">level</span><span class="p">:</span> <span class="kt">Level</span> <span class="o">=</span> <span class="o">.</span><span class="n">info</span>
<span class="kd">enum</span> <span class="kt">Level</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="n">warning</span>
<span class="k">case</span> <span class="o">.</span><span class="n">success</span>
<span class="k">var</span> <span class="nv">tintColor</span><span class="p">:</span> <span class="kt">Color</span> <span class="p">{</span>
<span class="k">switch</span> <span class="k">self</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">warning</span><span class="p">:</span> <span class="k">return</span> <span class="o">.</span><span class="n">yellow</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">success</span><span class="p">:</span> <span class="k">return</span> <span class="o">.</span><span class="n">green</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">struct</span> <span class="kt">BannerView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">data</span><span class="p">:</span> <span class="kt">BannerData</span>
<span class="k">var</span> <span class="nv">action</span><span class="p">:</span> <span class="p">(()</span> <span class="o">-></span> <span class="kt">Void</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="n">data</span><span class="o">.</span><span class="n">title</span><span class="p">)</span>
<span class="kt">Spacer</span><span class="p">()</span>
<span class="kt">Button</span><span class="p">(</span><span class="nv">action</span><span class="p">:</span>
<span class="nf">action</span><span class="p">?()</span>
<span class="p">)</span> <span class="p">{</span>
<span class="kt">Text</span><span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">actionTitle</span> <span class="p">??</span> <span class="err">“</span><span class="kt">Action</span><span class="err">”</span><span class="p">)</span>
<span class="o">.</span><span class="nf">foregroundColor</span><span class="p">(</span><span class="o">.</span><span class="n">white</span><span class="p">)</span>
<span class="o">.</span><span class="nf">padding</span><span class="p">(</span><span class="kt">EdgeInsets</span><span class="p">(</span><span class="nv">top</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span> <span class="nv">leading</span><span class="p">:</span> <span class="mi">12</span><span class="p">,</span> <span class="nv">bottom</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span> <span class="nv">trailing</span><span class="p">:</span> <span class="mi">12</span><span class="p">))</span>
<span class="o">.</span><span class="nf">background</span><span class="p">(</span><span class="kt">Rectangle</span><span class="p">()</span><span class="o">.</span><span class="nf">foregroundColor</span><span class="p">(</span><span class="kt">Color</span><span class="o">.</span><span class="n">black</span><span class="o">.</span><span class="nf">opacity</span><span class="p">(</span><span class="mf">0.3</span><span class="p">)))</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="o">.</span><span class="nf">padding</span><span class="p">(</span><span class="kt">EdgeInsets</span><span class="p">(</span><span class="nv">top</span><span class="p">:</span> <span class="mi">12</span><span class="p">,</span> <span class="nv">leading</span><span class="p">:</span> <span class="mi">8</span><span class="p">,</span> <span class="nv">bottom</span><span class="p">:</span> <span class="mi">12</span><span class="p">,</span> <span class="nv">trailing</span><span class="p">:</span> <span class="mi">8</span><span class="p">))</span>
<span class="o">.</span><span class="nf">background</span><span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">level</span><span class="o">.</span><span class="n">tintColor</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>In your case, you might want to use an image, a subtitle, an option to be dismissible or not. But for this example we just decided to create a simple <code class="language-plaintext highlighter-rouge">BannerView</code> .</p>
<h3 id="display-the-banner-the-naive-way">Display the banner the naive way</h3>
<p>Having both <code class="language-plaintext highlighter-rouge">BannerData</code> and <code class="language-plaintext highlighter-rouge">BannerView</code> we could already display it from any other SwiftUI View.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@State</span> <span class="kd">private</span> <span class="k">var</span> <span class="nv">showBanner</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">false</span>
<span class="kd">@State</span> <span class="kd">private</span> <span class="k">var</span> <span class="nv">countTap</span><span class="p">:</span> <span class="kt">Int</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
<span class="kt">VStack</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">showBanner</span> <span class="p">{</span>
<span class="kt">BannerView</span><span class="p">(</span><span class="nv">data</span><span class="p">:</span> <span class="kt">BannerData</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="err">“</span><span class="kt">Naive</span> <span class="n">way</span><span class="err">”</span><span class="p">,</span> <span class="nv">actionTitle</span><span class="p">:</span> <span class="err">“</span><span class="kt">OK</span><span class="err">”</span><span class="p">,</span> <span class="nv">level</span><span class="p">:</span> <span class="o">.</span><span class="n">warning</span><span class="p">),</span> <span class="nv">action</span><span class="p">:</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">countTap</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="p">})</span>
<span class="o">.</span><span class="nf">animation</span><span class="p">(</span><span class="o">.</span><span class="n">easeInOut</span><span class="p">)</span>
<span class="o">.</span><span class="nf">transition</span><span class="p">(</span><span class="kt">AnyTransition</span><span class="o">.</span><span class="nf">move</span><span class="p">(</span><span class="nv">edge</span><span class="p">:</span> <span class="o">.</span><span class="n">top</span><span class="p">)</span><span class="o">.</span><span class="nf">combined</span><span class="p">(</span><span class="nv">with</span><span class="p">:</span> <span class="o">.</span><span class="n">opacity</span><span class="p">))</span>
<span class="o">.</span><span class="n">onTapGesture</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">showBanner</span> <span class="o">=</span> <span class="kc">false</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kt">Text</span><span class="p">(</span><span class="err">“</span><span class="kt">Some</span> <span class="n">text</span><span class="err">”</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>I call this the <em>naive way</em> because we are doing too much:</p>
<ul>
<li>We know that a banner will be displayed, so we wrap everything in a <code class="language-plaintext highlighter-rouge">VStack</code>.</li>
<li>We use have a local <code class="language-plaintext highlighter-rouge">@State var showBanner</code> to display it or not.</li>
<li>We define the animation and transition.</li>
</ul>
<p>Nothing is <em>wrong</em>, it could just be improved. The syntax could be more concise and less repetitive accross your app.</p>
<h3 id="introducing-viewmodifier">Introducing ViewModifier</h3>
<p>From <em>Apple</em> documentation, this is what we can read about <code class="language-plaintext highlighter-rouge">ViewModifier</code>.</p>
<blockquote>
<p>A modifier that can be applied to a view or other view modifier, <strong>producing a different version of the original value</strong>.</p>
</blockquote>
<p>The cool part is the second part of the sentence. We can produce a different version of the original value? Isn’t this what we want with a banner?</p>
<p>Given a <code class="language-plaintext highlighter-rouge">@State showBanner</code> we need to add a <code class="language-plaintext highlighter-rouge">VStack { }</code> to display the <code class="language-plaintext highlighter-rouge">BannerView</code> on top of the rest of the content.</p>
<p>So I gave it a try and this is how my ViewModifier looks like for this banner example:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">BannerViewModifier</span><span class="p">:</span> <span class="kt">ViewModifier</span> <span class="p">{</span>
<span class="kd">@Binding</span> <span class="k">var</span> <span class="nv">isPresented</span><span class="p">:</span> <span class="kt">Bool</span>
<span class="k">let</span> <span class="nv">data</span><span class="p">:</span> <span class="kt">BannerData</span>
<span class="k">let</span> <span class="nv">action</span><span class="p">:</span> <span class="p">(()</span> <span class="o">-></span> <span class="kt">Void</span><span class="p">)?</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">-></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="nv">spacing</span><span class="p">:</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">isPresented</span> <span class="p">{</span>
<span class="kt">BannerView</span><span class="p">(</span><span class="nv">data</span><span class="p">:</span> <span class="n">data</span><span class="p">)</span>
<span class="o">.</span><span class="nf">animation</span><span class="p">(</span><span class="o">.</span><span class="n">easeInOut</span><span class="p">)</span>
<span class="o">.</span><span class="nf">transition</span><span class="p">(</span><span class="kt">AnyTransition</span><span class="o">.</span><span class="nf">move</span><span class="p">(</span><span class="nv">edge</span><span class="p">:</span> <span class="o">.</span><span class="n">top</span><span class="p">)</span><span class="o">.</span><span class="nf">combined</span><span class="p">(</span><span class="nv">with</span><span class="p">:</span> <span class="o">.</span><span class="n">opacity</span><span class="p">))</span>
<span class="o">.</span><span class="n">onTapGesture</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">isPresented</span> <span class="o">=</span> <span class="kc">false</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>We basically moved almost everything we had before from the naive way, to this BannerViewModifier.</p>
<p>And to make this accessible from any view, we just need to create an extension of View.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">View</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">banner</span><span class="p">(</span><span class="nv">isPresented</span><span class="p">:</span> <span class="kt">Binding</span><span class="o"><</span><span class="kt">Bool</span><span class="o">></span><span class="p">,</span> <span class="nv">data</span><span class="p">:</span> <span class="kt">BannerData</span><span class="p">,</span> <span class="nv">action</span><span class="p">:</span> <span class="p">(()</span> <span class="o">-></span> <span class="kt">Void</span><span class="p">)?</span> <span class="o">=</span> <span class="kc">nil</span><span class="p">)</span> <span class="o">-></span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="nf">modifier</span><span class="p">(</span><span class="kt">BannerViewModifier</span><span class="p">(</span><span class="nv">isPresented</span><span class="p">:</span> <span class="n">isPresented</span><span class="p">,</span> <span class="nv">data</span><span class="p">:</span> <span class="n">data</span><span class="p">,</span> <span class="nv">action</span><span class="p">:</span> <span class="n">action</span><span class="p">))</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This gives us the ability to <strong>show a banner from any View</strong>.. Any! So we might need to show one from our main container of a View, but we could also show a contextualised one next to a button for example.</p>
<p>Finally, if we go back to our initial approach we can update this to something way more elegant:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@State</span> <span class="kd">private</span> <span class="k">var</span> <span class="nv">showBanner</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">false</span>
<span class="kd">@State</span> <span class="kd">private</span> <span class="k">var</span> <span class="nv">countTap</span><span class="p">:</span> <span class="kt">Int</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
<span class="kt">Text</span><span class="p">(</span><span class="err">“</span><span class="kt">Some</span> <span class="n">text</span><span class="err">”</span><span class="p">)</span>
<span class="o">.</span><span class="nf">banner</span><span class="p">(</span><span class="nv">isPresented</span><span class="p">:</span> <span class="err">$</span><span class="n">showBanner</span><span class="p">,</span> <span class="nv">data</span><span class="p">:</span> <span class="kt">BannerData</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="err">“</span><span class="kt">View</span> <span class="n">modifier</span> <span class="n">war</span><span class="err">”</span><span class="p">,</span> <span class="nv">actionTitle</span><span class="p">:</span> <span class="err">“</span><span class="kt">NICE</span><span class="err">”</span><span class="p">,</span> <span class="nv">level</span><span class="p">:</span> <span class="o">.</span><span class="n">warning</span><span class="p">),</span> <span class="nv">action</span><span class="p">:</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">countTap</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="p">})</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="conclusion">Conclusion</h2>
<p>Looking to what we have done, we have the ability from any feature/content to access to a nice and consice API that is highly re-usable.</p>
<p>It’s following SwiftUI principles using the tools we have been given. With <code class="language-plaintext highlighter-rouge">ViewModifier</code> we are also leveraging opaque return type, returning <code class="language-plaintext highlighter-rouge">some View</code> <strong>we hide the concrete implementation of a <code class="language-plaintext highlighter-rouge">BannerView</code></strong> to allow us to update it without having changes on the feature side. That’s a huge benefit.</p>
<p>This is just an example of how we could use <code class="language-plaintext highlighter-rouge">ViewModifier</code> which can be handy and could be applied in many more places! If you find yourself repeating common layout in multiple places, use a function builder, extract it to its own view or use ViewModifier and have an extension on View.</p>
<h3 id="project-on-github">Project on GitHub</h3>
<p>Everything is available under a project here:
https://github.com/thomas-sivilay/blog-notification-banner-swiftui</p>Creating view isn’t hard with SwiftUI, we can quickly iterate to build our final struct BannerView: View. But what if we would like to display it on top of our content? What if we have multiple View that needs to show a banner? Aren’t we going to duplicate code in lot of places with also defining how we want to animate in/out the transition?Interact with the photo library with SwiftUI2019-10-26T07:38:05+00:002019-10-26T07:38:05+00:00https://thomas-sivilay.github.io//morningswiftui.github.io/swiftui/2019/10/26/interact-photo-library-swiftui<p>Let’s see how we can bridge SwiftUI and UIKit to allow ourself to use <code class="language-plaintext highlighter-rouge">UIImagePickerController</code> which gives us the ability to let the user select a photo from his photo library.</p>
<p><img src="/morningswiftui.github.io//assets/images/20191026-picker.gif" alt="header" /></p>
<p>This is a common problem with the first version of SwiftUI that we have today, we would still have to request and use some API that are <strong>only available in UIKit</strong>. We already saw how to do similar things to use <code class="language-plaintext highlighter-rouge">MapKit</code>, Apple have a tutorial to interface with <code class="language-plaintext highlighter-rouge">UIPageViewController</code> but here we will only focus on <code class="language-plaintext highlighter-rouge">UIImagePickerController</code>.</p>
<h2 id="introduce-uiviewcontrollerrepresentable">Introduce <code class="language-plaintext highlighter-rouge">UIViewControllerRepresentable</code></h2>
<p>First, you need to create a new struct that will conform to <code class="language-plaintext highlighter-rouge">UIViewControllerRepresentable</code> which is the protocol to implement when we need to interface/integrate with a UIViewController from UIKit.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">ImagePickerViewController</span><span class="p">:</span> <span class="kt">UIViewControllerRepresentable</span> <span class="p">{</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This protocol require us to provide:</p>
<ul>
<li>A function <code class="language-plaintext highlighter-rouge">makeUIViewController(context:)</code> to initialize our UIImagePickerController instance. It’s called once when needed to be displayed.</li>
<li>A function <code class="language-plaintext highlighter-rouge">updateUIViewcontroller(_: context:)</code> to update the instance of UIImagePickerController which we don’t need in this example.</li>
<li>A nested <code class="language-plaintext highlighter-rouge">class Coordinator</code> to communicate with UIKit, in this example it will be the one conforming to <code class="language-plaintext highlighter-rouge">UIImagePickerControllerDelegate</code>.</li>
</ul>
<p>The protocol also have a default implementation of</p>
<ul>
<li>A function <code class="language-plaintext highlighter-rouge">makeCoordinator()</code> that we will override to provide our custom nested Coordinator.</li>
</ul>
<h2 id="initialization-of-the-imagepicker">Initialization of the <code class="language-plaintext highlighter-rouge">ImagePicker</code></h2>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">ImagePickerViewController</span><span class="p">:</span> <span class="kt">UIViewControllerRepresentable</span> <span class="p">{</span>
<span class="kd">@Binding</span> <span class="k">var</span> <span class="nv">presentationMode</span><span class="p">:</span> <span class="kt">PresentationMode</span>
<span class="kd">@Binding</span> <span class="k">var</span> <span class="nv">image</span><span class="p">:</span> <span class="kt">UIImage</span><span class="p">?</span>
<span class="kd">func</span> <span class="nf">makeUIViewController</span><span class="p">(</span><span class="nv">context</span><span class="p">:</span> <span class="kt">UIViewControllerRepresentableContext</span><span class="o"><</span><span class="kt">ImagePickerViewController</span><span class="o">></span><span class="p">)</span> <span class="o">-></span> <span class="kt">UIImagePickerController</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">imagePicker</span> <span class="o">=</span> <span class="kt">UIImagePickerController</span><span class="p">()</span>
<span class="n">imagePicker</span><span class="o">.</span><span class="n">sourceType</span> <span class="o">=</span> <span class="kt">UIImagePickerController</span><span class="o">.</span><span class="kt">SourceType</span><span class="o">.</span><span class="n">photoLibrary</span>
<span class="n">imagePicker</span><span class="o">.</span><span class="n">allowsEditing</span> <span class="o">=</span> <span class="kc">false</span>
<span class="n">imagePicker</span><span class="o">.</span><span class="n">delegate</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="n">coordinator</span>
<span class="k">return</span> <span class="n">imagePicker</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">updateUIViewcontroller</span><span class="p">(</span><span class="n">_</span> <span class="nv">uiViewController</span><span class="p">:</span> <span class="kt">UIImagePickerController</span><span class="p">,</span> <span class="nv">context</span><span class="p">:</span> <span class="kt">UIViewControllerRepresentableContext</span><span class="o"><</span><span class="kt">ImagePickerViewController</span><span class="o">></span><span class="p">)</span>
</code></pre></div></div>
<p>Here we just conform to the protocol by implementing the required methods. We also added a <code class="language-plaintext highlighter-rouge">@Binding var presentedMode: PresentationMode</code> to let us dismiss this ViewController when user has selected an image. That’s also why we have an optional binding to a UIImage that represent the selected image.</p>
<h2 id="a-coordinator-to-implement-uiimagepickercontrollerdelegate">A Coordinator to implement <code class="language-plaintext highlighter-rouge">UIImagePickerControllerDelegate</code></h2>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Override the default implementation to use our Coordinator</span>
<span class="kd">func</span> <span class="nf">makeCoordinator</span><span class="p">()</span> <span class="o">-></span> <span class="kt">Coordinator</span> <span class="p">{</span>
<span class="k">return</span> <span class="kt">Coordinator</span><span class="p">(</span><span class="k">self</span><span class="p">)</span> <span class="c1">// Inject ImagePickerViewController in the Coordinator</span>
<span class="p">}</span>
<span class="c1">// Nested class inside ImagePickerViewController</span>
<span class="kd">class</span> <span class="kt">Coordinator</span><span class="p">:</span> <span class="kt">NSObject</span><span class="p">:</span> <span class="kt">UIImagePickerControllerDelegate</span><span class="p">,</span> <span class="kt">UINavigationControllerDelegate</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">parent</span><span class="p">:</span> <span class="kt">ImagePickerViewController</span>
<span class="nf">init</span><span class="p">(</span><span class="n">_</span> <span class="nv">parent</span><span class="p">:</span> <span class="kt">ImagePickerViewController</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">parent</span> <span class="o">=</span> <span class="n">parent</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">imagePickerController</span><span class="p">(</span><span class="n">_</span> <span class="nv">picker</span><span class="p">:</span> <span class="kt">UIImagePickerController</span><span class="p">,</span> <span class="n">didFinishPickingMediaWithInfo</span> <span class="nv">info</span><span class="p">:</span> <span class="p">[</span><span class="kt">UIImagePickerController</span><span class="o">.</span><span class="kt">InfoKey</span> <span class="p">:</span> <span class="kt">Any</span><span class="p">])</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">imagePicked</span> <span class="o">=</span> <span class="n">info</span><span class="p">[</span><span class="o">.</span><span class="n">originalImage</span><span class="p">]</span> <span class="k">as!</span> <span class="kt">UIImage</span>
<span class="n">parent</span><span class="o">.</span><span class="n">image</span> <span class="o">=</span> <span class="n">imagePicked</span>
<span class="n">parent</span><span class="o">.</span><span class="n">presentationMode</span><span class="o">.</span><span class="nf">dismiss</span><span class="p">()</span>
<span class="n">picker</span><span class="o">.</span><span class="nf">dismiss</span><span class="p">(</span><span class="nv">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nv">completion</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">imagePickerControllerDidCancel</span><span class="p">(</span><span class="n">_</span> <span class="nv">picker</span><span class="p">:</span> <span class="kt">UIImagePickerController</span><span class="p">)</span> <span class="p">{</span>
<span class="n">parent</span><span class="o">.</span><span class="n">presentationMode</span><span class="o">.</span><span class="nf">dismiss</span><span class="p">()</span>
<span class="n">picker</span><span class="o">.</span><span class="nf">dismiss</span><span class="p">(</span><span class="nv">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nv">completion</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>If you have already use <code class="language-plaintext highlighter-rouge">UIImagePickerControllerDelegate</code> there’s nothing really surprising, we have a delegate with a method to handle a success (when user did select) and another method to handle a cancel. In both situation, we dismiss both the UIImagePickerController itself along with our <code class="language-plaintext highlighter-rouge">ImagePickerController.presentationMode</code>.</p>
<p>In case of success, we assign to the ImagePickerController the image picked by the user.</p>
<h2 id="wrap-it-in-a-view">Wrap it in a <code class="language-plaintext highlighter-rouge">View</code></h2>
<p>We are already almost done here but in order to use the <code class="language-plaintext highlighter-rouge">ImagePickerViewController</code> we need to wrap everything in a view so that any view from SwiftUI can use the picker immediately and easily.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">ImagePicker</span> <span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="kd">@EnvironmentObject</span> <span class="k">var</span> <span class="nv">userData</span><span class="p">:</span> <span class="kt">UserData</span>
<span class="kd">@Environment</span><span class="p">(\</span><span class="o">.</span><span class="n">presentationMode</span><span class="p">)</span> <span class="k">var</span> <span class="nv">presentationMode</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">ImagePickerViewController</span><span class="p">(</span><span class="nv">image</span><span class="p">:</span> <span class="err">$</span><span class="n">userData</span><span class="o">.</span><span class="n">image</span><span class="p">,</span> <span class="nv">presentationMode</span><span class="p">:</span> <span class="n">presentationMode</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">final</span> <span class="kd">class</span> <span class="kt">UserData</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">image</span><span class="p">:</span> <span class="kt">UIImage</span><span class="p">?</span> <span class="o">=</span> <span class="kc">nil</span>
<span class="p">}</span>
<span class="kd">struct</span> <span class="kt">ContentView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="kd">@EnvironmentObject</span> <span class="k">var</span> <span class="nv">userData</span><span class="p">:</span> <span class="kt">UserData</span>
<span class="kd">@State</span> <span class="k">var</span> <span class="nv">pickerIsActive</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">false</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">NavigationView</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">userData</span><span class="o">.</span><span class="n">image</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
<span class="kt">Image</span><span class="p">(</span><span class="nv">uiImage</span><span class="p">:</span> <span class="n">userData</span><span class="o">.</span><span class="n">image</span><span class="o">!</span><span class="p">)</span>
<span class="p">}</span>
<span class="kt">Button</span><span class="p">(</span><span class="nv">action</span><span class="p">:</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">pickerIsActive</span> <span class="o">=</span> <span class="kc">true</span>
<span class="p">})</span> <span class="p">{</span>
<span class="kt">Text</span><span class="p">(</span><span class="err">“</span><span class="kt">Import</span> <span class="n">image</span><span class="err">”</span><span class="p">)</span>
<span class="p">}</span>
<span class="o">.</span><span class="nf">sheet</span><span class="p">(</span><span class="nv">isPresented</span><span class="p">:</span> <span class="err">$</span><span class="n">pickerIsActive</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">ImagePicker</span><span class="p">()</span><span class="o">.</span><span class="nf">environmentObject</span><span class="p">(</span><span class="k">self</span><span class="o">.</span><span class="n">userData</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>In that example, we use the <code class="language-plaintext highlighter-rouge">ImagePicker</code> as the content of a <code class="language-plaintext highlighter-rouge">sheet()</code> which will show the picker in the new iOS13 card modal presentation. And we keep track on the state with pickerIsActive.</p>Let’s see how we can bridge SwiftUI and UIKit to allow ourself to use UIImagePickerController which gives us the ability to let the user select a photo from his photo library.Fix large title animation on iOS132019-10-14T07:38:05+00:002019-10-14T07:38:05+00:00https://thomas-sivilay.github.io//morningswiftui.github.io/ios13/2019/10/14/fix-large-title-animation-on-ios13<p>On iOS13 you might start to have your app, or have seen other apps having this issue where the large title animation isn’t animating. Instead, it stays on the pushed view controller for a split seconds before disappearing.</p>
<p><img src="/morningswiftui.github.io//assets/images/20191014-gif.gif" alt="header" /></p>
<p>This problem actually already exist in iOS12.2, iOS11</p>
<p>The animation is broken in the other way around where if you go back from the <code class="language-plaintext highlighter-rouge">SecondViewController</code> to the <code class="language-plaintext highlighter-rouge">FirstViewController</code> the largeTitle will take some time to appear.</p>
<h2 id="why-when">Why? When?</h2>
<p>To have a <code class="language-plaintext highlighter-rouge">ViewController</code> displaying its title as large you have multiple ways to achieve it:</p>
<ul>
<li>Only use <code class="language-plaintext highlighter-rouge">prefersLargeTitles</code> with <code class="language-plaintext highlighter-rouge">.automatic</code> and FirstViewController using <code class="language-plaintext highlighter-rouge">prefersLargeTitles = true</code> and the SecondViewController using <code class="language-plaintext highlighter-rouge">false</code> .</li>
<li>Override <code class="language-plaintext highlighter-rouge">largeTitleDisplayMode</code> and <code class="language-plaintext highlighter-rouge">prefersLargeTitles</code>. The FirstViewController using <code class="language-plaintext highlighter-rouge">.always</code> and <code class="language-plaintext highlighter-rouge">true</code> while the SecondViewController using <code class="language-plaintext highlighter-rouge">.never</code> and <code class="language-plaintext highlighter-rouge">false</code>.</li>
</ul>
<h3 id="what-does-the-documentation-says">What does the documentation says?</h3>
<blockquote>
<p>“When large titles are available, this property controls how the navigation bar displays the navigation item’s title. The default value of this property is UINavigationItem.LargeTitleDisplayMode.automatic, which causes the title to use the same styling as the previously displayed navigation item. You can change the value of this property to force the navigation bar to display a large title (UINavigationItem.LargeTitleDisplayMode.always) or a small title (UINavigationItem.LargeTitleDisplayMode.never) for this item.”</p>
</blockquote>
<ul>
<li>The default value of <code class="language-plaintext highlighter-rouge">largeTitleDisplayMode</code> is to have <code class="language-plaintext highlighter-rouge">.automatic</code> and <code class="language-plaintext highlighter-rouge">prefersLargeTitles</code> is <code class="language-plaintext highlighter-rouge">false</code>.</li>
<li><code class="language-plaintext highlighter-rouge">.automatic</code> actually means that it will use the previous state, so if it’s large it will use large, if it’s small it will use small.</li>
</ul>
<p>An one of the most important part of the documentation says:</p>
<blockquote>
<p>“If the prefersLargeTitles property of the navigation bar is false, this property has no effect and the navigation item’s title is always displayed as a small title.”</p>
</blockquote>
<h2 id="how-to-fix-it">How to fix it?</h2>
<h3 id="if-overriding-largetitledisplaymode">If overriding <code class="language-plaintext highlighter-rouge">largeTitleDisplayMode</code></h3>
<p>If you are using <code class="language-plaintext highlighter-rouge">navigationItem.largeTitleDisplayMode</code> you basically always have to set <code class="language-plaintext highlighter-rouge">prefersLargeTitles</code> property to be true.</p>
<p>The reason is in the documentation itself, <strong>if this property is false then largeTitleDisplayMode has no effect</strong>, and it will always use the small title which will break the animation.</p>
<h3 id="when-relying-on-automatic">When relying on <code class="language-plaintext highlighter-rouge">.automatic</code></h3>
<p>The easiest way would be to be explicit and use <code class="language-plaintext highlighter-rouge">largeTitleDisplayMode</code> to drive if a ViewController should use large title or not.</p>
<p>I thought first that <code class="language-plaintext highlighter-rouge">.automatic</code> was here to basically only give a largeTitle to the first item of your navigation. So if you start with FirstViewController then it will use a large title but if you navigate to SecondViewController it will use a small title. Same, if you present SecondViewController as a modal it then use a large title. But it’s not the case.</p>
<h3 id="convenience">Convenience</h3>
<p>Wrapping these two casess together, I added an extension to <code class="language-plaintext highlighter-rouge">UIViewController</code>:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">UIViewController</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">setLargeTitleDisplayMode</span><span class="p">(</span><span class="n">_</span> <span class="nv">largeTitleDisplayMode</span><span class="p">:</span> <span class="kt">UINavigationItem</span><span class="o">.</span><span class="kt">LargeTitleDisplayMode</span><span class="p">)</span> <span class="p">{</span>
<span class="k">switch</span> <span class="n">largeTitleDisplayMode</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">automatic</span><span class="p">:</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">navigationController</span> <span class="o">=</span> <span class="n">navigationController</span> <span class="k">else</span> <span class="p">{</span> <span class="k">break</span> <span class="p">}</span>
<span class="k">if</span> <span class="k">let</span> <span class="nv">index</span> <span class="o">=</span> <span class="n">navigationController</span><span class="o">.</span><span class="n">children</span><span class="o">.</span><span class="nf">firstIndex</span><span class="p">(</span><span class="nv">of</span><span class="p">:</span> <span class="k">self</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">setLargeTitleDisplayMode</span><span class="p">(</span><span class="n">index</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">?</span> <span class="o">.</span><span class="nv">always</span> <span class="p">:</span> <span class="o">.</span><span class="n">never</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nf">setLargeTitleDisplayMode</span><span class="p">(</span><span class="o">.</span><span class="n">always</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">case</span> <span class="o">.</span><span class="n">always</span><span class="p">,</span> <span class="o">.</span><span class="nv">never</span><span class="p">:</span>
<span class="n">navigationItem</span><span class="o">.</span><span class="n">largeTitleDisplayMode</span> <span class="o">=</span> <span class="n">largeTitleDisplayMode</span>
<span class="c1">// Even when .never, needs to be true otherwise animation will be broken on iOS11, 12, 13</span>
<span class="n">navigationController</span><span class="p">?</span><span class="o">.</span><span class="n">navigationBar</span><span class="o">.</span><span class="n">prefersLargeTitles</span> <span class="o">=</span> <span class="kc">true</span>
<span class="kd">@unknown</span> <span class="k">default</span><span class="p">:</span>
<span class="nf">assertionFailure</span><span class="p">(</span><span class="err">“</span><span class="p">\(</span><span class="kd">#function</span><span class="p">):</span> <span class="kt">Missing</span> <span class="n">handler</span> <span class="k">for</span> <span class="p">\(</span><span class="n">largeTitleDisplayMode</span><span class="p">)</span><span class="err">”</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>You can see that .automatic is now a bit different in term of logic, you might want to exclude this case and not allow it because it can be confusing. I choose to change the logic (which might be surprising and un-expected for other developers so be careful) to override to use .always only for the first item of the navigation and .never for the rest of the time. That allow you to use your SecondViewController as a modal which will also be using a large title when needed.</p>
<p>In the documentation, it’s says that largeTitleDisplayMode is only used when available. I’m not sure what are the constraints except the OS version (introduced since iOS11) but with this convenience method you can add a bit more logic to only use a large title when:</p>
<ul>
<li>Users has a 4.7” screen so that users with iPhone SE will never have a large title that will take too much space.</li>
<li>Users using a UIContentSizeCategory that is not bigger than .extraExtraExtraLarge will not have large title for the same reason</li>
</ul>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">func</span> <span class="nf">isLargeTitleAvailable</span><span class="p">()</span> <span class="o">-></span> <span class="kt">Bool</span> <span class="p">{</span>
<span class="k">switch</span> <span class="n">traitCollection</span><span class="o">.</span><span class="n">preferredContentSizeCategory</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="n">accessibilityExtraExtraExtraLarge</span><span class="p">,</span>
<span class="o">.</span><span class="n">accessibilityExtraExtraLarge</span><span class="p">,</span>
<span class="o">.</span><span class="n">accessibilityExtraLarge</span><span class="p">,</span>
<span class="o">.</span><span class="n">accessibilityLarge</span><span class="p">,</span>
<span class="o">.</span><span class="n">accessibilityMedium</span><span class="p">,</span>
<span class="o">.</span><span class="nv">extraExtraExtraLarge</span><span class="p">:</span>
<span class="k">return</span> <span class="kc">false</span>
<span class="k">default</span><span class="p">:</span>
<span class="c1">/// Exclude 4” screen or 4.7” with zoomed</span>
<span class="k">return</span> <span class="kt">UIScreen</span><span class="o">.</span><span class="n">main</span><span class="o">.</span><span class="n">bounds</span><span class="o">.</span><span class="n">height</span> <span class="o">></span> <span class="mi">568</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Other things you might want to take a look or be aware of is to exclude setting large title display mode on a ViewController that is used as a ChildViewController.</p>
<h3 id="repo">Repo</h3>
<p>You can find all examples illustrated in a project <a href="https://github.com/thomas-sivilay/blog-large-title-ios13">here</a></p>
<h3 id="what-about-swiftui">What about SwiftUI?</h3>
<p>You shouldn’t be having this issue since the way to tell SwiftUI that we want to display a large title is by wrapping our view in a NavigationView where inside your View will use .navigationBarTitle(“First”, displayMode: .large) or .navigationBarTitle(“Second”, displayMode: .inline so you don’t need to care about prefersLargeTitles.</p>On iOS13 you might start to have your app, or have seen other apps having this issue where the large title animation isn’t animating. Instead, it stays on the pushed view controller for a split seconds before disappearing.Stretchable header in SwiftUI ScrollView2019-08-17T07:38:05+00:002019-08-17T07:38:05+00:00https://thomas-sivilay.github.io//morningswiftui.github.io/swiftui/2019/08/17/stretchable-header-in-swiftui-scrollview<p>SwiftUI doesn’t provide yet a <code class="language-plaintext highlighter-rouge">TableHeaderView</code> or something similar to <code class="language-plaintext highlighter-rouge">UIScrollViewDelegate</code> but I tried to experiment with different Views to have something close to a stretchable header.</p>
<p><img src="/morningswiftui.github.io//assets/images/20190817-header.png" alt="header" /></p>
<p>The initial idea is to try to implement a stretchable header that would play well with a <code class="language-plaintext highlighter-rouge">List</code> or a <code class="language-plaintext highlighter-rouge">Form</code>.</p>
<p>And if I want to describe what I would like to achieve it will have been as simple as this:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">Example</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">minHeight</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="mi">200</span>
<span class="kd">@State</span> <span class="k">var</span> <span class="nv">stretchableHeight</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="n">minHeight</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="kt">StretchableHeader</span><span class="p">()</span><span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">height</span><span class="p">:</span> <span class="n">stretchableHeight</span><span class="p">)</span>
<span class="kt">List</span><span class="p">()</span>
<span class="o">.</span><span class="n">onScroll</span> <span class="p">{</span> <span class="n">offset</span> <span class="k">in</span>
<span class="k">self</span><span class="o">.</span><span class="n">strechableHeader</span> <span class="o">=</span> <span class="nf">max</span><span class="p">(</span><span class="n">minHeight</span><span class="p">,</span> <span class="n">minHeight</span> <span class="o">+</span> <span class="n">offset</span><span class="o">.</span><span class="n">y</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>
<h2 id="not-that-simple">Not that simple</h2>
<p>Unfortunately this doesn’t work, we don’t have an extension of View with a <code class="language-plaintext highlighter-rouge">ViewModifier onScroll</code> which will help us achieve our goal. Even if we did have this, I’m not sure if it will play nicely because we are mutating the height of the <code class="language-plaintext highlighter-rouge">StretchableHeader</code> from List.</p>
<h2 id="the-key-is-to-use-preferencekey">The key is to use PreferenceKey</h2>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Finally got something! Wanted to play with "stretchy" header in a scroll view with <a href="https://twitter.com/hashtag/SwiftUI?src=hash&ref_src=twsrc%5Etfw">#SwiftUI</a>. The key is.. preferenceKey! Still needs a lot of tweaks but it's good to see some progress. <a href="https://twitter.com/hashtag/ScrollView?src=hash&ref_src=twsrc%5Etfw">#ScrollView</a> <a href="https://twitter.com/hashtag/ContentOffset?src=hash&ref_src=twsrc%5Etfw">#ContentOffset</a> <a href="https://twitter.com/hashtag/PreferenceKey?src=hash&ref_src=twsrc%5Etfw">#PreferenceKey</a> <a href="https://t.co/cunjfBLD65">pic.twitter.com/cunjfBLD65</a></p>— Thomas Sivilay (@thomassivilay) <a href="https://twitter.com/thomassivilay/status/1161397525355458560?ref_src=twsrc%5Etfw">August 13, 2019</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>After some research and not finding much help, there is actually one blog that helped a lot in this experimentation: <a href="https://swiftui-lab.com">SwiftUI-Lab</a></p>
<p>And they will probably soon share how to achieve similar goal, hopefully in a more complete way:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Here's another example of the <a href="https://twitter.com/hashtag/SwiftUI?src=hash&ref_src=twsrc%5Etfw">#SwiftUI</a> PreferenceKey protocol in action. Maybe one day ScrollView will support refresh controls natively... until then, here's my first approximation. Blog post + source code coming soon. First I need to do some cleanup. <a href="https://t.co/CbcymKTyIG">pic.twitter.com/CbcymKTyIG</a></p>— The SwiftUI Lab (@SwiftUILab) <a href="https://twitter.com/SwiftUILab/status/1162355929230315520?ref_src=twsrc%5Etfw">August 16, 2019</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>Anyway, <code class="language-plaintext highlighter-rouge">PreferenceKey</code> was what helped me having something closer to the initial goal, you can read about it in <a href="https://swiftui-lab.com/communicating-with-the-view-tree-part-1/">this article</a></p>
<p>I use <code class="language-plaintext highlighter-rouge">PreferenceKey</code> as a way to share some values between <code class="language-plaintext highlighter-rouge">Views</code> <strong>that share the same ancestors</strong>. So in this example the value that I want to share is “what is the offset on the scroll” from the List to the StretchableHeader.</p>
<h2 id="some-solution">Some solution</h2>
<p>Instead of embedding everything in a VStack, the first change is to use a <code class="language-plaintext highlighter-rouge">ZStack</code> so we can have the <code class="language-plaintext highlighter-rouge">StretchableHeader</code> to be displayed on top of the <code class="language-plaintext highlighter-rouge">ScrollViewContentView</code>. We also need to adjust the offset of the content based on the height of header.</p>
<p>We use <code class="language-plaintext highlighter-rouge">@State</code> as our local variable to capture both the current offset of the scroll and the default height of the header.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">Example</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="kd">@State</span> <span class="k">var</span> <span class="nv">yOffset</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">let</span> <span class="nv">headerHeight</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="mi">100</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">ZStack</span> <span class="p">{</span>
<span class="kt">StretchableHeader</span><span class="p">(</span><span class="nv">offset</span><span class="p">:</span> <span class="err">$</span><span class="n">yOffset</span><span class="p">,</span> <span class="nv">headerHeight</span><span class="p">:</span> <span class="n">headerHeight</span><span class="p">)</span>
<span class="o">.</span><span class="nf">zIndex</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
<span class="kt">ScrollViewContentView</span><span class="p">(</span><span class="nv">offset</span><span class="p">:</span> <span class="err">$</span><span class="n">yOffset</span><span class="p">)</span>
<span class="o">.</span><span class="nf">offset</span><span class="p">(</span><span class="nv">y</span><span class="p">:</span> <span class="n">headerHeight</span><span class="p">)</span>
<span class="p">}</span>
<span class="o">.</span><span class="nf">onPreferenceChange</span><span class="p">(</span><span class="kt">ScrollOffsetPreferenceKey</span><span class="o">.</span><span class="k">self</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">yOffset</span> <span class="o">=</span> <span class="nv">$0</span><span class="o">.</span><span class="n">first</span> <span class="p">??</span> <span class="mi">0</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Breaking this example into two View brings more clarity but involve injecting the state as a @Binding.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">StretchableHeader</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">yOffset</span><span class="p">:</span> <span class="kt">CGFloat</span>
<span class="k">let</span> <span class="nv">headerHeight</span><span class="p">:</span> <span class="kt">CGFloat</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">GeometryReader</span> <span class="p">{</span> <span class="n">proxy</span> <span class="o">-></span> <span class="kt">AnyView</span> <span class="k">in</span>
<span class="k">return</span> <span class="kt">AnyView</span><span class="p">(</span>
<span class="kt">ZStack</span> <span class="p">{</span>
<span class="kt">Image</span><span class="p">(</span><span class="err">“</span><span class="n">catalina</span><span class="err">”</span><span class="p">)</span>
<span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">height</span><span class="p">:</span> <span class="nf">max</span><span class="p">(</span><span class="k">self</span><span class="o">.</span><span class="n">yOffset</span><span class="p">,</span> <span class="k">self</span><span class="o">.</span> <span class="n">headerHeight</span><span class="p">))</span>
<span class="o">.</span><span class="nf">clipped</span><span class="p">()</span>
<span class="kt">Text</span><span class="p">(</span><span class="err">“</span><span class="kt">Content</span> <span class="nv">offset</span><span class="p">:</span> <span class="p">\(</span><span class="k">self</span><span class="o">.</span><span class="n">offset</span><span class="p">)</span><span class="err">”</span><span class="p">)</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="n">proxy</span><span class="o">.</span><span class="n">size</span><span class="o">.</span><span class="n">width</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="nf">max</span><span class="p">(</span><span class="k">self</span><span class="o">.</span><span class="n">yOffset</span><span class="p">,</span> <span class="k">self</span><span class="o">.</span><span class="n">headerHeight</span><span class="p">))</span>
<span class="o">.</span><span class="nf">clipped</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>We use <code class="language-plaintext highlighter-rouge">GeometryReader</code> to have access to a <code class="language-plaintext highlighter-rouge">GeometryProxy</code> to help us defining the exact frame of the StretchableHeader where we keep its width but set the height to use the yOffset so that the content will grow.</p>
<p>Earlier, we said we will use a PreferenceKey to define and share the data representing the scroll offset.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">ScrollOffsetPreferenceKey</span><span class="p">:</span> <span class="kt">PreferenceKey</span><span class="p">,</span> <span class="kt">Equatable</span> <span class="p">{</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">defaultValue</span><span class="p">:</span> <span class="p">[</span><span class="kt">CGFloat</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
<span class="kd">static</span> <span class="kd">func</span> <span class="nf">reduce</span><span class="p">(</span><span class="nv">value</span><span class="p">:</span> <span class="k">inout</span> <span class="p">[</span><span class="kt">CGFloat</span><span class="p">],</span> <span class="nv">nextValue</span><span class="p">:</span> <span class="p">()</span> <span class="o">-></span> <span class="p">[</span><span class="kt">CGFloat</span><span class="p">])</span> <span class="p">{</span>
<span class="n">value</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="nv">contentsOf</span><span class="p">:</span> <span class="nf">nextValue</span><span class="p">())</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Which is already listened/handled in the ExampleView with:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@inlinable</span> <span class="kd">public</span> <span class="kd">func</span> <span class="n">onPreferenceChange</span><span class="o"><</span><span class="kt">K</span><span class="o">></span><span class="p">(</span><span class="n">_</span> <span class="nv">key</span><span class="p">:</span> <span class="kt">K</span><span class="o">.</span><span class="k">Type</span> <span class="o">=</span> <span class="kt">K</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="n">perform</span> <span class="nv">action</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="p">(</span><span class="kt">K</span><span class="o">.</span><span class="kt">Value</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Void</span><span class="p">)</span> <span class="o">-></span> <span class="kd">some</span> <span class="kt">View</span> <span class="k">where</span> <span class="kt">K</span> <span class="p">:</span> <span class="kt">PreferenceKey</span><span class="p">,</span> <span class="kt">K</span><span class="o">.</span><span class="kt">Value</span> <span class="p">:</span> <span class="kt">Equatable</span>
</code></pre></div></div>
<p>But needs to be mutated in ScrollViewContentView using:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@inlinable</span> <span class="kd">public</span> <span class="kd">func</span> <span class="n">preference</span><span class="o"><</span><span class="kt">K</span><span class="o">></span><span class="p">(</span><span class="n">key</span> <span class="nv">_</span><span class="p">:</span> <span class="kt">K</span><span class="o">.</span><span class="k">Type</span> <span class="o">=</span> <span class="kt">K</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">value</span><span class="p">:</span> <span class="kt">K</span><span class="o">.</span><span class="kt">Value</span><span class="p">)</span> <span class="o">-></span> <span class="kd">some</span> <span class="kt">View</span> <span class="k">where</span> <span class="kt">K</span> <span class="p">:</span> <span class="kt">PreferenceKey</span>
</code></pre></div></div>
<p>The way I choose to update the PreferenceKey values if by finding the minY value of the first view inside ScrollViewContentView.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">ScrollViewContentView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">correction</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="mi">85</span> <span class="o">+</span> <span class="mi">44</span>
<span class="kd">@Binding</span> <span class="k">var</span> <span class="nv">offset</span><span class="p">:</span> <span class="kt">CGFloat</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">GeometryReader</span> <span class="p">{</span> <span class="n">proxy</span> <span class="o">-></span> <span class="kt">AnyView</span> <span class="k">in</span>
<span class="k">return</span> <span class="kt">AnyView</span><span class="p">(</span>
<span class="kt">Form</span> <span class="p">{</span>
<span class="kt">GeometryReader</span> <span class="p">{</span> <span class="n">firstViewProxy</span> <span class="k">in</span>
<span class="kt">Text</span><span class="p">(</span><span class="err">“</span><span class="kt">First</span> <span class="n">view</span><span class="err">”</span><span class="p">)</span>
<span class="o">.</span><span class="nf">preference</span><span class="p">(</span><span class="nv">key</span><span class="p">:</span> <span class="kt">ScrollOffsetPreferenceKey</span><span class="o">.</span><span class="k">self</span><span class="p">,</span>
<span class="nv">value</span><span class="p">:</span> <span class="p">[</span><span class="n">firstViewProxy</span><span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">in</span><span class="p">:</span> <span class="o">.</span><span class="n">global</span><span class="p">)</span><span class="o">.</span><span class="n">minY</span> <span class="o">-</span> <span class="k">self</span><span class="o">.</span><span class="n">correction</span><span class="p">])</span>
<span class="p">}</span>
<span class="kt">Text</span><span class="p">(</span><span class="err">“</span><span class="kt">Second</span> <span class="n">view</span><span class="err">”</span><span class="p">)</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="n">proxy</span><span class="o">.</span><span class="n">size</span><span class="o">.</span><span class="n">width</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>Again GeometryReader help us finding the minY which we use as a value update of ScrollOffsetPreferenceKey.</p>
<h2 id="known-issues">Known issues</h2>
<h3 id="correction-for-navigation-bar-height">Correction for navigation bar height</h3>
<p>Maybe you noticed that in the ScrollViewContentView there is a correction value, which I’m not comfortable with and needs to change based on the displayMode of a .navigationBarTitle.</p>
<h3 id="large-title-isnt-my-friend">Large title isn’t my friend</h3>
<p>With a large title, we will need to modify the offset of the StretchableHeader based on the scrollOffset but by doing so we will again update the scrollOffset.. Then we have a nice infinite loop.</p>
<h3 id="using-first-view-isnt-a-good-idea">Using first view isn’t a good idea</h3>
<p>Using the first view to update the preference value might work for our example here, it will also play nicely if we want to have a refresh control in the stretchable header. But if we have a bigger header height, you can find some issues where we are not updating the preference key values anymore. I believe it’s because of the first view not being on the screen which will not relay frame changes anymore!</p>
<h2 id="so-whats-next">So what’s next?</h2>
<p>That was just some experimentation that I wanted to share, definitely not something I recommend but some ideas to that could be use to improve and solve the initial goal. Without much documentation and using SwiftUI at the early stage, we do with what we have and the most important is that I had fun and learned more about GeometryReader and PreferenceKey trying this.</p>SwiftUI doesn’t provide yet a TableHeaderView or something similar to UIScrollViewDelegate but I tried to experiment with different Views to have something close to a stretchable header.Customising navigation bar in iOS13 with `UINavigationBarAppearance`2019-08-10T07:38:05+00:002019-08-10T07:38:05+00:00https://thomas-sivilay.github.io//morningswiftui.github.io/ios13/2019/08/10/customizing-navigation-bar-ios13<p>Base on <a href="https://developer.apple.com/videos/play/wwdc2019/224/">Modernizing Your UI for iOS 13 - WWDC 2019</a> in iOS13 the new way to customise your navigation bar is to use <code class="language-plaintext highlighter-rouge">UINavigationBarAppearance</code>. It also gives you more granularity over:</p>
<ul>
<li>.scrollEdgeAppearance will be used if your view contains a scrollview and it’s scrolled at the top</li>
<li>.compactAppearance for iPhone in landscape</li>
<li>.standardAppearance for the rest</li>
</ul>
<h2 id="navigationbar-shadow">NavigationBar shadow</h2>
<p>I’ll suggest to start by defining if you need to show the shadow of the navigation bar or not. Because on iOS13, you use one of these methods which can override your previous customisations.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">customApperance</span><span class="p">()</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">navBarAppearance</span> <span class="o">=</span> <span class="kt">UINavigationBarAppearance</span><span class="p">()</span>
<span class="c1">// Will remove the shadow and set background back to clear</span>
<span class="n">navBarAppearance</span><span class="o">.</span><span class="nf">configureWithTransparentBackground</span><span class="p">()</span>
<span class="n">navBarAppearance</span><span class="o">.</span><span class="nf">configureWithOpaqueBackground</span><span class="p">()</span>
<span class="n">navBarAppearance</span><span class="o">.</span><span class="nf">configureWithDefaultBackground</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">With the new UINavigationBarAppearance on iOS13 we can customise how the nav bar looks like when it's scrolled at the top or when user is actually scrolling down! Also, no more shadow (if you remove it) when adding a searchController. <a href="https://twitter.com/hashtag/UINavigationBarAppearance?src=hash&ref_src=twsrc%5Etfw">#UINavigationBarAppearance</a> <a href="https://twitter.com/hashtag/iOS13?src=hash&ref_src=twsrc%5Etfw">#iOS13</a> <a href="https://twitter.com/hashtag/UIKit?src=hash&ref_src=twsrc%5Etfw">#UIKit</a> <a href="https://t.co/QxGT5Lrh3E">pic.twitter.com/QxGT5Lrh3E</a></p>— Thomas Sivilay (@thomassivilay) <a href="https://twitter.com/thomassivilay/status/1159995320396201985?ref_src=twsrc%5Etfw">August 10, 2019</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>One of the cool thing with iOS13 is that now if you have a <code class="language-plaintext highlighter-rouge">searchController</code> attached to your navigationItem you can also use this navBarAppearance to remove the shadow! Something that wasn’t as easy (or even possible?) for previous iOS versions.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">customSearchBar</span><span class="p">()</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">transparentAppearance</span> <span class="o">=</span> <span class="n">navBarAppearance</span><span class="o">.</span><span class="nf">copy</span><span class="p">()</span>
<span class="n">transparentAppearance</span><span class="o">.</span><span class="nf">configureWithTransparentBackground</span><span class="p">()</span>
<span class="n">navigationItem</span><span class="o">.</span><span class="n">searchController</span> <span class="o">=</span> <span class="n">searchController</span>
<span class="n">searchController</span><span class="o">.</span><span class="n">navigationItem</span><span class="o">.</span><span class="n">scrollEdgeAppearance</span> <span class="o">=</span> <span class="n">transparentAppearance</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="navigationbar-background">NavigationBar background</h2>
<p><img src="/morningswiftui.github.io//assets/images/20190810-1.jpg" alt="Nav 1" /></p>
<p>After configuring the appearance, you can now override the background color.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">customBackground</span><span class="p">()</span> <span class="p">{</span>
<span class="n">navBarAppearance</span><span class="o">.</span><span class="n">backgroundColor</span> <span class="o">=</span> <span class="kt">UIColor</span><span class="o">.</span><span class="n">systemGray</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This also works even if you decided previously to call <code class="language-plaintext highlighter-rouge">.configureWithTransparentBackground()</code>.</p>
<h2 id="navigation-title">Navigation title</h2>
<p>There’s two cases here, either you use a large title or not which results in two different properties to update:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">customNavBarTitle</span><span class="p">()</span> <span class="p">{</span>
<span class="n">navBarAppearance</span><span class="o">.</span><span class="n">titleTextAttributes</span> <span class="o">=</span> <span class="p">[</span>
<span class="o">.</span><span class="nv">foregroundColor</span> <span class="p">:</span> <span class="kt">UIColor</span><span class="o">.</span><span class="n">purple</span><span class="p">,</span>
<span class="o">.</span><span class="nv">font</span> <span class="p">:</span> <span class="kt">UIFont</span><span class="o">.</span><span class="nf">italicSystemFont</span><span class="p">(</span><span class="nv">ofSize</span><span class="p">:</span> <span class="mi">17</span><span class="p">)</span>
<span class="p">]</span>
<span class="n">navBarAppearance</span><span class="o">.</span><span class="n">largeTitleTextAttributes</span> <span class="o">=</span> <span class="p">[</span>
<span class="o">.</span><span class="nv">foregroundColor</span> <span class="p">:</span> <span class="kt">UIColor</span><span class="o">.</span><span class="n">systemBlue</span><span class="p">,</span>
<span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div>
<p><img src="/morningswiftui.github.io//assets/images/20190810-2.jpg" alt="Nav 2" /></p>
<p>One note here, it’s good to use a fixed font size and not preferred font size. The navigation bar height doesn’t grow and don’t give more space, this is also the default behaviour anyway.</p>
<p>There is also .titlePositionAdjustment that allows you to vertically and horizontally move the title, which could be useful if you use a custom font.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">navBarAppearance</span><span class="o">.</span><span class="n">titlePositionAdjustment</span> <span class="o">=</span> <span class="kt">UIOffset</span><span class="p">(</span><span class="nv">horizontal</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="nv">vertical</span><span class="p">:</span> <span class="mi">3</span><span class="p">)</span>
</code></pre></div></div>
<h2 id="navigation-items">Navigation Items</h2>
<p><img src="/morningswiftui.github.io//assets/images/20190810-3.jpg" alt="Nav 3" /></p>
<p>With UINavigationBarAppearance you can also individually customise the navigation bar button items appearance using UIBarButtonItemAppearance.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">customBarButtonItems</span><span class="p">()</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">buttonAppearance</span> <span class="o">=</span> <span class="kt">UIBarButtonItemAppearance</span><span class="p">()</span>
<span class="n">buttonAppearance</span><span class="o">.</span><span class="n">normal</span><span class="o">.</span><span class="n">titleTextAttributes</span> <span class="o">=</span> <span class="p">[</span><span class="o">.</span><span class="nv">foregroundColor</span> <span class="p">:</span> <span class="kt">UIColor</span><span class="o">.</span><span class="n">darkGray</span><span class="p">]</span>
<span class="n">navBarAppearance</span><span class="o">.</span><span class="n">buttonAppearance</span> <span class="o">=</span> <span class="n">buttonAppearance</span>
<span class="p">}</span>
</code></pre></div></div>
<p>There is a granularity for each bar button type:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">navBarAppearance</span><span class="o">.</span><span class="n">doneButtonAppearance</span>
<span class="n">navBarAppearance</span><span class="o">.</span><span class="n">backButtonAppearance</span>
</code></pre></div></div>
<p>Remember that by default, the done button is bolded to emphasize on the main action to take!</p>
<h2 id="everything-together">Everything together</h2>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">customizeNavigationBar</span><span class="p">()</span> <span class="p">{</span>
<span class="n">navigationItem</span><span class="o">.</span><span class="nf">setLeftBarButton</span><span class="p">(</span><span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">barButtonSystemItem</span><span class="p">:</span> <span class="o">.</span><span class="n">cancel</span><span class="p">,</span> <span class="nv">target</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="nv">action</span><span class="p">:</span> <span class="kc">nil</span><span class="p">),</span> <span class="nv">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
<span class="n">navigationItem</span><span class="o">.</span><span class="nf">setRightBarButton</span><span class="p">(</span><span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">barButtonSystemItem</span><span class="p">:</span> <span class="o">.</span><span class="n">done</span><span class="p">,</span> <span class="nv">target</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="nv">action</span><span class="p">:</span> <span class="kc">nil</span><span class="p">),</span> <span class="nv">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
<span class="n">navigationItem</span><span class="o">.</span><span class="n">searchController</span> <span class="o">=</span> <span class="n">searchController</span>
<span class="k">let</span> <span class="nv">navBarAppearance</span> <span class="o">=</span> <span class="kt">UINavigationBarAppearance</span><span class="p">()</span>
<span class="c1">// Call this first otherwise it will override your customizations</span>
<span class="n">navBarAppearance</span><span class="o">.</span><span class="nf">configureWithDefaultBackground</span><span class="p">()</span>
<span class="k">let</span> <span class="nv">buttonAppearance</span> <span class="o">=</span> <span class="kt">UIBarButtonItemAppearance</span><span class="p">()</span>
<span class="k">let</span> <span class="nv">titleTextAttributes</span> <span class="o">=</span> <span class="p">[</span><span class="kt">NSAttributedString</span><span class="o">.</span><span class="kt">Key</span><span class="o">.</span><span class="nv">foregroundColor</span> <span class="p">:</span> <span class="kt">UIColor</span><span class="o">.</span><span class="n">systemGray</span><span class="p">]</span>
<span class="n">buttonAppearance</span><span class="o">.</span><span class="n">normal</span><span class="o">.</span><span class="n">titleTextAttributes</span> <span class="o">=</span> <span class="n">titleTextAttributes</span>
<span class="n">navBarAppearance</span><span class="o">.</span><span class="n">titleTextAttributes</span> <span class="o">=</span> <span class="p">[</span>
<span class="o">.</span><span class="nv">foregroundColor</span> <span class="p">:</span> <span class="kt">UIColor</span><span class="o">.</span><span class="n">purple</span><span class="p">,</span> <span class="c1">// Navigation bar title color</span>
<span class="o">.</span><span class="nv">font</span> <span class="p">:</span> <span class="kt">UIFont</span><span class="o">.</span><span class="nf">italicSystemFont</span><span class="p">(</span><span class="nv">ofSize</span><span class="p">:</span> <span class="mi">17</span><span class="p">)</span> <span class="c1">// Navigation bar title font</span>
<span class="p">]</span>
<span class="n">navBarAppearance</span><span class="o">.</span><span class="n">largeTitleTextAttributes</span> <span class="o">=</span> <span class="p">[</span>
<span class="o">.</span><span class="nv">foregroundColor</span> <span class="p">:</span> <span class="kt">UIColor</span><span class="o">.</span><span class="n">systemBlue</span><span class="p">,</span> <span class="c1">// Navigation bar title color</span>
<span class="p">]</span>
<span class="c1">// appearance.backgroundColor = UIColor.systemGray // Navigation bar bg color</span>
<span class="c1">// appearance.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: 8) // Only works on non large title</span>
<span class="k">let</span> <span class="nv">transNavBarAppearance</span> <span class="o">=</span> <span class="n">navBarAppearance</span><span class="o">.</span><span class="nf">copy</span><span class="p">()</span>
<span class="n">transNavBarAppearance</span><span class="o">.</span><span class="nf">configureWithTransparentBackground</span><span class="p">()</span>
<span class="n">navigationController</span><span class="p">?</span><span class="o">.</span><span class="n">navigationBar</span><span class="o">.</span><span class="n">standardAppearance</span> <span class="o">=</span> <span class="n">navBarAppearance</span>
<span class="n">navigationController</span><span class="p">?</span><span class="o">.</span><span class="n">navigationBar</span><span class="o">.</span><span class="n">compactAppearance</span> <span class="o">=</span> <span class="n">navBarAppearance</span>
<span class="n">navigationController</span><span class="p">?</span><span class="o">.</span><span class="n">navigationBar</span><span class="o">.</span><span class="n">scrollEdgeAppearance</span> <span class="o">=</span> <span class="n">transNavBarAppearance</span>
<span class="n">searchController</span><span class="o">.</span><span class="n">navigationItem</span><span class="o">.</span><span class="n">standardAppearance</span> <span class="o">=</span> <span class="n">navBarAppearance</span>
<span class="n">searchController</span><span class="o">.</span><span class="n">navigationItem</span><span class="o">.</span><span class="n">compactAppearance</span> <span class="o">=</span> <span class="n">navBarAppearance</span>
<span class="n">searchController</span><span class="o">.</span><span class="n">navigationItem</span><span class="o">.</span><span class="n">scrollEdgeAppearance</span> <span class="o">=</span> <span class="n">transNavBarAppearance</span>
<span class="n">title</span> <span class="o">=</span> <span class="s">"Title"</span>
<span class="n">navigationController</span><span class="p">?</span><span class="o">.</span><span class="n">navigationBar</span><span class="o">.</span><span class="n">prefersLargeTitles</span> <span class="o">=</span> <span class="kc">true</span> <span class="c1">// Activate large title</span>
<span class="p">}</span>
</code></pre></div></div>Base on Modernizing Your UI for iOS 13 - WWDC 2019 in iOS13 the new way to customise your navigation bar is to use UINavigationBarAppearance. It also gives you more granularity over: